summaryrefslogtreecommitdiff
path: root/chef-server-webui
diff options
context:
space:
mode:
authorNuo Yan <nuo@opscode.com>2011-02-04 15:33:06 -0800
committerNuo Yan <nuo@opscode.com>2011-02-04 15:33:06 -0800
commitcec3fcd58261a7544dac38344b08c17bcfb3d1c4 (patch)
tree36f098e2dc822e5e548f023cd2de72491192de54 /chef-server-webui
parent4f00cfd3bf59460ea1f8cb270e61cd423e329fe9 (diff)
downloadchef-cec3fcd58261a7544dac38344b08c17bcfb3d1c4.tar.gz
CHEF-1999 Environment LCRUD WebUI
Diffstat (limited to 'chef-server-webui')
-rw-r--r--chef-server-webui/app/controllers/application.rb27
-rw-r--r--chef-server-webui/app/controllers/cookbooks.rb49
-rw-r--r--chef-server-webui/app/controllers/environments.rb76
-rw-r--r--chef-server-webui/app/views/environments/_form.html.erb99
-rw-r--r--chef-server-webui/app/views/environments/edit.html.erb23
-rw-r--r--chef-server-webui/app/views/environments/new.html.erb23
-rw-r--r--chef-server-webui/app/views/environments/show.html.haml6
-rw-r--r--chef-server-webui/app/views/layout/application.html.haml3
-rw-r--r--chef-server-webui/config/router.rb3
-rw-r--r--chef-server-webui/public/javascripts/chef.js70
-rw-r--r--chef-server-webui/public/javascripts/cookbook_constraint_ctrl.js74
-rw-r--r--chef-server-webui/public/javascripts/jquery.suggest.js250
-rw-r--r--chef-server-webui/public/stylesheets/chef.css17
-rw-r--r--chef-server-webui/public/stylesheets/jquery.suggest.css28
-rw-r--r--chef-server-webui/public/stylesheets/themes/djime-cerulean/style.css16
15 files changed, 652 insertions, 112 deletions
diff --git a/chef-server-webui/app/controllers/application.rb b/chef-server-webui/app/controllers/application.rb
index 5c3ad0a266..34e1d75236 100644
--- a/chef-server-webui/app/controllers/application.rb
+++ b/chef-server-webui/app/controllers/application.rb
@@ -175,9 +175,9 @@ class Application < Merb::Controller
def append_tree(name, html, node, count, parent)
to_do = node
#to_do = node.kind_of?(Chef::Node) ? node.attribute : node
- Chef::Log.error("I have #{to_do.inspect}")
+ Chef::Log.debug("I have #{to_do.inspect}")
to_do.sort{ |a,b| a[0] <=> b[0] }.each do |key, value|
- Chef::Log.error("I am #{key.inspect} #{value.inspect}")
+ Chef::Log.debug("I am #{key.inspect} #{value.inspect}")
to_send = Array.new
count += 1
is_parent = false
@@ -267,4 +267,27 @@ class Application < Merb::Controller
string.to_s.gsub(/\n/, '<br />') unless string.nil?
end
+ def format_exception(exception)
+ require 'pp'
+ pretty_params = StringIO.new
+ PP.pp({:request_params => params}, pretty_params)
+ "#{exception.class.name}: #{exception.message}\n#{pretty_params.string}\n#{exception.backtrace.join("\n")}"
+ end
+
+ def conflict?(exception)
+ exception.kind_of?(Net::HTTPServerException) && exception.message =~ /409/
+ end
+
+ def forbidden?(exception)
+ exception.kind_of?(Net::HTTPServerException) && exception.message =~ /403/
+ end
+
+ def not_found?(exception)
+ exception.kind_of?(Net::HTTPServerException) && exception.message =~ /404/
+ end
+
+ def bad_request?(exception)
+ exception.kind_of?(Net::HTTPServerException) && exception.message =~ /400/
+ end
+
end
diff --git a/chef-server-webui/app/controllers/cookbooks.rb b/chef-server-webui/app/controllers/cookbooks.rb
index 66dd1e35d9..4c6065f6dc 100644
--- a/chef-server-webui/app/controllers/cookbooks.rb
+++ b/chef-server-webui/app/controllers/cookbooks.rb
@@ -8,9 +8,9 @@
# 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.
@@ -22,17 +22,18 @@ require 'chef/cookbook_loader'
require 'chef/cookbook_version'
class Cookbooks < Application
-
+
provides :html
before :login_required
before :params_helper
-
+
attr_reader :cookbook_id
def params_helper
@cookbook_id = params[:id] || params[:cookbook_id]
end
-
+
def index
+ provides :html, :json # Because the Environment web form needs to retrieve the list of cookbooks, this method needs to provide both html and json results.
@cl = begin
if session[:environment]
result = Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("environments/#{session[:environment]}/cookbooks")
@@ -48,23 +49,23 @@ class Cookbooks < Application
Chef::Log.error("#{e}\n#{e.backtrace.join("\n")}")
@_message = {:error => $!}
{}
- end
- render
+ end
+ display @cl
end
def show
begin
# array of versions, sorted from large to small e.g. ["0.20.0", "0.1.0"]
- versions = Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("cookbooks/#{cookbook_id}")[cookbook_id].sort!{|x,y| y <=> x }
+ versions = Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("cookbooks/#{cookbook_id}")[cookbook_id].sort!{|x,y| y <=> x }
# if version is not specified in the url, get the most recent version, otherwise get the specified version
version = if params[:cb_version].nil? || params[:cb_version].empty?
versions.first
else
params[:cb_version]
end
-
+
@cookbook = Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("cookbooks/#{cookbook_id}/#{version}")
-
+
# by default always show the largest version number (assuming largest means most recent)
@other_versions = versions - [version]
raise NotFound unless @cookbook
@@ -76,14 +77,22 @@ class Cookbooks < Application
@_message = {:error => $!}
@cl = {}
render :index
- end
+ end
+ end
+
+ # GET /cookbooks/cookbook_id
+``# provides :json, for the javascript on the environments web form.
+ def cb_versions
+ provides :json
+ @versions = {cookbook_id => get_versions}
+ display @versions
end
-
+
def recipe_files
- # node = params.has_key?('node') ? params[:node] : nil
+ # node = params.has_key?('node') ? params[:node] : nil
# @recipe_files = load_all_files(:recipes, node)
r = Chef::REST.new(Chef::Config[:chef_server_url])
- @recipe_files = r.get_rest("cookbooks/#{params[:id]}/recipes")
+ @recipe_files = r.get_rest("cookbooks/#{params[:id]}/recipes")
display @recipe_files
end
@@ -92,17 +101,23 @@ class Cookbooks < Application
@recipe_files = r.get_rest("cookbooks/#{params[:id]}/attributes")
display @attribute_files
end
-
+
def definition_files
r = Chef::REST.new(Chef::Config[:chef_server_url])
@recipe_files = r.get_rest("cookbooks/#{params[:id]}/definitions")
display @definition_files
end
-
+
def library_files
r = Chef::REST.new(Chef::Config[:chef_server_url])
@recipe_files = r.get_rest("cookbooks/#{params[:id]}/libraries")
display @lib_files
end
-
+
+ private
+
+ def get_versions
+ Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("cookbooks/#{cookbook_id}")[cookbook_id].sort!{|x,y| y <=> x }
+ end
+
end
diff --git a/chef-server-webui/app/controllers/environments.rb b/chef-server-webui/app/controllers/environments.rb
index 6c8c801e01..aacc6ff018 100644
--- a/chef-server-webui/app/controllers/environments.rb
+++ b/chef-server-webui/app/controllers/environments.rb
@@ -45,37 +45,64 @@ class Environments < Application
# GET /environemnts/new
def new
@environment = Chef::Environment.new
+ load_cookbooks
render :new
end
# POST /environments
def create
@environment = Chef::Environment.new
- if @environment.update_from_params(params)
- @environment.save
- render :show
+ if @environment.update_from_params(processed_params=process_params)
+ begin
+ @environment.create
+ redirect(url(:environments), :message => { :notice => "Created Environment #{@environment.name}" })
+ rescue Net::HTTPServerException => e
+ if conflict?(e)
+ Chef::Log.debug("Got 409 conflict creating environment #{params[:name]}\n#{format_exception(e)}")
+ redirect(url(:new_environment), :message => { :error => "An environment with that name already exists"})
+ elsif forbidden?(e)
+ # Currently it's not possible to get 403 here. I leave the code here for completeness and may be useful in the future.[nuo]
+ Chef::Log.debug("Got 403 forbidden creating environment #{params[:name]}\n#{format_exception(e)}")
+ redirect(url(:new_environment), :message => { :error => "Permission Denied. You do not have permission to create an environment."})
+ else
+ Chef::Log.error("Error communicating with the API server\n#{format_exception(e)}")
+ raise
+ end
+ end
else
- raise "TODO"
+ load_cookbooks
+ # By rendering :new, the view shows errors from @environment.invalid_fields
render :new
- # redirect to new, tell them what they did wrong
end
end
# GET /environments/:id/edit
def edit
load_environment
+ load_cookbooks
render
end
# PUT /environments/:id
def update
load_environment
- @environment.update_from_params(params)
- if @environment.invalid_fields.empty? #success
- @environment.save
- render :show
+ if @environment.update_from_params(process_params(params[:id]))
+ begin
+ @environment.save
+ redirect(url(:environment, @environment.name), :message => { :notice => "Updated Environment #{@environment.name}" })
+ rescue Net::HTTPServerException => e
+ if forbidden?(e)
+ # Currently it's not possible to get 403 here. I leave the code here for completeness and may be useful in the future.[nuo]
+ Chef::Log.debug("Got 403 forbidden updating environment #{params[:name]}\n#{format_exception(e)}")
+ redirect(url(:edit_environment), :message => { :error => "Permission Denied. You do not have permission to update an environment."})
+ else
+ Chef::Log.error("Error communicating with the API server\n#{format_exception(e)}")
+ raise
+ end
+ end
else
- @environment.update_from_params(params)
+ load_cookbooks
+ # By rendering :new, the view shows errors from @environment.invalid_fields
render :edit
end
end
@@ -160,4 +187,33 @@ class Environments < Application
end
end
+ def load_cookbooks
+ begin
+ # @cookbooks is a hash, keys are cookbook names, values are their URIs.
+ @cookbooks = Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("cookbooks").keys
+ rescue Net::HTTPServerException => e
+ Chef::Log.error(format_exception(e))
+ redirect(url(:new_environment), :message => { :error => "Could not load the list of available cookbooks."})
+ end
+ end
+
+ def process_params(name=params[:name])
+ {:name => name, :description => params[:description], :attributes => params[:attributes], :cookbook_version => search_params_for_cookbook_version_constraints}
+ end
+
+ def search_params_for_cookbook_version_constraints
+ cookbook_version_constraints = {}
+ index = 0
+ params.each do |k,v|
+ cookbook_name_box_id = k[/cookbook_name_(\d+)/, 1]
+ unless cookbook_name_box_id.nil? || v.nil? || v.empty?
+ cookbook_version_constraints[index] = v + " " + params["operator_#{cookbook_name_box_id}"] + " " + params["cookbook_version_#{cookbook_name_box_id}"].strip # e.g. {"0" => "foo > 0.3.0"}
+ index = index + 1
+ end
+ end
+ Chef::Log.debug("cookbook version constraints are: #{cookbook_version_constraints.inspect}")
+ cookbook_version_constraints
+ end
+
+
end \ No newline at end of file
diff --git a/chef-server-webui/app/views/environments/_form.html.erb b/chef-server-webui/app/views/environments/_form.html.erb
index bef255decf..0fd55e3f6c 100644
--- a/chef-server-webui/app/views/environments/_form.html.erb
+++ b/chef-server-webui/app/views/environments/_form.html.erb
@@ -1,34 +1,71 @@
-<div id="#block-tables" class="block">
- <div class="content">
- <% if new_object %>
- <h3>New Environment</h3>
- <% else %>
- <h3><%= @environment.name -%></h3>
- <% end %>
- <%= form(:action => url(*route), :method => method) do -%>
- <div id="environment_name_control_container" class="block">
- <span class="label"><label for="name">name</label></span>
- <span><%= text_field(:name => "name", :value => @environment.name, :disabled => !new_object)%></span>
- </div>
- <div id="environment_description_control_container" class="block">
- <span class="label"><label for="description">description:</label></span>
- <span><%= text_area(@environment.description, :name => "description")%></span>
- </div>
- <div id="attributes_control_container">
- <span class="label"><label for="attributes">attributes:</label></span>
- <span><%= text_area(@environment.attributes.to_json, :name => "attributes")%></span>
+<div class="content environmentFormContainer">
+ <%= form(:action => form_url, :method => (method || :post), :id => form_id, :class => 'form') do -%>
+ <% unless form_for == 'edit' %>
+ <div class="group form">
+ <label class="label" for="environment_name">Name</label>
+ <% if !@environment.invalid_fields[:name].nil? && @environment.invalid_fields[:name].size != 0 %>
+ <span class="inline_error_message">The environment name contains illegal characters. The following characters are allowed: a-z, A-Z, 0-9, _, and -.</span>
+ <% end %>
+
+ <%= text_field :id => "environment_name", :name => "name", :class => "text_field", :value => params.has_key?(:name) ? h(params[:name]) : @environment.name %>
+ <span class="description">The name of the Environment</span>
</div>
- <% cookbook_version_index = 0 -%>
- <% @environment.cookbook_versions.each do |cookbook_name, constraint| -%>
- <%= partial "environments/version_selector",:cookbook_version_index => cookbook_version_index,
- :cookbook_name => cookbook_name,
- :constraint => constraint %>
- <% cookbook_version_index += 1 -%>
+ <% end %>
+
+ <div class="group form">
+ <label class="label" for="environment_description">Description</label>
+ <%= text_area(params.has_key?(:description) ? h(params[:description]) : @environment.description, :name => "description", :class => "text_area", :id => "environment_description") %>
+ <span class="description">A description of this Environment</span>
+ </div>
+
+ <div class="group form cbVerPicker" id=<%=form_for%>>
+ <label class="label" for="cookbook_version_constraints">Cookbook Version Constraints</label>
+ <% if !@environment.invalid_fields[:cookbook_version].nil? && !@environment.invalid_fields[:cookbook_version].empty? %>
+ <span class="inline_error_message" id='error'>There was a problem in one of your constraints. Please enter version constraints in acceptable formats (e.g. 0.0.0). </span>
<% end %>
- <%= partial "environments/version_selector",:cookbook_version_index => cookbook_version_index,
- :cookbook_name => nil,
- :constraint => nil %>
- <%= submit("#{new_object ? 'create' : 'update'}")%>
- <% end =%>
- </div>
+ <table class="table" id="cbVerPickerTable">
+ <tbody>
+ <tr>
+ <th class = "first" id="CookbookVersionConstraints">Name</th>
+ <th>Operator</th>
+ <th>Version</th>
+ <th class = "last">Foobar</th>
+ </tr>
+ </tbody>
+ </table>
+ <a href='javascript:void(0)' onclick='addTableRow(null, ">=", "0.0.0")'>Add Cookbook Version Constraint</a>
+ </div>
+
+ <div class="group form attrEditor">
+ <label class="label">Environment Attributes</label>
+ <%= partial 'layout/jsonedit', :json => @environment.attributes.to_json%>
+ <span class="description">
+ A JSON hash for attributes of this environment. These attributes will be applied according to the attribute precedence rules.
+ </span>
+ </div>
+
+ <div class="group">
+ <div class="actions-bar">
+ <%= submit(submit_name, :id => submit_id, :class => 'button') -%>
+ </div>
+ </div>
+ <% end =%>
</div>
+
+
+<script type='text/javascript'>
+var cookbook_names = <%= @cookbooks.to_json %>;
+
+isEdit = document.getElementById('edit');
+if (isEdit != null){
+ <%unless @environment.cookbook_versions.nil? || @environment.cookbook_versions.empty?%>
+ var existing_env_cookbook_version_constraints = <%= @environment.cookbook_versions.to_json %>;
+ for (var cookbook in existing_env_cookbook_version_constraints){
+ var operator = existing_env_cookbook_version_constraints[cookbook].split(" ")[0];
+ var version = existing_env_cookbook_version_constraints[cookbook].split(" ")[1];
+ addTableRow(cookbook, operator, version);
+ }
+ <% end %>
+}
+</script>
+<script type='text/javascript' charset="utf-8" src="/javascripts/cookbook_constraint_ctrl.js"></script>
diff --git a/chef-server-webui/app/views/environments/edit.html.erb b/chef-server-webui/app/views/environments/edit.html.erb
index 44bc9de233..c1485cc54a 100644
--- a/chef-server-webui/app/views/environments/edit.html.erb
+++ b/chef-server-webui/app/views/environments/edit.html.erb
@@ -1,6 +1,17 @@
-<%= partial "environments/navigation", :environment => @environment, :active => 'edit'%>
-
-<%= partial 'environments/form', :environment => @environment,
- :route => [:environment, @environment.name],
- :method => :put,
- :new_object => false %> \ No newline at end of file
+<div id="block-tables" class="block">
+ <div class="content">
+ <h2 class="title">Environment <%= params[:id] %></h2>
+ <div class="inner">
+ <%= partial "environments/navigation", :environment => @environment, :active => 'edit'%>
+ </div>
+ <%= partial 'environments/form', :environment => @environment,
+ :route => :environments,
+ :method => :put,
+ :form_url => url(:environment, @environment.name),
+ :form_id => 'edit_environment',
+ :form_for => 'edit',
+ :submit_name => "Update Environment",
+ :submit_id => "edit_environment_button",
+ :new_object => true %>
+ </div>
+</div> \ No newline at end of file
diff --git a/chef-server-webui/app/views/environments/new.html.erb b/chef-server-webui/app/views/environments/new.html.erb
index 1eef0fa1df..b24f2c65cf 100644
--- a/chef-server-webui/app/views/environments/new.html.erb
+++ b/chef-server-webui/app/views/environments/new.html.erb
@@ -1,6 +1,17 @@
-<%= partial "environments/navigation", :environment => @environment, :active => 'create'%>
-
-<%= partial 'environments/form', :environment => @environment,
- :route => :environments,
- :method => :post,
- :new_object => true %>
+<div id="block-tables" class="block">
+ <div class="content">
+ <h2 class="title">Environment <%= params[:id] %></h2>
+ <div class="inner">
+ <%= partial "environments/navigation", :environment => @environment, :active => 'create'%>
+ </div>
+ <%= partial 'environments/form', :environment => @environment,
+ :route => :environments,
+ :method => :post,
+ :form_url => url(:environments),
+ :form_id => 'create_environment',
+ :form_for => 'create',
+ :submit_name => "Create Environment",
+ :submit_id => "create_environment_button",
+ :new_object => true %>
+ </div>
+</div> \ No newline at end of file
diff --git a/chef-server-webui/app/views/environments/show.html.haml b/chef-server-webui/app/views/environments/show.html.haml
index dc5898258b..bf4f3cffe7 100644
--- a/chef-server-webui/app/views/environments/show.html.haml
+++ b/chef-server-webui/app/views/environments/show.html.haml
@@ -12,14 +12,14 @@
%h3 Description
= @environment.description
.left
- %h3 Cookbook Versions
+ %h3 Cookbook Version Constraints
%table.table
%tr
%th.first Name
- %th.last Version(s)
+ %th.last Version Constraints
- if @environment.cookbook_versions.empty?
%tr
- %td{:colspan => 2} This environment does not include any cookbook version restrictions
+ %td{:colspan => 2} This environment does not include any cookbook version constraints.
- else
- @environment.cookbook_versions.each do |name, versions|
%tr
diff --git a/chef-server-webui/app/views/layout/application.html.haml b/chef-server-webui/app/views/layout/application.html.haml
index 703b676316..de1d492942 100644
--- a/chef-server-webui/app/views/layout/application.html.haml
+++ b/chef-server-webui/app/views/layout/application.html.haml
@@ -3,13 +3,14 @@
%head
%meta{ "http-equiv" => "content-type", :content => "text/html; charset=utf-8" }
%title Chef Server
- = css_include_tag "base", "themes/djime-cerulean/style", "chef", "/facebox/facebox.css", "jquery-ui-1.7.1.custom", "jquery.treeTable", "jsonedit_main.css"
+ = css_include_tag "base", "themes/djime-cerulean/style", "chef", "/facebox/facebox.css", "jquery-ui-1.7.1.custom", "jquery.treeTable", "jsonedit_main.css", "jquery.suggest.css"
= js_include_tag "jquery-1.4.4.min", "jquery.jeditable.mini", "jquery.livequery", "jquery.localscroll", "jquery.scrollTo", "jquery.tools.min"
= js_include_tag "/facebox/facebox.js"
= js_include_tag "jquery-ui-1.7.1.custom.min"
= js_include_tag "jquery.treeTable.min"
= js_include_tag "chef"
= js_include_tag "drop_down_menu"
+ = js_include_tag "jquery.suggest"
= catch_content :header_content
diff --git a/chef-server-webui/config/router.rb b/chef-server-webui/config/router.rb
index e02e771dde..0f2340115c 100644
--- a/chef-server-webui/config/router.rb
+++ b/chef-server-webui/config/router.rb
@@ -56,9 +56,10 @@ Merb::Router.prepare do
match("/cookbooks/:cookbook_id/attributes", :cookbook_id => /[\w\.]+/).to(:controller => "cookbook_attributes", :action => "index")
match("/cookbooks/:cookbook_id/files", :cookbook_id => /[\w\.]+/).to(:controller => "cookbook_files", :action => "index")
+ match("/cookbooks/:cookbook_id", :cookbook_id => /[\w\.]+/, :method => 'get').to(:controller => "cookbooks", :action => "cb_versions")
match("/cookbooks/:cookbook_id/:cb_version", :cb_version => /[\w\.]+/, :method => 'get').to(:controller => "cookbooks", :action => "show").name(:show_specific_version_cookbook)
resources :cookbooks
-
+
resources :clients
match("/databags/:databag_id/databag_items", :method => 'get').to(:controller => "databags", :action => "show", :id=>":databag_id")
diff --git a/chef-server-webui/public/javascripts/chef.js b/chef-server-webui/public/javascripts/chef.js
index 2959b24ab7..07070dab7c 100644
--- a/chef-server-webui/public/javascripts/chef.js
+++ b/chef-server-webui/public/javascripts/chef.js
@@ -7,9 +7,9 @@
// 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.
@@ -42,7 +42,13 @@ $(document).ready(function(){
buildHiddenFormFromDragDrop($(this), $('ul.runListItemList'));
buildHiddenFormFromJSONEditor($(this));
});
-
+
+ $('form#edit_environment, form#create_environment').submit(function(event) {
+ var form = $(this);
+ form.append('<input type="hidden" id="attributes" name="attributes"/>');
+ $('input#attributes').attr('value', BCJTEP.save('json'))
+ });
+
$('form#edit_node, form#create_node').submit(function(event) {
var form = $(this);
var to_node = $('ul#for_node').sortable('toArray');
@@ -66,7 +72,7 @@ $(document).ready(function(){
form.append($('input#json_data').attr('value', BCJTEP.save()));
});
- $('form#edit_databag, form#create_databag').submit(function(event) {
+ $('form#edit_databag, form#create_databag').submit(function(event) {
var form = $(this);
if (form.attr('id') == 'edit_databag') {
form.append('<input type="hidden" name="_method" value="put">');
@@ -91,30 +97,30 @@ $(document).ready(function(){
var form = $(this);
if (form.attr('id') == 'edit_user') {
form.append('<input type="hidden" name="_method" value="put">');
- form.append($('input#user_new_password')).css('display', 'none');
- form.append($('input#user_admin')).css('display', 'none');
- form.append($('input#user_confirm_new_password')).css('display', 'none');
- form.append($('input#openid')).css('display', 'none');
- }
- if (form.attr('id') == 'login') {
- form.append($('input#user_name')).css('display', 'none');
- form.append($('input#password')).css('display', 'none');
- }
+ form.append($('input#user_new_password')).css('display', 'none');
+ form.append($('input#user_admin')).css('display', 'none');
+ form.append($('input#user_confirm_new_password')).css('display', 'none');
+ form.append($('input#openid')).css('display', 'none');
+ }
+ if (form.attr('id') == 'login') {
+ form.append($('input#user_name')).css('display', 'none');
+ form.append($('input#password')).css('display', 'none');
+ }
});
// livequery hidden form for link_to ajax magic
$('a[method]').livequery(function(){
var message = $(this).attr('confirm');
var method = $(this).attr('method');
-
+
if (!method && !message) return;
-
+
$(this).click(function(event){
if (message && !confirm(message)) {
event.preventDefault();
return;
}
-
+
if (method === 'post' || method === 'put' || method === 'delete') {
event.preventDefault();
var form = $("<form/>").attr('method', 'post').attr('action', this.href).attr('style', 'display: none');
@@ -125,16 +131,16 @@ $(document).ready(function(){
}
});
});
-
+
// 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();
-
+ $('.accordion .head').click(function() {
+ $(this).next().toggle('slow');
+ return false;
+ }).next().hide();
+
+ // global facebox callback
+ $('a[rel*=facebox]').facebox();
+
var enableDragDropBehavior = function() {
$('.connectedSortable').sortable({
@@ -152,23 +158,23 @@ $(document).ready(function(){
// The table tree!
$('table.tree').treeTable({ expandable: true });
$('span.expander').click(function() { $('tr#' + $(this).attr('toggle')).toggleBranch(); });
-
+
// Tooltips
- $("div.tooltip").tooltip({
+ $("div.tooltip").tooltip({
position: ['center', 'right'],
offset: [-5, 10],
effect: 'toggle',
- opacity: 0.7
+ opacity: 0.7
});
-
+
// Show the sidebars if they have text in them!
var sidebar_block_notice_children = $("#sidebar_block_notice").children().length;
var sidebar_block_children = $("#sidebar_block").children().length;
-
+
if (sidebar_block_notice_children > 0) {
$("#sidebar_block_notice").fadeIn();
}
-
+
if (sidebar_block_children > 0) {
$("#sidebar_block").fadeIn();
}
@@ -205,7 +211,7 @@ $(document).ready(function(){
var role = allRoles[i];
$('ul#availableRoles').append('<li id="role[' + role + ']" class="ui-state-highlight runListItem">' + role + '</li>');
}
-
+
};
var clearRunListFor = function(environment) {
diff --git a/chef-server-webui/public/javascripts/cookbook_constraint_ctrl.js b/chef-server-webui/public/javascripts/cookbook_constraint_ctrl.js
new file mode 100644
index 0000000000..3ef7b1647d
--- /dev/null
+++ b/chef-server-webui/public/javascripts/cookbook_constraint_ctrl.js
@@ -0,0 +1,74 @@
+function jQuerySuggest(timestamp){
+ var cb_name = retrieveCbName(timestamp);
+ populateVersionBoxContent(timestamp, cb_name);
+ document.getElementById("cookbook_version_" + timestamp).value = "0.0.0";
+}
+
+function populateVersionBoxContent(timestamp, cb_name){
+ $.getJSON('/cookbooks/'+cb_name, function(result){
+ jQuery('#cookbook_version_'+timestamp).suggest(result[cb_name]);
+ });
+}
+
+function clearVersionBox(box, timestamp){
+ populateVersionBoxContent(timestamp, retrieveCbName(timestamp));
+ //if (box.value == "0.0.0"){box.value = "";}
+ error_message = document.getElementById('inline_error_message_' + timestamp);
+ if (error_message != null)
+ $(error_message).remove();
+}
+
+function validateVersionBoxValue(box, timestamp){
+ if (box.value.match(/\d+\.\d+\.\d+/) == null){
+ if (box.value.length != 0 && document.getElementById('inline_error_message_' + timestamp) == null)
+ $(box).parent().append('<span class="inline_error_message" id="inline_error_message_' + timestamp + '" >Invalid version format. The version should be in the format of 0.0.0.</span>');
+ if (box.value.length==0)
+ box.value = "0.0.0";
+ }
+}
+
+function buildCookbookList(cookbook_names, default_cookbook){
+ if (default_cookbook != null && $.inArray(default_cookbook, cookbook_names) < 0){
+ cookbook_names.push(default_cookbook);
+ }
+ var result = '<option value=""></option>';
+ for(i=0; i<cookbook_names.length; i++){
+ result += '<option value=' + '"' + cookbook_names[i] + '" ';
+ if (cookbook_names[i] == default_cookbook)
+ result += 'selected=true>' + cookbook_names[i] + '</option>';
+ else
+ result += '>' + cookbook_names[i] + '</option>';
+ }
+ return result;
+}
+
+function buildOperatorList(default_operator){
+ return '<option value="&gt;="' + (default_operator == ">=" ? "selected=true" : "") + '>&gt;=</option>'
+ + '<option value="&gt;"' + (default_operator == ">" ? "selected=true" : "" )+ '>&gt;</option>'
+ + '<option value="="' + (default_operator == "=" ? "selected=true" : "") +'>=</option>'
+ + '<option value="&lt;"' + (default_operator == "<" ? "selected=true" : "") + '>&lt;</option>'
+ + '<option value="&lt;="' + (default_operator == "<=" ? "selected=true" : "") + '>&lt;=</option>'
+ + '<option value="~&gt;"' + (default_operator == "~>" ? "selected=true" : "") + '>~&gt;</option>';
+}
+
+function retrieveCbName(timestamp){
+ var select_box = document.getElementById("cookbook_name_" + timestamp);
+ return select_box.options[select_box.selectedIndex].value;
+}
+
+function addTableRow(default_cookbook, default_operator, default_version){
+ var timestamp = new Date().getTime();
+ var row = '<tr id=' + '"' + timestamp + '"><td>' + '<select size="1" name="cookbook_name_' + timestamp + '" ' + 'id="cookbook_name_' + timestamp + '" class="cookbook_version_constraints_cb_name" onchange="jQuerySuggest(' + timestamp + ')"'+'>'
+ + buildCookbookList(cookbook_names, default_cookbook) + '</select>'
+ + '</td>'
+ + '<td><select name="operator_' + timestamp + '">' + buildOperatorList(default_operator) + '</select></td>'
+ + '<td><input class="text" name="cookbook_version_' + timestamp +'" ' + 'id="cookbook_version_' + timestamp + '" ' + 'type="text" onfocus="clearVersionBox(this,' + timestamp + ')" onblur="validateVersionBoxValue(this,' + timestamp + ')" value="' + default_version + '"></td>'
+ + '<td><a href="javascript::void(0)" onclick="removeTableRow($(this).parent().parent())">Remove</a></td>'
+ + '</tr>';
+ $("#cbVerPickerTable tbody").append(row);
+ validateVersionBoxValue(document.getElementById("cookbook_version_" + timestamp));
+}
+
+function removeTableRow(row){
+ row.remove();
+} \ No newline at end of file
diff --git a/chef-server-webui/public/javascripts/jquery.suggest.js b/chef-server-webui/public/javascripts/jquery.suggest.js
new file mode 100644
index 0000000000..aaee89cebd
--- /dev/null
+++ b/chef-server-webui/public/javascripts/jquery.suggest.js
@@ -0,0 +1,250 @@
+
+/*
+ * jquery.suggest 1.1 - 2007-08-06
+ *
+ * Uses code and techniques from following libraries:
+ * 1. http://www.dyve.net/jquery/?autocomplete
+ * 2. http://dev.jquery.com/browser/trunk/plugins/interface/iautocompleter.js
+ *
+ * All the new stuff written by Peter Vulgaris (www.vulgarisoip.com)
+ * Feel free to do whatever you want with this file
+ *
+ */
+
+(function($) {
+
+ $.suggest = function(input, options) {
+ var $input = $(input).attr("autocomplete", "off");
+ var $results = $(document.createElement("ul"));
+
+ var timeout = false; // hold timeout ID for suggestion results to appear
+ var prevLength = 0; // last recorded length of $input.val()
+ var cache = []; // cache MRU list
+ var cacheSize = 0; // size of cache in chars (bytes?)
+
+ $results.addClass(options.resultsClass).appendTo('body');
+
+ resetPosition();
+ $(window)
+ .load(resetPosition) // just in case user is changing size of page while loading
+ .resize(resetPosition);
+
+ //Show suggestion drop down list when on focus.
+ $input.focus(function() {
+ suggest();
+ });
+
+ $input.blur(function() {
+ setTimeout(function() { $results.hide() }, 200);
+ });
+
+ // help IE users if possible
+ try {
+ $results.bgiframe();
+ } catch(e) { }
+
+ // I really hate browser detection, but I don't see any other way
+ if ($.browser.mozilla)
+ $input.keypress(processKey); // onkeypress repeats arrow keys in Mozilla/Opera
+ else
+ $input.keydown(processKey); // onkeydown repeats arrow keys in IE/Safari
+
+ function resetPosition() {
+ // requires jquery.dimension plugin
+ var offset = $input.offset();
+ $results.css({
+ top: (offset.top + input.offsetHeight) + 'px',
+ left: offset.left + 'px'
+ });
+ }
+
+ function processKey(e) {
+ // handling up/down/escape requires results to be visible
+ // handling enter/tab requires that AND a result to be selected
+ if ((/27$|38$|40$/.test(e.keyCode) && $results.is(':visible')) ||
+ (/^13$|^9$/.test(e.keyCode) && getCurrentResult())) {
+
+ if (e.preventDefault)
+ e.preventDefault();
+ if (e.stopPropagation)
+ e.stopPropagation();
+
+ e.cancelBubble = true;
+ e.returnValue = false;
+
+ switch(e.keyCode) {
+ case 38: // up
+ prevResult();
+ break;
+ case 40: // down
+ nextResult();
+ break;
+ case 9: // tab
+ case 13: // return
+ selectCurrentResult();
+ break;
+ case 27: // escape
+ $results.hide();
+ break;
+ }
+ } else {
+ if (timeout)
+ clearTimeout(timeout);
+ timeout = setTimeout(suggest, options.delay);
+ prevLength = $input.val().length;
+ }
+ }
+
+ function suggest() {
+ var q = $.trim($input.val()).replace(/\s/g, '');
+ if (q.length >= options.minchars) {
+ var filter = new RegExp(q);
+ var filtered_list = [];
+ if (q != '0.0.0'){
+ for (item in options.list) {
+ var item_string = options.list[item];
+ if (filter.test(item_string.replace(/\s/g,''))){
+ filtered_list.push(item_string);
+ }
+ }
+ } else {
+ filtered_list = options.list;
+ }
+ displayItems(filtered_list);
+ } else {
+ $results.hide();
+ }
+ }
+
+ function checkCache(q) {
+ for (var i = 0; i < cache.length; i++)
+ if (cache[i]['q'] == q) {
+ cache.unshift(cache.splice(i, 1)[0]);
+ return cache[0];
+ }
+ return false;
+ }
+
+ function addToCache(q, items, size) {
+ while (cache.length && (cacheSize + size > options.maxCacheSize)) {
+ var cached = cache.pop();
+ cacheSize -= cached['size'];
+ }
+ cache.push({
+ q: q,
+ size: size,
+ items: items
+ });
+ cacheSize += size;
+ }
+
+ function displayItems(items) {
+ if (!items)
+ return;
+
+ if (!items.length) {
+ $results.hide();
+ return;
+ }
+
+ var html = '';
+ for (var i = 0; i < items.length; i++)
+ html += '<li>' + items[i] + '</li>';
+ $results.html(html).show();
+
+ $results
+ .children('li')
+ .mouseover(function() {
+ $results.children('li').removeClass(options.selectClass);
+ $(this).addClass(options.selectClass);
+ })
+ .click(function(e) {
+ e.preventDefault();
+ e.stopPropagation();
+ selectCurrentResult();
+ });
+ }
+
+ function parseTxt(txt, q) {
+ var items = [];
+ var tokens = txt.split(options.delimiter);
+
+ // parse returned data for non-empty items
+ for (var i = 0; i < tokens.length; i++) {
+ var token = $.trim(tokens[i]);
+ if (token) {
+ token = token.replace(
+ new RegExp(q, 'ig'),
+ function(q) { return '<span class="' + options.matchClass + '">' + q + '</span>' }
+ );
+ items[items.length] = token;
+ }
+ }
+ return items;
+ }
+
+ function getCurrentResult() {
+ if (!$results.is(':visible'))
+ return false;
+ var $currentResult = $results.children('li.' + options.selectClass);
+ if (!$currentResult.length)
+ $currentResult = false;
+ return $currentResult;
+ }
+
+ function selectCurrentResult() {
+ $currentResult = getCurrentResult();
+ if ($currentResult) {
+ $input.val($currentResult.text());
+ $results.hide();
+ if (options.onSelect)
+ options.onSelect.apply($input[0]);
+ }
+ }
+
+ function nextResult() {
+ $currentResult = getCurrentResult();
+ if ($currentResult)
+ $currentResult
+ .removeClass(options.selectClass)
+ .next()
+ .addClass(options.selectClass);
+ else
+ $results.children('li:first-child').addClass(options.selectClass);
+ }
+
+ function prevResult() {
+ $currentResult = getCurrentResult();
+ if ($currentResult)
+ $currentResult
+ .removeClass(options.selectClass)
+ .prev()
+ .addClass(options.selectClass);
+ else
+ $results.children('li:last-child').addClass(options.selectClass);
+ }
+ }
+
+ $.fn.suggest = function(list, options) {
+ //if (!source)
+ // return;
+ options = options || {};
+ //options.source = source;
+ options.delay = options.delay || 100;
+ options.resultsClass = options.resultsClass || 'ac_results';
+ options.selectClass = options.selectClass || 'ac_over';
+ options.matchClass = options.matchClass || 'ac_match';
+ options.minchars = options.minchars || 0;
+ options.delimiter = options.delimiter || '\n';
+ options.onSelect = options.onSelect || false;
+ options.maxCacheSize = options.maxCacheSize || 65536;
+ options.list = list;
+
+ this.each(function() {
+ new $.suggest(this, options);
+ });
+
+ return this;
+ };
+
+})(jQuery); \ No newline at end of file
diff --git a/chef-server-webui/public/stylesheets/chef.css b/chef-server-webui/public/stylesheets/chef.css
index 2f145d146a..094b1565c7 100644
--- a/chef-server-webui/public/stylesheets/chef.css
+++ b/chef-server-webui/public/stylesheets/chef.css
@@ -52,7 +52,7 @@ dl dt { font-weight: bold; }
padding-top: 2px;
}
-div.roleFormContainer, div.nodeFormContainer{
+div.roleFormContainer, div.nodeFormContainer, div.environmentFormContainer{
padding: 0 15px 15px;
min-width:1050px;
}
@@ -70,6 +70,15 @@ div#current_run_list_display_container{
margin-left:20px;
}
+span#cookbook_constraints_picker{
+ float:left;
+ margin-left:10px;
+}
+
+span#cookbook_constraints_picker select{
+ size: 15;
+ margin-right:50px;
+}
span#environment_run_list_selection_control{
float:right;
@@ -108,7 +117,7 @@ div.runListItemListContainer{
margin-top:5px;
margin-right: 10px;
box-shadow:2px 2px 2px #dfdfdf;
- /* Safari 5 has a weird problem where these divs increase in width by 15px after updating contents with ajax.
+ /* Safari 5 has a weird problem where these divs increase in width by 15px after updating contents with ajax.
Problem doesn't occur in webkit nightlies. setting max-width works around.*/
max-width:360px;
}
@@ -300,4 +309,8 @@ h1 label {
font-size: 12px;
color: #F7F7F8;
padding-left: 10px;
+}
+
+.cookbook_version_constraints_cb_name{
+ width:80%;
} \ No newline at end of file
diff --git a/chef-server-webui/public/stylesheets/jquery.suggest.css b/chef-server-webui/public/stylesheets/jquery.suggest.css
new file mode 100644
index 0000000000..8ddee6c0d4
--- /dev/null
+++ b/chef-server-webui/public/stylesheets/jquery.suggest.css
@@ -0,0 +1,28 @@
+.ac_results {
+ width: 152px;
+ border: 1px solid gray;
+ background-color: white;
+ padding: 0;
+ margin: 0;
+ list-style: none;
+ position: absolute;
+ z-index: 10000;
+ display: none;
+}
+
+.ac_results li {
+ padding: 2px 5px;
+ white-space: nowrap;
+ color: #101010;
+ text-align: left;
+}
+
+.ac_over {
+ cursor: pointer;
+ background-color: #F0F0B8;
+}
+
+.ac_match {
+ text-decoration: underline;
+ color: black;
+} \ No newline at end of file
diff --git a/chef-server-webui/public/stylesheets/themes/djime-cerulean/style.css b/chef-server-webui/public/stylesheets/themes/djime-cerulean/style.css
index 5c5538a6f3..6acc13406a 100644
--- a/chef-server-webui/public/stylesheets/themes/djime-cerulean/style.css
+++ b/chef-server-webui/public/stylesheets/themes/djime-cerulean/style.css
@@ -211,6 +211,12 @@ hr {
font-size: .9em;
}
+.form .inline_error_message {
+ color: #FF0000;
+ font-weight: bold;
+ padding-left:5px;
+}
+
/* @group Flash messages */
.flash .message {
@@ -267,7 +273,7 @@ ul.list li .item .avatar {
/* rounded borders */
-#main, #main-navigation, #main-navigation li, #main-navigation li a, .secondary-navigation, #main .block, #sidebar .block, #sidebar h3, ul.list li,
+#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;
@@ -280,6 +286,14 @@ ul.list li .item .avatar {
-webkit-border-top-left-radius: 4px;
}
+.table th.first#CookbookVersionConstraints{
+ width:30%;
+}
+
+.table th.last#CookbookVersionConstraints{
+ width:50%;
+}
+
.table th.last {
-moz-border-radius-topright: 4px;
-webkit-border-top-right-radius: 4px;