[issue #64] Merge branch 'master' into 64-project_wiki

Conflicts:
	config/routes.rb
	db/schema.rb
This commit is contained in:
George Vinogradov 2012-01-13 18:05:42 +04:00
commit 729bd94045
105 changed files with 14495 additions and 639 deletions

View File

@ -4,7 +4,7 @@ class ApplicationController < ActionController::Base
layout :layout_by_resource
before_filter lambda { EventLog.current_controller = self },
:only => [:create, :destroy, :open_id, :auto_build, :process_build, :cancel, :publish, :change_visibility] # :update
:only => [:create, :destroy, :open_id, :auto_build, :cancel, :publish, :change_visibility] # :update
after_filter lambda { EventLog.current_controller = nil }
helper_method :get_owner

View File

@ -1,35 +1,22 @@
class BuildListsController < ApplicationController
CALLBACK_ACTIONS = [:status_build, :pre_build, :post_build, :circle_build, :new_bbdt]
CALLBACK_ACTIONS = [:publish_build, :status_build, :pre_build, :post_build, :circle_build, :new_bbdt]
NESTED_ACTIONS = [:index, :new, :create]
before_filter :authenticate_user!, :except => CALLBACK_ACTIONS
before_filter :authenticate_build_service!, :only => CALLBACK_ACTIONS
before_filter :find_project, :only => [:filter, :show, :publish]
before_filter :find_arches, :only => [:index]
# before_filter :find_project_versions, :only => [:index]
before_filter :find_build_list_by_bs, :only => [:status_build, :pre_build, :post_build]
before_filter :find_project, :only => NESTED_ACTIONS
before_filter :find_build_list, :only => [:show, :publish, :cancel]
before_filter :find_build_list_by_bs, :only => [:publish_build, :status_build, :pre_build, :post_build, :circle_build]
load_and_authorize_resource :project, :only => :index
load_and_authorize_resource :through => :project, :only => :index, :shallow => true
load_and_authorize_resource :except => CALLBACK_ACTIONS.concat([:index])
def cancel
build_list = BuildList.find(params[:id])
if build_list.cancel_build_list
redirect_to :back, :notice => t('layout.build_lists.cancel_successed')
else
redirect_to :back, :notice => t('layout.build_lists.cancel_failed')
end
end
load_and_authorize_resource :project, :only => NESTED_ACTIONS
load_and_authorize_resource :build_list, :through => :project, :only => NESTED_ACTIONS, :shallow => true
load_and_authorize_resource :except => CALLBACK_ACTIONS.concat(NESTED_ACTIONS)
def index
filter_params = params[:filter] || {}
if params[:project_id]
find_project
find_project_versions
if @project
@action_url = project_build_lists_path(@project)
else
@project = nil
@action_url = build_lists_path
end
@ -43,16 +30,61 @@ class BuildListsController < ApplicationController
end
end
def new
@build_list = BuildList.new
end
def create
notices, errors = [], []
Arch.where(:id => params[:arches]).each do |arch|
Platform.main.where(:id => params[:bpls]).each do |bpl|
@build_list = @project.build_lists.build(params[:build_list])
@build_list.bpl = bpl; @build_list.arch = arch; @build_list.user = current_user
flash_options = {:project_version => @build_list.project_version, :arch => arch.name, :bpl => bpl.name, :pl => @build_list.pl}
if @build_list.save
notices << t("flash.build_list.saved", flash_options)
else
errors << t("flash.build_list.save_error", flash_options)
end
end
end
errors << t("flash.build_list.no_arch_or_platform_selected") if errors.blank? and notices.blank?
if errors.present?
@build_list ||= BuildList.new
flash[:error] = errors.join('<br>').html_safe
render :action => :new
else
flash[:notice] = notices.join('<br>').html_safe
redirect_to @project
end
end
def show
@build_list = @project.build_lists.find(params[:id])
@item_groups = @build_list.items.group_by_level
end
def publish
@build_list = @project.build_lists.find(params[:id])
@build_list.publish
if @build_list.publish
redirect_to :back, :notice => t('layout.build_lists.publish_success')
else
redirect_to :back, :notice => t('layout.build_lists.publish_fail')
end
end
redirect_to project_build_lists_path(@project)
def cancel
if @build_list.cancel
redirect_to :back, :notice => t('layout.build_lists.cancel_success')
else
redirect_to :back, :notice => t('layout.build_lists.cancel_fail')
end
end
def publish_build
@build_list.status = (params[:status].to_i == 0 ? BuildList::BUILD_PUBLISHED : BuildList::FAILED_PUBLISH)
@build_list.notified_at = Time.current
@build_list.save
render :nothing => true, :status => 200
end
def status_build
@ -62,7 +94,6 @@ class BuildListsController < ApplicationController
@build_list.container_path = params[:container_path]
@build_list.notified_at = Time.current
@build_list.save
render :nothing => true, :status => 200
@ -71,7 +102,6 @@ class BuildListsController < ApplicationController
def pre_build
@build_list.status = BuildServer::BUILD_STARTED
@build_list.notified_at = Time.current
@build_list.save
render :nothing => true, :status => 200
@ -81,9 +111,10 @@ class BuildListsController < ApplicationController
@build_list.status = params[:status]
@build_list.container_path = params[:container_path]
@build_list.notified_at = Time.current
@build_list.save
@build_list.delay.publish if @build_list.auto_publish # && @build_list.can_publish?
render :nothing => true, :status => 200
end
@ -91,7 +122,6 @@ class BuildListsController < ApplicationController
@build_list.is_circle = true
@build_list.container_path = params[:container_path]
@build_list.notified_at = Time.current
@build_list.save
render :nothing => true, :status => 200
@ -102,10 +132,9 @@ class BuildListsController < ApplicationController
@build_list.name = params[:name]
@build_list.additional_repos = ActiveSupport::JSON.decode(params[:additional_repos])
@build_list.set_items(ActiveSupport::JSON.decode(params[:items]))
@build_list.notified_at = Time.current
@build_list.is_circle = (params[:is_circular] != "0")
@build_list.is_circle = (params[:is_circular].to_i != 0)
@build_list.bs_id = params[:id]
params[:arch]
@build_list.notified_at = Time.current
@build_list.save
render :nothing => true, :status => 200
@ -114,16 +143,11 @@ class BuildListsController < ApplicationController
protected
def find_project
@project = Project.find params[:project_id]
@project = Project.find_by_id params[:project_id]
end
def find_arches
@arches = Arch.recent
end
def find_project_versions
@git_repository = @project.git_repository
@project_versions = @project.versions
def find_build_list
@build_list = BuildList.find(params[:id])
end
def find_build_list_by_bs

View File

@ -0,0 +1,72 @@
class CommentsController < ApplicationController
before_filter :authenticate_user!
before_filter :set_commentable, :only => [:index, :edit, :create]
before_filter :find_project, :only => [:index]
before_filter :find_comment, :only => [:edit, :update, :destroy]
authorize_resource :only => [:show, :edit, :update, :destroy]
authorize_resource :project, :only => [:index]
def index
@comments = @commentable.comments
end
def create
@comment = @commentable.comments.build(params[:comment])
@comment.user = current_user
if @comment.save
flash[:notice] = I18n.t("flash.comment.saved")
redirect_to :back
else
flash[:error] = I18n.t("flash.comment.save_error")
render :action => 'new'
end
end
def edit
@issue = @commentable
@project = @issue.project
end
def update
if @comment.update_attributes(params[:comment])
flash[:notice] = I18n.t("flash.comment.saved")
#redirect_to :back
redirect_to [@comment.commentable.project, @comment.commentable]
else
flash[:error] = I18n.t("flash.comment.save_error")
render :action => 'new'
end
end
def destroy
@comment.destroy
flash[:notice] = t("flash.comment.destroyed")
redirect_to :back
end
private
def find_commentable
#params.each do |name, value|
# if name =~ /(.+)_id$/
# return $1.classify.constantize.find(value)
# end
#end
#nil
return Issue.find(params[:issue_id])
end
def set_commentable
@commentable = find_commentable
end
def find_comment
@comment = Comment.find(params[:id])
end
def find_project
@project = @comment.commentable.project
end
end

View File

@ -0,0 +1,76 @@
class IssuesController < ApplicationController
before_filter :authenticate_user!
before_filter :find_project
before_filter :find_issue_by_serial_id, :only => [:show, :edit, :update, :destroy]
load_and_authorize_resource :project
load_and_authorize_resource :issue, :through => :project, :find_by => :serial_id
autocomplete :user, :uname
def index
@issues = @project.issues
case params[:status]
when 'open'
@issues = @issues.where(:status => 'open')
when 'closed'
@issues = @issues.where(:status => 'closed')
end
@issues = @issues.paginate :per_page => 10, :page => params[:page]
end
def new
@issue = Issue.new(:project => @project)
end
def create
@user_id = params[:user_id]
@user_uname = params[:user_uname]
@issue = Issue.new(params[:issue])
@issue.user_id = @user_id
@issue.project_id = @project.id
if @issue.save
flash[:notice] = I18n.t("flash.issue.saved")
redirect_to project_issues_path(@project)
else
flash[:error] = I18n.t("flash.issue.save_error")
render :action => :new
end
end
def edit
@user_id = @issue.user_id
@user_uname = @issue.user.uname
end
def update
@user_id = params[:user_id].blank? ? @issue.user_id : params[:user_id]
@user_uname = params[:user_uname].blank? ? @issue.user.uname : params[:user_uname]
if @issue.update_attributes( params[:issue].merge({:user_id => @user_id}) )
flash[:notice] = I18n.t("flash.issue.saved")
redirect_to [@project, @issue]
else
flash[:error] = I18n.t("flash.issue.save_error")
render :action => :new
end
end
def destroy
@issue.destroy
flash[:notice] = t("flash.issue.destroyed")
redirect_to root_path
end
private
def find_project
@project = Project.find(params[:project_id])
end
def find_issue_by_serial_id
@issue = @project.issues.find_by_serial_id!(params[:id])
end
end

View File

@ -30,7 +30,7 @@ class PlatformsController < ApplicationController
{:name => p.name,
:architectures => ['i586', 'x86_64'],
:repositories => p.repositories.map(&:name),
:url => "http://#{request.host_with_port}/downloads/#{p.name}/repository/"}
:url => p.public_downloads_url(request.host_with_port)}
end
}
end
@ -64,7 +64,7 @@ class PlatformsController < ApplicationController
flash[:notice] = I18n.t("flash.platform.saved")
redirect_to @platform
else
flash[:error] = I18n.t("flash.platform.saved_error")
flash[:error] = I18n.t("flash.platform.save_error")
render :action => :new
end
end
@ -81,7 +81,7 @@ class PlatformsController < ApplicationController
flash[:notice] = I18n.t("flash.platform.saved")
redirect_to @platform
else
flash[:error] = I18n.t("flash.platform.saved_error")
flash[:error] = I18n.t("flash.platform.save_error")
render :action => :new
end
end

View File

@ -4,14 +4,12 @@ class ProjectsController < ApplicationController
belongs_to :user, :group, :polymorphic => true, :optional => true
before_filter :authenticate_user!, :except => :auto_build
before_filter :find_project, :only => [:show, :edit, :update, :destroy, :fork, :build, :process_build]
before_filter :find_project, :only => [:show, :edit, :update, :destroy, :fork]
before_filter :get_paths, :only => [:new, :create, :edit, :update]
load_and_authorize_resource
def index
# puts parent.inspect
# puts parent.is_a? User
@projects = if parent? and !parent.nil?
parent.projects
else
@ -95,54 +93,6 @@ class ProjectsController < ApplicationController
render :nothing => true
end
def build
@arches = Arch.recent
@bpls = Platform.main
@pls = @project.repositories.collect { |rep| ["#{rep.platform.name}/#{rep.name}", rep.platform.id] }
@project_versions = @project.versions
end
def process_build
@arch_ids = params[:build][:arches].select{|_,v| v == "1"}.collect{|x| x[0].to_i }
@arches = Arch.where(:id => @arch_ids)
@project_version = params[:build][:project_version]
bpls_ids = params[:build][:bpl].blank? ? [] : params[:build][:bpl].select{|_,v| v == "1"}.collect{|x| x[0].to_i }
bpls = Platform.where(:id => bpls_ids)
pl = Platform.find params[:build][:pl]
update_type = params[:build][:update_type]
build_requires = params[:build][:build_requires]
@project_versions = @project.versions
if !check_arches || !check_project_versions
@arches = Arch.recent
@bpls = Platform.main
@pls = @project.repositories.collect { |rep| ["#{rep.platform.name}/#{rep.name}", rep.platform.id] }
render :action => "build"
else
flash[:notice], flash[:error] = "", ""
@arches.each do |arch|
bpls.each do |bpl|
build_list = @project.build_lists.new(:arch => arch, :project_version => @project_version,
:pl => pl, :bpl => bpl, :update_type => update_type,
:build_requires => build_requires, :user => current_user)
if build_list.save
flash[:notice] += t("flash.build_list.saved", :project_version => @project_version, :arch => arch.name, :bpl => bpl.name, :pl => pl)
else
flash[:error] += t("flash.build_list.save_error", :project_version => @project_version, :arch => arch.name, :bpl => bpl.name, :pl => pl)
end
end
end
redirect_to project_path(@project)
end
end
protected
def get_paths
@ -163,28 +113,4 @@ class ProjectsController < ApplicationController
def find_project
@project = Project.find params[:id]
end
def check_arches
if @arch_ids.blank?
flash[:error] = t("flash.build_list.no_arch_selected")
false
elsif @arch_ids.length != @arches.length
flash[:error] = t("flash.build_list.no_arch_found")
false
else
true
end
end
def check_project_versions
if @project_version.blank?
flash[:error] = t("flash.build_list.no_project_version_selected")
false
elsif !@project_versions.flatten.include?(@project_version)
flash[:error] = t("flash.build_list.no_project_version_found", :project_version => @project_version)
false
else
true
end
end
end

View File

@ -54,7 +54,6 @@ class RepositoriesController < ApplicationController
def add_project
if params[:project_id]
@project = Project.find(params[:project_id])
# params[:project_id] = nil
unless @repository.projects.find_by_name(@project.name)
@repository.projects << @project
flash[:notice] = t('flash.repository.project_added')
@ -63,13 +62,35 @@ class RepositoriesController < ApplicationController
end
redirect_to repository_path(@repository)
else
if @repository.platform.platform_type == 'main'
@projects = Project.addable_to_repository(@repository.id).by_visibilities(['open']).paginate(:page => params[:project_page])
else
@projects = Project.addable_to_repository(@repository.id).paginate(:page => params[:project_page])
render :projects_list
end
render 'projects_list'
end
def projects_list
owner_subquery = "
INNER JOIN (
SELECT id, 'User' AS type, uname
FROM users
UNION
SELECT id, 'Group' AS type, uname
FROM groups
) AS owner
ON projects.owner_id = owner.id AND projects.owner_type = owner.type"
colName = ['owner.uname', 'projects.name']
sort_col = params[:iSortCol_0] || 0
sort_dir = params[:sSortDir_0]=="asc" ? 'asc' : 'desc'
order = "#{colName[sort_col.to_i]} #{sort_dir}"
@projects = Project.joins(owner_subquery).addable_to_repository(@repository.id)
@projects = @projects.paginate(:page => (params[:iDisplayStart].to_i/params[:iDisplayLength].to_i).to_i + 1, :per_page => params[:iDisplayLength])
@projects = @projects.by_visibilities(['open']) if @repository.platform.platform_type == 'main'
@total_projects = @projects.count
@projects = @projects.where(['projects.name LIKE ?', "#{params[:sSearch]}%"]) if params[:sSearch] and !params[:sSearch].empty?
@total_project = @projects.count
@projects = @projects.order(order)#.includes(:owner) #WTF????
render :partial => 'proj_ajax', :layout => false
end
def remove_project

View File

@ -0,0 +1,25 @@
class SubscribesController < ApplicationController
before_filter :authenticate_user!
load_and_authorize_resource :project
load_and_authorize_resource :issue, :through => :project, :find_by => :serial_id
load_and_authorize_resource :subscribe, :through => :issue, :find_by => :user_id
def create
@subscribe = @issue.subscribes.build(:user_id => current_user.id)
if @subscribe.save
flash[:notice] = I18n.t("flash.subscribe.saved")
redirect_to :back
else
flash[:error] = I18n.t("flash.subscribe.saved_error")
redirect_to :back
end
end
def destroy
@subscribe.destroy
flash[:notice] = t("flash.subscribe.destroyed")
redirect_to :back
end
end

View File

@ -0,0 +1,2 @@
module CommentsHelper
end

View File

@ -0,0 +1,2 @@
module IssuesHelper
end

View File

@ -0,0 +1,2 @@
module SubscribesHelper
end

View File

@ -5,7 +5,31 @@ class UserMailer < ActionMailer::Base
def new_user_notification(user)
@user = user
mail(:to => user.email, :subject => "Регистрация на проекте «#{APP_CONFIG['project_name']}»") do |format|
mail(:to => user.email, :subject => I18n.t("notifications.subjects.new_user_notification", :project_name => APP_CONFIG['project_name'])) do |format|
format.html
end
end
def new_comment_notification(comment, user)
@user = user
@comment = comment
mail(:to => user.email, :subject => I18n.t("notifications.subjects.new_comment_notification")) do |format|
format.html
end
end
def new_issue_notification(issue, user)
@user = user
@issue = issue
mail(:to => user.email, :subject => I18n.t("notifications.subjects.new_issue_notification")) do |format|
format.html
end
end
def issue_assign_notification(issue, user)
@user = user
@issue = issue
mail(:to => user.email, :subject => I18n.t("notifications.subjects.issue_assign_notification")) do |format|
format.html
end
end

View File

@ -1,147 +1,97 @@
# If rules goes one by one CanCan joins them by 'OR' sql operator
# If rule has multiple conditions CanCan joins them by 'AND' sql operator
# WARNING:
# - put cannot rules _after_ can rules and not before!
# - beware inner joins. Use sub queries against them!
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
@user = user
if user.admin?
can :manage, :all
cannot :destroy, Subscribe
cannot :create, Subscribe
else
#WARNING:
# - put cannot rules _after_ can rules and not before!
# - beware inner joins. Use sub queries against them!
# Shared rights between guests and registered users
can :forbidden, Platform
can :read, [Repository, Platform], :visibility => 'open'
# TODO remove because auth callbacks skipped
can :auto_build, Project
can [:status_build, :pre_build, :post_build, :circle_build, :new_bbdt], BuildList
can [:publish_build, :status_build, :pre_build, :post_build, :circle_build, :new_bbdt], BuildList
# Guest rights
if user.guest?
if user.guest? # Guest rights
can :create, User
# Registered user rights
else
can [:read, :platforms], Category
else # Registered user rights
can [:show, :autocomplete_user_uname], User
can [:read, :create], Group
can [:update, :manage_members], Group do |group|
group.objects.exists?(:object_type => 'User', :object_id => user.id, :role => 'admin') # or group.owner_id = user.id
end
can :destroy, Group, :owner_id => user.id
can :create, Project
can :read, Project, :visibility => 'open'
can :read, Project, :owner_type => 'User', :owner_id => user.id
can :read, Project, :owner_type => 'Group', :owner_id => user.group_ids
can(:read, Project, read_relations_for('projects')) {|project| local_reader? project}
can(:write, Project) {|project| local_writer? project} # for grack
can([:update, :manage_collaborators], Project) {|project| local_admin? project}
can(:fork, Project) {|project| can? :read, project}
can(:destroy, Project) {|project| owner? project}
can :create, AutoBuildList
can [:index, :destroy], AutoBuildList, :project_id => user.own_project_ids
# If rules goes one by one CanCan joins them by 'OR' sql operator
can :read, Project, :visibility => 'open'
can :read, Group
can :read, User
cannot :index, User
can :manage_collaborators, Project do |project|
project.relations.exists? :object_id => user.id, :object_type => 'User', :role => 'admin'
end
can :manage_members, Group do |group|
group.objects.exists? :object_id => user.id, :object_type => 'User', :role => 'admin'
end
# Put here model names which objects can user create
can :create, Project
can :create, Group
can :publish, BuildList do |build_list|
build_list.can_published? && build_list.project.relations.exists?(:object_type => 'User', :object_id => user.id)
end
can [:index, :read], BuildList, ["build_lists.project_id IN (SELECT id FROM projects WHERE visibility = ?)", 'open'] do |build_list|
build_list.project.public?
end
can [:index, :read], BuildList, build_lists_in_relations_with(:object_type => 'User', :object_id => user.id) do |build_list|
build_list.project.relations.exists?(:object_type => 'User', :object_id => user.id)
end
can :read, BuildList, :project => {:visibility => 'open'}
can :read, BuildList, :project => {:owner_type => 'User', :owner_id => user.id}
can :read, BuildList, :project => {:owner_type => 'Group', :owner_id => user.group_ids}
can(:read, BuildList, read_relations_for('build_lists', 'projects')) {|build_list| can? :read, build_list.project}
can(:create, BuildList) {|build_list| can? :write, build_list.project}
can(:publish, BuildList) {|build_list| build_list.can_publish? && can?(:write, build_list.project)}
can :read, Platform, :visibility => 'open'
can :read, Platform, :owner_type => 'User', :owner_id => user.id
can :read, Platform, :owner_type => 'Group', :owner_id => user.group_ids
can(:read, Platform, read_relations_for('platforms')) {|platform| local_reader? platform}
can(:update, Platform) {|platform| local_admin? platform}
can([:freeze, :unfreeze, :destroy], Platform) {|platform| owner? platform}
can :autocomplete_user_uname, Platform
# TODO delegate to platform?
can :read, Repository, :visibility => 'open'
can :read, Repository, :owner_type => 'User', :owner_id => user.id
can :read, Repository, :owner_type => 'Group', :owner_id => user.group_ids
can(:read, Repository, read_relations_for('repositories')) {|repository| local_reader? repository}
can(:create, Repository) {|repository| local_admin? repository.platform}
can([:update, :add_project, :remove_project], Repository) {|repository| local_admin? repository}
can([:change_visibility, :settings, :destroy], Repository) {|repository| owner? repository}
can :read, Product, :platform => {:owner_type => 'User', :owner_id => user.id}
can :read, Product, :platform => {:owner_type => 'Group', :owner_id => user.group_ids}
can(:manage, Product, read_relations_for('products', 'platforms')) {|product| local_admin? product.platform}
can [:read, :platforms], Category
can [:read, :create], PrivateUser, :platform => {:owner_type => 'User', :owner_id => user.id}
# If rule has multiple conditions CanCan joins them by 'AND' sql operator
can [:read, :update, :process_build, :build, :destroy], Project, :owner_type => 'User', :owner_id => user.id
#can :read, Project, :relations => {:role => 'reader'}
can :read, Project, projects_in_relations_with(:role => 'reader', :object_type => 'User', :object_id => user.id) do |project|
#The can? and cannot? call cannot be used with a raw sql 'can' definition.
project.relations.exists?(:role => 'reader', :object_type => 'User', :object_id => user.id)
end
#can [:update, :process_build, :build], Project, :relations => {:role => 'writer'}
can [:read, :update, :process_build, :build], Project, projects_in_relations_with(:role => ['writer', 'admin'], :object_type => 'User', :object_id => user.id) do |project|
project.relations.exists?(:role => ['writer', 'admin'], :object_type => 'User', :object_id => user.id)
end
can [:read, :update, :destroy], Product, products_in_relations_with(:role => ['writer', 'admin'], :object_type => 'User', :object_id => user.id) do |product|
product.relations.exists?(:role => 'admin', :object_type => 'User', :object_id => user.id)
end
# Small CanCan hack by Product.new(:platform_id => ...)
can [:new, :create], Product do |product|
product.platform.relations.exists?(:role => 'admin', :object_type => 'User', :object_id => user.id)
end
can [:read, :update], Group, groups_in_relations_with(:role => ['writer', 'admin'], :object_type => 'User', :object_id => user.id) do |group|
group.objects.exists?(:role => ['writer', 'admin'], :object_type => 'User', :object_id => user.id)
end
can :manage, Platform, :owner_type => 'User', :owner_id => user.id
can :manage, Platform, platforms_in_relations_with(:role => 'admin', :object_type => 'User', :object_id => user.id) do |platform|
platform.relations.exists?(:role => 'admin', :object_type => 'User', :object_id => user.id)
end
#can :read, Platform, :members => {:id => user.id}
can :read, Platform, platforms_in_relations_with(:role => 'reader', :object_type => 'User', :object_id => user.id) do |platform|
platform.relations.exists?(:role => 'reader', :object_type => 'User', :object_id => user.id)
end
can [:manage, :add_project, :remove_project, :change_visibility, :settings], Repository, :owner_type => 'User', :owner_id => user.id
can :manage, Repository, repositories_in_relations_with(:role => 'admin', :object_type => 'User', :object_id => user.id) do |repository|
repository.relations.exists?(:role => 'admin', :object_type => 'User', :object_id => user.id)
end
#can :read, Repository, :members => {:id => user.id}
can :read, Repository, repositories_in_relations_with(:role => 'reader', :object_type => 'User', :object_id => user.id) do |repository|
repository.relations.exists?(:role => 'reader', :object_type => 'User', :object_id => user.id)
end
# Small CanCan hack by Repository.new(:platform_id => ...)
can [:new, :create], Repository do |repository|
repository.platform.relations.exists?(:role => 'admin', :object_type => 'User', :object_id => user.id)
end
#can :read, Repository
# TODO: Add personal repos rules
# Same rights for groups:
can [:read, :create], PrivateUser, :platform => {:owner_type => 'Group', :owner_id => user.group_ids}
can :publish, BuildList do |build_list|
build_list.can_published? && build_list.project.relations.exists?(:object_type => 'Group', :object_id => user.group_ids)
end
can [:index, :read], BuildList, build_lists_in_relations_with(:object_type => 'Group', :object_id => user.group_ids) do |build_list|
build_list.project.relations.exists?(:object_type => 'Group', :object_id => user.group_ids)
end
can :manage_collaborators, Project, projects_in_relations_with(:role => 'admin', :object_type => 'Group', :object_id => user.group_ids) do |project|
project.relations.exists? :object_id => user.group_ids, :object_type => 'Group', :role => 'admin'
end
# can :read, Issue, :status => 'open'
can :read, Issue, :project => {:visibility => 'open'}
can :read, Issue, :project => {:owner_type => 'User', :owner_id => user.id}
can :read, Issue, :project => {:owner_type => 'Group', :owner_id => user.group_ids}
can(:read, Issue, read_relations_for('issues', 'projects')) {|issue| can? :read, issue.project rescue nil}
can(:create, Issue) {|issue| can? :write, issue.project}
can([:update, :destroy], Issue) {|issue| issue.user_id == user.id or local_admin?(issue.project)}
cannot :manage, Issue, :project => {:has_issues => false} # switch off issues
can [:read, :update, :process_build, :build, :destroy], Project, :owner_type => 'Group', :owner_id => user.group_ids
#can :read, Project, :relations => {:role => 'reader', :object_type => 'Group', :object_id => user.group_ids}
can :read, Project, projects_in_relations_with(:role => 'reader', :object_type => 'Group', :object_id => user.group_ids) do |project|
project.relations.exists?(:role => 'reader', :object_type => 'Group', :object_id => user.group_ids)
end
#can [:update, :process_build, :build], Project, :relations => {:role => 'writer', :object_type => 'Group', :object_id => user.group_ids}
can [:read, :update, :process_build, :build], Project, projects_in_relations_with(:role => ['writer', 'admin'], :object_type => 'Group', :object_id => user.group_ids) do |project|
project.relations.exists?(:role => ['writer', 'admin'], :object_type => 'Group', :object_id => user.group_ids)
end
can :manage, Platform, :owner_type => 'Group', :owner_id => user.group_ids
#can :read, Platform, :groups => {:id => user.group_ids}
can :read, Platform, platforms_in_relations_with(:role => 'reader', :object_type => 'Group', :object_id => user.group_ids) do |platform|
platform.relations.exists?(:role => 'reader', :object_type => 'Group', :object_id => user.group_ids)
end
can [:manage, :add_project, :remove_project], Repository, :owner_type => 'Group', :owner_id => user.group_ids
#can :read, Platform, :groups => {:id => user.group_ids}
can :read, Repository, repositories_in_relations_with(:role => 'reader', :object_type => 'Group', :object_id => user.group_ids) do |repository|
repository.relations.exists?(:role => 'reader', :object_type => 'Group', :object_id => user.group_ids)
end
can(:fork, Project) {|p| can? :read, p}
# Things that can not do simple user
cannot :create, [Platform, User]
can(:create, Comment) {|comment| can? :read, comment.commentable.project}
can(:update, Comment) {|comment| comment.user_id == user.id or local_admin?(comment.commentable.project)}
cannot :manage, Comment, :commentable => {:project => {:has_issues => false}} # switch off issues
end
end
@ -149,34 +99,44 @@ class Ability
cannot :destroy, Platform, :platform_type => 'personal'
cannot :destroy, Repository, :platform => {:platform_type => 'personal'}
cannot :fork, Project, :owner_id => user.id, :owner_type => user.class.to_s
end
cannot :destroy, Issue
# Sub query for platforms, projects relations
# TODO: Replace table names list by method_missing way
%w[platforms projects products repositories groups].each do |table_name|
define_method table_name + "_in_relations_with" do |opts|
query = "#{ table_name }.id IN (SELECT target_id FROM relations WHERE relations.target_type = '#{ table_name.singularize.camelize }'"
opts.each do |key, value|
query = query + " AND relations.#{ key } #{ value.class == Array ? 'IN (?)' : '= ?' } "
can :create, Subscribe do |subscribe|
!subscribe.subscribeable.subscribes.exists?(:user_id => user.id)
end
query = query + ")"
return opts.values.unshift query
can :destroy, Subscribe do |subscribe|
subscribe.subscribeable.subscribes.exists?(:user_id => user.id) && user.id == subscribe.user_id
end
end
def build_lists_in_relations_with(opts)
query = "build_lists.project_id IN (SELECT target_id FROM relations WHERE relations.target_type = 'Project'"
opts.each do |key, value|
query = query + " AND relations.#{ key } #{ value.class == Array ? 'IN (?)' : '= ?' } "
end
query = query + ")"
return opts.values.unshift query
# TODO group_ids ??
def read_relations_for(table, parent = nil)
key = parent ? "#{parent.singularize}_id" : 'id'
parent ||= table
["#{table}.#{key} IN (
SELECT target_id FROM relations WHERE relations.target_type = ? AND
(relations.object_type = 'User' AND relations.object_id = ? OR
relations.object_type = 'Group' AND relations.object_id IN (?)))", parent.classify, @user, @user.group_ids]
end
## Sub query for project relations
#def projects_in_relations_with(opts={})
# ["projects.id IN (SELECT target_id FROM relations WHERE relations.object_id #{ opts[:object_id].class == Array ? 'IN (?)' : '= ?' } AND relations.object_type = '#{ opts[:object_type] }' AND relations.target_type = 'Platform') AND relations.role", opts[:object_id]]
#end
def relation_exists_for(object, roles)
object.relations.exists?(:object_id => @user.id, :object_type => 'User', :role => roles) or
object.relations.exists?(:object_id => @user.group_ids, :object_type => 'Group', :role => roles)
end
def local_reader?(object)
relation_exists_for(object, %w{reader writer admin}) or owner?(object)
end
def local_writer?(object)
relation_exists_for(object, %w{writer admin}) or owner?(object)
end
def local_admin?(object)
relation_exists_for(object, 'admin') or owner?(object)
end
def owner?(object)
object.owner == @user or @user.own_groups.include?(object.owner)
end
end

View File

@ -6,28 +6,27 @@ class BuildList < ActiveRecord::Base
belongs_to :user
has_many :items, :class_name => "BuildList::Item", :dependent => :destroy
validates :project_id, :presence => true
validates :project_version, :presence => true
#validates_inclusion_of :update_type, :in => UPDATE_TYPES#, :message => "extension %s is not included in the list"
validates :project_id, :project_version, :arch, :include_repos, :presence => true
UPDATE_TYPES = %w[security bugfix enhancement recommended newpackage]
validates :update_type, :inclusion => UPDATE_TYPES
validate lambda {
errors.add(:bpl, I18n.t('flash.build_list.wrong_platform')) if pl.platform_type == 'main' && pl_id != bpl_id
}
validate lambda {
errors.add(:bpl, I18n.t('flash.build_list.can_not_published')) if status == BUILD_PUBLISHED && status_was != BuildServer::SUCCESS
}
# The kernel does not send these statuses directly
BUILD_CANCELED = 5000
WAITING_FOR_RESPONSE = 4000
BUILD_PENDING = 2000
BUILD_PUBLISHED = 6000
BUILD_PUBLISH = 7000
FAILED_PUBLISH = 8000
STATUSES = [ WAITING_FOR_RESPONSE,
BUILD_CANCELED,
BUILD_PENDING,
BUILD_PUBLISHED,
BUILD_PUBLISH,
FAILED_PUBLISH,
BuildServer::SUCCESS,
BuildServer::BUILD_STARTED,
BuildServer::BUILD_ERROR,
@ -42,6 +41,8 @@ class BuildList < ActiveRecord::Base
BUILD_CANCELED => :build_canceled,
BUILD_PENDING => :build_pending,
BUILD_PUBLISHED => :build_published,
BUILD_PUBLISH => :build_publish,
FAILED_PUBLISH => :failed_publish,
BuildServer::BUILD_ERROR => :build_error,
BuildServer::BUILD_STARTED => :build_started,
BuildServer::SUCCESS => :success,
@ -83,6 +84,7 @@ class BuildList < ActiveRecord::Base
scope :scoped_to_project_name, lambda {|project_name| joins(:project).where('projects.name LIKE ?', "%#{project_name}%")}
serialize :additional_repos
serialize :include_repos
before_create :set_default_status
after_create :place_build
@ -106,27 +108,26 @@ class BuildList < ActiveRecord::Base
end
def publish
return false unless can_published?
BuildServer.publish_container bs_id
self.update_attribute(:status, BUILD_PUBLISHED)
#self.destroy # self.delete
return false unless can_publish?
has_published = BuildServer.publish_container bs_id
update_attribute(:status, has_published == 0 ? BUILD_PUBLISH : FAILED_PUBLISH)
return has_published == 0
end
def can_published?
self.status == BuildServer::SUCCESS
def can_publish?
status == BuildServer::SUCCESS or status == FAILED_PUBLISH
end
def cancel_build_list
def cancel
return false unless can_cancel?
has_canceled = BuildServer.delete_build_list bs_id
update_attribute(:status, BUILD_CANCELED) if has_canceled == 0
return has_canceled == 0
end
#TODO: Share this checking on product owner.
def can_cancel?
self.status == BUILD_PENDING && bs_id
status == BUILD_PENDING && bs_id
end
def event_log_message
@ -140,8 +141,8 @@ class BuildList < ActiveRecord::Base
end
def place_build
#XML-RPC params: project_name, project_version, plname, arch, bplname, update_type, build_requires, id_web
self.status = BuildServer.add_build_list project.name, project_version, pl.name, arch.name, (pl_id == bpl_id ? '' : bpl.name), update_type, build_requires, id
#XML-RPC params: project_name, project_version, plname, arch, bplname, update_type, build_requires, id_web, include_repos
self.status = BuildServer.add_build_list project.name, project_version, pl.name, arch.name, (pl_id == bpl_id ? '' : bpl.name), update_type, build_requires, id, include_repos
self.status = BUILD_PENDING if self.status == 0
save
end

20
app/models/comment.rb Normal file
View File

@ -0,0 +1,20 @@
class Comment < ActiveRecord::Base
belongs_to :commentable, :polymorphic => true
belongs_to :user
validates :body, :user_id, :commentable_id, :commentable_type, :presence => true
after_create :deliver_new_comment_notification
protected
def deliver_new_comment_notification
recipients = self.commentable.project.relations.by_role('admin').where(:object_type => 'User').map { |rel| rel.read_attribute(:object_id) }
recipients = recipients | [self.commentable.user_id]
recipients = recipients | [self.commentable.project.owner_id] if self.commentable.project.owner_type == 'User'
recipients.each do |recipient_id|
recipient = User.find(recipient_id)
UserMailer.delay.new_comment_notification(self, recipient)
end
end
end

62
app/models/issue.rb Normal file
View File

@ -0,0 +1,62 @@
class Issue < ActiveRecord::Base
STATUSES = ['open', 'closed']
belongs_to :project
belongs_to :user
has_many :comments, :as => :commentable
has_many :subscribes, :as => :subscribeable
validates :title, :body, :project_id, :presence => true
#attr_readonly :serial_id
after_create :set_serial_id
after_create :subscribe_users
after_create :deliver_new_issue_notification
after_create :deliver_issue_assign_notification
after_update :deliver_issue_assign_notification
def assign_uname
user.uname if user
end
def to_param
serial_id.to_s
end
protected
def set_serial_id
self.serial_id = self.project.issues.count
self.save!
end
def deliver_new_issue_notification
recipients = collect_recipient_ids
recipients.each do |recipient_id|
recipient = User.find(recipient_id)
UserMailer.delay.new_issue_notification(self, recipient)#.deliver
end
end
def deliver_issue_assign_notification
UserMailer.delay.issue_assign_notification(self, self.user) if self.user_id_was != self.user_id
end
def subscribe_users
recipients = collect_recipient_ids
recipients.each do |recipient_id|
ss = self.subscribes.build(:user_id => recipient_id)
ss.save!
end
end
def collect_recipient_ids
recipients = self.project.relations.by_role('admin').where(:object_type => 'User').map { |rel| rel.read_attribute(:object_id) }
recipients = recipients | [self.user_id] if self.user_id
recipients = recipients | [self.project.owner_id] if self.project.owner_type == 'User'
recipients
end
end

View File

@ -14,7 +14,7 @@ class Platform < ActiveRecord::Base
has_many :groups, :through => :objects, :source => :object, :source_type => 'Group'
validates :description, :presence => true, :uniqueness => true
validates :name, :uniqueness => true, :presence => true, :format => { :with => /^[a-zA-Z0-9_]+$/ }
validates :name, :uniqueness => true, :presence => true, :format => { :with => /^[a-zA-Z0-9_\-]+$/ }
validates :distrib_type, :presence => true, :inclusion => {:in => APP_CONFIG['distr_types']}
before_create :xml_rpc_create, :unless => lambda {Thread.current[:skip]}
@ -43,14 +43,17 @@ class Platform < ActiveRecord::Base
urpmi_commands[pl.name] = []
local_pair = pl.id != self.id ? blank_pair : pair
head = hidden? ? "http://#{local_pair[:login]}@#{local_pair[:pass]}:#{host}/private/" : "http://#{host}/downloads/"
if pl.distrib_type == APP_CONFIG['distr_types'].first
# prefix = prefix_url hidden?, :host => host, :login => local_pair[:login], :password => local_pair[:pass]
if pl.distrib_type == APP_CONFIG['distr_types'].first # mdv
Arch.all.each do |arch|
tail = "/#{arch.name}/main/release"
urpmi_commands[pl.name] << "urpmi.addmedia #{name} #{head}#{name}/repository/#{pl.name}#{tail}"
# urpmi_commands[pl.name] << "urpmi.addmedia #{name} #{prefix}/#{name}/repository#{pl.downloads_url '', arch.name, 'main', 'release'}"
end
else
tail = ''
urpmi_commands[pl.name] << "urpmi.addmedia #{name} #{head}#{name}/repository/#{pl.name}#{tail}"
# urpmi_commands[pl.name] << "urpmi.addmedia #{name} #{prefix}/#{name}/repository#{pl.downloads_url ''}"
end
end
@ -65,6 +68,27 @@ class Platform < ActiveRecord::Base
Rails.root.join("public", "downloads", name)
end
def prefix_url(pub, options = {})
options[:host] ||= EventLog.current_controller.request.host_with_port rescue ::Rosa::Application.config.action_mailer.default_url_options[:host]
pub ? "http://#{options[:host]}/downloads" : "http://#{options[:login]}:#{options[:password]}@#{options[:host]}/private"
end
def public_downloads_url(host = nil, arch = nil, repo = nil, suffix = nil)
downloads_url prefix_url(true, :host => host), arch, repo, suffix
end
def private_downloads_url(login, password, host = nil, arch = nil, repo = nil, suffix = nil)
downloads_url prefix_url(false, :host => host, :login => login, :password => password), arch, repo, suffix
end
def downloads_url(prefix, arch = nil, repo = nil, suffix = nil)
"#{prefix}/#{name}/repository/".tap do |url|
url << "#{arch}/" if arch.present?
url << "#{repo}/" if repo.present?
url << "#{suffix}/" if suffix.present?
end
end
def hidden?
visibility == 'hidden'
end
@ -115,9 +139,7 @@ class Platform < ActiveRecord::Base
system("sudo mkdir -p #{mount_path}")
system("sudo mount --bind #{path} #{mount_path}")
Arch.all.each do |arch|
host = EventLog.current_controller.request.host_with_port rescue ::Rosa::Application.config.action_mailer.default_url_options[:host]
url = "http://#{host}/downloads/#{name}/repository/"
str = "country=Russian Federation,city=Moscow,latitude=52.18,longitude=48.88,bw=1GB,version=2011,arch=#{arch.name},type=distrib,url=#{url}\n"
str = "country=Russian Federation,city=Moscow,latitude=52.18,longitude=48.88,bw=1GB,version=2011,arch=#{arch.name},type=distrib,url=#{public_downloads_url}\n"
File.open(File.join(mount_path, "#{name}.#{arch.name}.list"), 'w') {|f| f.write(str) }
end
end

View File

@ -3,11 +3,9 @@ class Product < ActiveRecord::Base
belongs_to :platform
has_many :product_build_lists, :dependent => :destroy
has_many :relations, :as => :target, :dependent => :destroy
after_validation :merge_tar_errors
before_save :destroy_tar?
after_create :add_admin_relations
has_attached_file :tar
@ -77,13 +75,4 @@ class Product < ActiveRecord::Base
errors[:tar] += errors[:tar_content_type]
errors[:tar_content_type] = []
end
def add_admin_relations
platform.relations.where(:role => 'admin').each do |rel|
r = relations.build(:role => 'admin', :object_type => rel.object_type)
r.object_id = rel.object_id
r.save
end
end
end

View File

@ -4,6 +4,7 @@ class Project < ActiveRecord::Base
belongs_to :category, :counter_cache => true
belongs_to :owner, :polymorphic => true
has_many :issues, :dependent => :destroy
has_many :build_lists, :dependent => :destroy
has_many :auto_build_lists, :dependent => :destroy
@ -136,6 +137,10 @@ class Project < ActiveRecord::Base
end
end
def platforms
@platforms ||= repositories.map(&:platform).uniq
end
protected
def build_path(dir)

View File

@ -7,4 +7,14 @@ class ProjectToRepository < ActiveRecord::Base
after_create lambda { project.xml_rpc_create(repository) }, :unless => lambda {Thread.current[:skip]}
after_destroy lambda { project.xml_rpc_destroy(repository) }
# after_rollback lambda { project.xml_rpc_destroy(repository) rescue true if new_record? }
validate :one_project_in_platform_repositories
protected
def one_project_in_platform_repositories
c = Platform.scoped.select('projects.*').joins(:repositories => :projects).where(
:projects => {:name => project.name}, :id => repository.platform_id).count
errors.add(:project, 'should be one in platform') if c > 0
end
end

View File

@ -10,6 +10,7 @@ class Relation < ActiveRecord::Base
scope :by_object, lambda {|obj| {:conditions => ['object_id = ? AND object_type = ?', obj.id, obj.class.to_s]}}
scope :by_target, lambda {|tar| {:conditions => ['target_id = ? AND target_type = ?', tar.id, tar.class.to_s]}}
scope :by_role, lambda {|role| {:conditions => ['role = ?', role]}}
def self.create_with_role(object, target, role)
r = new

View File

@ -11,7 +11,7 @@ class Repository < ActiveRecord::Base
has_many :groups, :through => :objects, :source => :object, :source_type => 'Group'
validates :description, :uniqueness => {:scope => :platform_id}, :presence => true
validates :name, :uniqueness => {:scope => :platform_id}, :presence => true, :format => { :with => /^[a-z0-9_]+$/ }
validates :name, :uniqueness => {:scope => :platform_id}, :presence => true, :format => { :with => /^[a-z0-9_\-]+$/ }
# validates :platform_id, :presence => true # if you uncomment this platform clone will not work
scope :recent, order("name ASC")

4
app/models/subscribe.rb Normal file
View File

@ -0,0 +1,4 @@
class Subscribe < ActiveRecord::Base
belongs_to :subscribeable, :polymorphic => true
belongs_to :user
end

View File

@ -5,6 +5,7 @@ class User < ActiveRecord::Base
:recoverable, :rememberable, :validatable #, :trackable, :confirmable, :lockable
has_many :authentications, :dependent => :destroy
has_many :build_lists, :dependent => :destroy
has_many :relations, :as => :object, :dependent => :destroy
has_many :targets, :as => :object, :class_name => 'Relation'

View File

@ -6,22 +6,20 @@
%th= t("activerecord.attributes.build_list.project")
%th= t("activerecord.attributes.build_list.arch")
%th= t("activerecord.attributes.build_list.user")
%th= t("activerecord.attributes.build_list.is_circle")
- if controller.action_name = 'all'
- unless @project
%th= t("layout.build_lists.cancel_button_header")
%th.last= t("activerecord.attributes.build_list.notified_at")
- build_lists.each do |build_list|
%tr{:class => cycle("odd", "even")}
%td= link_to (build_list.bs_id.present? ? build_list.bs_id : t("layout.build_lists.bs_id_not_set")), project_build_list_path(build_list.project, build_list)
%td= link_to (build_list.bs_id.present? ? build_list.bs_id : t("layout.build_lists.bs_id_not_set")), build_list
%td= build_list.human_status
%td= link_to build_list.project_version, "#"
%td= link_to build_list.project.name, project_path(build_list.project)
%td= build_list.arch.name
%td= build_list.user.fullname
%td= t("layout.#{build_list.is_circle?}_")
- if controller.action_name = 'all'
%td= link_to t("layout.build_lists.cancel_button"), build_list_cancel_path(build_list) if build_list.can_cancel?
%td= build_list.user.try(:fullname)
- unless @project
%td= link_to t("layout.build_lists.cancel_button"), cancel_build_list_path(build_list), :method => "put", :confirm => t("layout.confirm") if build_list.can_cancel?
%td.last= build_list.notified_at
= will_paginate build_lists

View File

@ -8,12 +8,12 @@
= f.select :status, BuildList::STATUSES.collect{|status| [BuildList.human_status(status), status]}, :include_blank => true, :selected => @filter.status
.group
= f.label :arch_id, t("activerecord.attributes.build_list.arch"), :class => :label
= f.select :arch_id, @arches.collect{|arch| [arch.name, arch.id]}, :include_blank => true, :selected => @filter.arch_id
= f.select :arch_id, Arch.recent.collect{|arch| [arch.name, arch.id]}, :include_blank => true, :selected => @filter.arch_id
.column.right
- if @project_versions
- if @project
.group
= f.label :project_version, t("activerecord.attributes.build_list.project_version"), :class => :label
= f.select :project_version, @project_versions, :include_blank => true, :selected => @filter.project_version
= f.select :project_version, @project.versions, :include_blank => true, :selected => @filter.project_version
.group
= f.label :is_circle, t("activerecord.attributes.build_list.is_circle"), :class => :label
= f.select :is_circle, [[t("layout.yes_"), 1], [t("layout.no_"), 0]], :include_blank => true, :selected => @filter.is_circle.present? ? (@filter.is_circle ? "1" : "0") : nil

View File

@ -0,0 +1,3 @@
- platform.repositories.each do |repo|
= check_box_tag "build_list[include_repos][]", repo.id, repo.name == 'main' || @project.repositories.map(&:id).include?(repo.id), :id => "include_repos_#{repo.id}" # (params[:build_list]||[]).fetch(:include_repos, []).include?(repo.id.to_s)
= label_tag "include_repos_#{repo.id}", repo.name

View File

@ -1,10 +1,10 @@
.block.notice
%h3= t("layout.repositories.current_repository_header")
.content
- @project.repositories.each do |repository|
- project.repositories.each do |repository|
%p= link_to "#{repository.name} (#{repository.platform.name})", platform_repository_path(repository.platform, repository)
.block.notice
%h3= t("layout.projects.current_project_header")
.content
%p= link_to @project.name, project_path(@project)
%p= link_to project.name, project

View File

@ -1,12 +1,12 @@
.block
- if controller.action_name != 'all'
- if @project
.secondary-navigation
%ul.wat-cf
%li.first= link_to t("layout.build_lists.current"), project_path(@project) + "#build_lists"
%li.active= link_to t("layout.build_lists.all"), project_build_lists_path(@project)
.content
- if controller.action_name == 'all'
- unless @project
.inner
%h2= t('layout.build_lists.build_server_status.header')
.field
@ -25,4 +25,4 @@
.inner
= render :partial => "build_lists/build_lists", :object => @build_lists
- content_for :sidebar, render(:partial => 'sidebar') if controller.action_name != 'all'
- content_for :sidebar, render('sidebar', :project => @project) if @project

View File

@ -0,0 +1,66 @@
.block
.secondary-navigation
%ul.wat-cf
%li.first=# link_to t("layout.projects.list"), platform_repository_path(@platform, @repository) + "#projects"
%li= link_to t("layout.projects.new"), new_project_path
%li= link_to t("layout.projects.show"), project_path(@project)
%li=# link_to "git-repo", project_repo_path(@platform, @repository, @project)
%li.active= link_to t("layout.projects.build"), new_project_build_list_path(@project)
.content
%h2.title= t("layout.projects.new_build", :project_name => @project.name)
.inner
= form_for [@project, @build_list], :html => { :class => :form, :method => :post } do |f|
.columns.wat-cf
.column.left
.group
= f.label :project_version, t("activerecord.attributes.build_list.project_version"), :class => :label
= f.select :project_version, @project.versions
.group.base_platforms
= f.label :bpl, t("activerecord.attributes.build_list.bpl"), :class => :label
- Platform.main.each do |bpl|
= check_box_tag "bpls[]", bpl.id, (params[:bpls]||[]).include?(bpl.id.to_s), :class => 'build_bpl_ids', :id => "bpls_#{bpl.id}"
= label_tag "bpls_#{bpl.id}", bpl.name
%br
.group
= f.label :update_type, t("activerecord.attributes.build_list.update_type"), :class => :label
= f.select :update_type, BuildList::UPDATE_TYPES
.group
= f.check_box :build_requires
= f.label :build_requires, t("activerecord.attributes.build_list.build_requires")
.column.right
.group
= f.label :arches, t("activerecord.attributes.build_list.arch"), :class => :label
- Arch.recent.each do |arch|
= check_box_tag "arches[]", arch.id, (params[:arches]||[]).include?(arch.id.to_s), :id => "arches_#{arch.id}"
= label_tag "arches_#{arch.id}", arch.name
%br
.group
= f.label :pl_id, t("activerecord.attributes.build_list.pl"), :class => :label
= f.select :pl_id, @project.repositories.collect{|r| ["#{r.platform.name}/#{r.name}", r.platform.id]}
.group
= f.label :include_repos, t("activerecord.attributes.build_list.include_repos"), :class => :label
#include_repos
.group
= f.check_box :auto_publish
= f.label :auto_publish, t("activerecord.attributes.build_list.auto_publish")
.group.navform.wat-cf
%button.button{:type => "submit"}
= image_tag("web-app-theme/icons/tick.png", :alt => t("layout.projects.build_button"))
= t("layout.projects.build_button")
%span.text_button_padding= t("layout.or")
= link_to t("layout.cancel"), root_path, :class => "text_button_padding link_button"
.preloaded_include_repos{:style => 'display: none'}
- @project.platforms.each do |p|
%div{:class => "include_repos_#{p.id}"}= render 'include_repos', :platform => p

View File

@ -1,9 +1,9 @@
.block
.secondary-navigation
%ul.wat-cf
%li.first= link_to t("layout.build_lists.current"), project_path(@project) + "#build_lists"
%li= link_to t("layout.build_lists.all"), project_build_lists_path(@project)
%li.active= link_to t("layout.build_lists.show"), project_build_list_path(@project, @build_list)
%li.first= link_to t("layout.build_lists.current"), project_path(@build_list.project) + "#build_lists"
%li= link_to t("layout.build_lists.all"), project_build_lists_path(@build_list.project)
%li.active= link_to t("layout.build_lists.show"), @build_list
.content
.inner
@ -34,6 +34,11 @@
= t("activerecord.attributes.build_list.pl")
\:
= @build_list.pl.name
%p
%b
= t("activerecord.attributes.build_list.include_repos")
\:
= (@build_list.include_repos||[]).map{|r| Repository.find(r).name}.join(', ')
%p
%b
= t("activerecord.attributes.build_list.update_type")
@ -44,6 +49,11 @@
= t("activerecord.attributes.build_list.build_requires")
\:
= @build_list.build_requires
%p
%b
= t("activerecord.attributes.build_list.auto_publish")
\:
= @build_list.auto_publish
%p
%b
= t("activerecord.attributes.build_list.status")
@ -68,7 +78,7 @@
%b
= t("activerecord.attributes.build_list.user")
\:
= @build_list.user.fullname
= @build_list.user.try(:fullname)
%p
%b
= t("activerecord.attributes.build_list.notified_at")
@ -80,9 +90,9 @@
\:
= t("layout.#{@build_list.is_circle?}_")
- if @build_list.can_published?
- if @build_list.can_publish?
.wat-cf
= link_to image_tag("web-app-theme/icons/tick.png", :alt => t("layout.publish")) + " " + t("layout.publish"), publish_project_build_list_path(@project, @build_list), :method => "post", :class => "button", :confirm => t("layout.build_lists.confirm_publish")
= link_to image_tag("web-app-theme/icons/tick.png", :alt => t("layout.publish")) + " " + t("layout.publish"), publish_build_list_path(@build_list), :method => "put", :class => "button", :confirm => t("layout.confirm")
.block
.content
@ -106,4 +116,4 @@
%td= item.version
%td.last= item.human_status
- content_for :sidebar, render(:partial => 'sidebar')
- content_for :sidebar, render('sidebar', :project => @build_list.project)

View File

@ -0,0 +1,10 @@
.group
= f.label :body, t("activerecord.attributes.comment.body"), :class => :label
= f.text_area :body, :class => 'text_field', :cols => 80
.group.navform.wat-cf
%button.button{:type => "submit"}
= image_tag("web-app-theme/icons/tick.png", :alt => t("layout.save"))
= t("layout.save")
%span.text_button_padding= t("layout.or")
= link_to t("layout.cancel"), [@issue.project, @issue], :class => "text_button_padding link_button"

View File

@ -0,0 +1,10 @@
.block
.secondary-navigation
%ul.wat-cf
%li.first= link_to t("layout.issues.list"), project_issue_path(@project, @issue)
.content
%h2.title
= t("layout.issues.edit_header")
.inner
= form_for @comment, :url => project_issue_comment_path(@project, @issue, @comment), :html => { :class => :form } do |f|
= render :partial => "form", :locals => {:f => f}

View File

@ -4,7 +4,7 @@
%li= link_to t("layout.projects.new"), new_project_path
%li= link_to t("layout.projects.show"), project_path(@project)
%li.active= link_to t("layout.git.repositories.source"), project_repo_path(@project)
%li= link_to t("layout.projects.build"), build_project_path(@project)
%li= link_to t("layout.projects.build"), new_project_build_list_path(@project)
%ul#git_submenu.sub-wat-cf.wat-cf
%li= link_to t("layout.git.repositories.commits"), commits_path(@project, :treeish => @treeish)

View File

@ -0,0 +1,26 @@
= javascript_include_tag "autocomplete-rails.js"
.group
= f.label :title, :class => :label
= f.text_field :title, :class => 'text_field'
.group
= f.label :body, :class => :label
= f.text_area :body, :class => 'text_field', :cols => 80
- unless @issue.new_record?
.group
= f.label :status, :class => :label
= f.select :status, Issue::STATUSES, :class => 'text_field'
.group
= label_tag "", t("activerecord.attributes.issue.user_id"), :class => :label
= autocomplete_field_tag 'user_id', @user_uname, autocomplete_user_uname_platforms_path, :id_element => '#user_id_field'
= hidden_field_tag 'user_id', @user_id, :id => 'user_id_field'
.group.navform.wat-cf
%button.button{:type => "submit"}
= image_tag("web-app-theme/icons/tick.png", :alt => t("layout.save"))
= t("layout.save")
%span.text_button_padding= t("layout.or")
= link_to t("layout.cancel"), project_path(@project), :class => "text_button_padding link_button"

View File

@ -0,0 +1,18 @@
%table.table
%tr
%th.first= t("activerecord.attributes.issue.title")
%th.first= t("activerecord.attributes.issue.user")
%th.first= t("activerecord.attributes.issue.status")
%th.last &nbsp;
- @issues.each do |issue|
%tr{:class => cycle("odd", "even")}
%td
= link_to issue.title, [@project, issue]
%td
= link_to issue.user.uname, user_path(issue.user) if issue.user
%td
= issue.status
%td.last
= link_to t("layout.show"), [@project, issue]
|
= link_to t("layout.delete"), project_issue_path(@project, issue), :method => :delete, :confirm => t("layout.issues.confirm_delete") if can? :destroy, issue

View File

@ -0,0 +1,11 @@
.block
.secondary-navigation
%ul.wat-cf
%li.first= link_to t("layout.issues.list"), project_issues_path(@project)
%li= link_to t("layout.issues.new"), new_project_issue_path(@project)
.content
%h2.title
= t("layout.issues.edit_header")
.inner
= form_for @issue, :url => project_issue_path(@project, @issue), :html => { :class => :form } do |f|
= render :partial => "form", :locals => {:f => f}

View File

@ -0,0 +1,22 @@
.block
.secondary-navigation
%ul.wat-cf
%li.first.active= link_to t("layout.issues.list"), project_issues_path(@project)
%li= link_to t("layout.issues.new"), new_project_issue_path(@project) if can? :new, Issue.new(:project_id => @project.id)
.secondary-navigation
%ul.wat-cf
%li{:class => "first " + (params[:status].blank? ? "active" : "")}
= link_to t("layout.issues.statuses.any"), project_issues_path(@project)
%li{:class => "first " + (params[:status] == 'open' ? "active" : "")}
= link_to t("layout.issues.statuses.open"), project_issues_path(@project, :status => 'open')
%li{:class => "first " + (params[:status] == 'closed' ? "active" : "")}
= link_to t("layout.issues.statuses.closed"), project_issues_path(@project, :status => 'closed')
.content
%h2.title
= t("layout.issues.list_header")
.inner
= render :partial => 'shared/search_form'
= render :partial => 'issues/list'
.actions-bar.wat-cf
.actions
= will_paginate @issues, :param_name => :issue_page

View File

@ -0,0 +1,11 @@
.block
.secondary-navigation
%ul.wat-cf
%li.first= link_to "#{t("layout.issues.list")}", project_issues_path(@project)
%li.active= link_to "#{t("layout.issues.new")}", new_project_issue_path(@project)
.content
%h2.title
= t("layout.issues.new_header")
.inner
= form_for :issue, :url => project_issues_path(@project), :html => { :class => :form } do |f|
= render :partial => "form", :locals => {:f => f}

View File

@ -0,0 +1,56 @@
.block
.secondary-navigation
%ul.wat-cf
%li.first= link_to t("layout.issues.list"), project_issues_path(@project)
%li= link_to t("layout.issues.edit"), edit_project_issue_path(@project, @issue) if can? :edit, @issue
.content
.inner
%p
%b
= t("activerecord.attributes.issue.title")
\:
= @issue.title
%p
%b
= t("activerecord.attributes.issue.body")
\:
= @issue.body
%p
%b
= t('activerecord.attributes.issue.status')
\:
= @issue.status
%p
%b
= t('layout.issues.subscribe')
\:
- if @issue.subscribes.exists? :user_id => current_user.id
= link_to t('layout.issues.unsubscribe_btn'), project_issue_subscribe_path(@project, @issue, current_user.id), :method => :delete
- else
= link_to t('layout.issues.subscribe_btn'), project_issue_subscribes_path(@project, @issue), :method => :post
%a{ :name => "comments" }
.block#block-list
.content
%h2.title
= t("layout.issues.comments_header")
.inner
%ul.list
- @issue.comments.each do |comment|
%li
.left
= link_to comment.user.uname, user_path(comment.user.uname)
.item
= comment.body
%br
%br
= link_to t("layout.edit"), edit_project_issue_comment_path(@project, @issue.id, comment) if can? :update, comment
= link_to image_tag("web-app-theme/icons/cross.png", :alt => t("layout.delete")) + " " + t("layout.delete"), project_issue_comment_path(@project, @issue.id, comment), :method => "delete", :class => "button", :confirm => t("layout.comments.confirm_delete") if can? :delete, comment
.block
.content
%h2.title
= t("layout.comments.new_header")
.inner
= form_for :comment, :url => project_issue_comments_path(@project, @issue), :method => :post, :html => { :class => :form } do |f|
= render :partial => "comments/form", :locals => {:f => f}

View File

@ -1,6 +1,6 @@
-#= include_stylesheets :application
= stylesheet_link_tag "web-app-theme/base.css", "web-app-theme/themes/default/style.css", "web-app-theme/override.css", "git/style.css"
= stylesheet_link_tag "jquery-ui-1.8.16.custom.css"
= stylesheet_link_tag "jquery-ui-1.8.16.custom.css", "datatable.css", "patches.css"
= yield :stylesheets

View File

@ -10,6 +10,9 @@
.group
= f.label :description, t("activerecord.attributes.project.description"), :class => :label
= f.text_area :description, :class => 'text_field', :cols => 80
.group
= f.label :has_issues, t("activerecord.attributes.project.has_issues"), :class => :label
= f.check_box :has_issues
.group.navform.wat-cf
%button.button{:type => "submit"}

View File

@ -1,56 +0,0 @@
.block
.secondary-navigation
%ul.wat-cf
%li.first=# link_to t("layout.projects.list"), platform_repository_path(@platform, @repository) + "#projects"
%li= link_to t("layout.projects.new"), new_project_path
%li= link_to t("layout.projects.show"), project_path(@project)
%li=# link_to "git-repo", project_repo_path(@platform, @repository, @project)
%li.active= link_to t("layout.projects.build"), build_project_path(@project)
.content
%h2.title= t("layout.projects.new_build", :project_name => @project.name)
.inner
= form_for :build, :url => process_build_project_path(@project), :html => { :class => :form, :method => :post } do |f|
.columns.wat-cf
.column.left
.group
= f.label :project_version, t("activerecord.attributes.build_list.project_version"), :class => :label
= f.select :project_version, @project_versions
.group.pl_ids_container
= f.label :bpl, t("activerecord.attributes.build_list.bpl"), :class => :label
- @bpls.each do |bpl|
= f.check_box "bpl[#{bpl.id}]", :bpl_id => bpl.id, :class => 'build_bpl_ids'
= bpl.name
%br
.group
= f.label :update_type, t("activerecord.attributes.build_list.update_type"), :class => :label
= f.select :update_type, BuildList::UPDATE_TYPES.collect { |ut| [ut, ut] }
.group
= f.check_box :build_requires
= t("activerecord.attributes.build_list.build_requires")
.column.right
.group
= f.label :arches, t("activerecord.attributes.build_list.arch"), :class => :label
- @arches.each do |arch|
= f.check_box "arches[#{arch.id}]"
= arch.name
%br
.group
= f.label :pl, t("activerecord.attributes.build_list.pl"), :class => :label
= f.select :pl, @pls
.group.navform.wat-cf
%button.button{:type => "submit"}
= image_tag("web-app-theme/icons/tick.png", :alt => t("layout.projects.build_button"))
= t("layout.projects.build_button")
%span.text_button_padding= t("layout.or")
= link_to t("layout.cancel"), root_path, :class => "text_button_padding link_button"
-# content_for :sidebar, render(:partial => 'sidebar')

View File

@ -5,7 +5,8 @@
%li= link_to t("layout.projects.new"), new_project_path
%li.active= link_to t("layout.projects.show"), project_path(@project)
%li= link_to t("layout.git.repositories.source"), project_repo_path(@project)
%li= link_to t("layout.projects.build"), build_project_path(@project)
%li= link_to t("layout.projects.build"), new_project_build_list_path(@project)
%li= link_to t("layout.projects.issues"), project_issues_path(@project)
.content
.inner

View File

@ -0,0 +1,15 @@
{
"sEcho": <%=h params[:sEcho].to_i || -1 %>,
"iTotalRecords": <%= @total_projects %>,
"iTotalDisplayRecords": <%= @total_project %>,
"aaData": [
<% @projects.each do |project| %>
[
"<%=j link_to project.owner.uname, project.owner %>",
"<%=j link_to project.name, project %>",
"<%=j link_to t("layout.add"), url_for(:controller => :repositories, :action => :add_project, :project_id => project.id) %>"
]<%= project == @projects.last ? '' : ',' %>
<% end %>
]
}

View File

@ -1,11 +1,21 @@
%table.table
%tr
%th.first= t("activerecord.attributes.project.name")
%th.last &nbsp;
- @projects.each do |project|
%tr{:class => cycle("odd", "even")}
%td
= link_to project.owner.name + '/' + project.name, project_path(project)
%td.last
#{link_to t("layout.show"), project_path(project)} | #{link_to t("layout.add"), url_for(:controller => :repositories, :action => :add_project, :project_id => project.id)}
- columns = [{:type => 'html', :searchable => false}, {:type => 'html'}, {:type => nil, :sortable => false, :searchable => false}]
= raw datatable(columns, {:sort_by => "[1, 'asc']", :search_label => t("layout.search_by_name"), :processing => t("layout.processing"),
:pagination_labels => {:first => t("datatables.first_label"), :previous => t("datatables.previous_label"),
:next => t("datatables.next_label"), :last => t("datatables.last_label")},
:empty_label => t("datatables.empty_label"),
:info_label => t("datatables.info_label"),
:info_empty_label => t("datatables.info_empty_label"),
:filtered_label => t("datatables.filtered_label"),
:ajax_source => "#{url_for :controller => :repositories, :action => :projects_list, :id => @repository.id}" })
%table.table.datatable
%thead
%tr
%th.first{:style => 'width: 80px'}= t("activerecord.attributes.user.uname")
%th= t("activerecord.attributes.project.name")
%th.last &nbsp;
%tbody
%br
= content_for :javascripts do
= javascript_include_tag 'jquery.dataTables.min.js'

View File

@ -39,9 +39,9 @@
%h2.title
= t("layout.projects.list_header")
.inner
= render :partial => 'shared/search_form'
-#= render :partial => 'shared/search_form'
= render :partial => 'proj_list', :object => @projects
.actions-bar.wat-cf
-#.actions-bar.wat-cf
.actions
= will_paginate @projects, :param_name => :project_page

View File

@ -0,0 +1,7 @@
%p== Здравствуйте, #{@user.name}.
%p Вам была назначена задача #{ link_to @issue.title, [@issue.project, @issue] }
%p== Команда поддержки «ROSA Build System»

View File

@ -0,0 +1,7 @@
%p== Здравствуйте, #{@user.name}.
%p К задаче #{ link_to @comment.commentable.title, [@comment.commentable.project, @comment.commentable] } был добавлен новый комментарий.
%p== Команда поддержки «ROSA Build System»

View File

@ -0,0 +1,7 @@
%p== Здравствуйте, #{@user.name}.
%p К проекту #{ link_to @issue.project.name, project_path(@issue.project) } была добавлена задача #{ link_to @issue.title, [@issue.project, @issue] }
%p== Команда поддержки «ROSA Build System»

View File

@ -23,8 +23,8 @@ ru:
open_id: 'вход через OpenID'
projects_controller:
auto_build: 'отправлен на автоматическую сборку'
process_build: 'отправлен на сборку'
build_lists_controller:
create: 'отправлен на сборку'
cancel: 'сборка отменена'
publish: 'сборка опубликована'
auto_build_lists_controller:

View File

@ -4,6 +4,16 @@ ru:
next_label: Следующая
page_gap: ...
datatables:
previous_label: Пред.
next_label: След.
first_label: « Первая
last_label: Последняя »
empty_label: Нет доступных данных
info_label: Показаны записи с _START_ по _END_ из _TOTAL_
info_empty_label: Показаны записи с 0 по 0 из 0
filtered_label: (отфильтровано из _MAX_)
layout:
logged_in_as: Вы вошли как
logout: Выйти
@ -30,6 +40,7 @@ ru:
not_access: Нет доступа!
owner: Владелец
confirm: Уверенны?
processing: Обрабатывается...
downloads:
title: Статистика закачек пакетов
@ -83,6 +94,7 @@ ru:
confirm_regenerate: Вы уверены, что хотите перегенерировать эту пару логин/пароль?
regenerate_btn: Перегенерировать
warning_message: Примечание - При получении новых данных старые становятся недействительными
categories:
list: Список
new: Создать
@ -93,6 +105,27 @@ ru:
edit_header: Редактировать категорию
confirm_delete: Вы уверены, что хотите удалить эту категорию?
issues:
list: Список
edit: Редактировать
comments_header: Комментарии
new: Новая задача
list_header: Список
confirm_delete: Вы уверены, что хотите удалить эту задачу?
edit_header: Редактирование задачи
new_header: Новая задача
statuses:
open: Открытые
closed: Закрытые
any: Все
subscribe: Подписка на уведомления
subscribe_btn: Подписаться
unsubscribe_btn: Отписаться
comments:
confirm_delete: Вы уверены, что хотите удалить комментарий?
new_header: Новый комментарий
platforms:
admin_id: Владелец
build_all: Собрать все
@ -214,6 +247,7 @@ ru:
collaborators: Коллабораторы
groups: Группы
edit_collaborators: Изменить список участников
issues: Задачи
collaborators:
back_to_proj: Вернуться к проекту
@ -280,7 +314,6 @@ ru:
build_lists:
filter_header: Фильтр
current: Текущие
all: Все
created_at_start: "Время постановки на сборку с:"
created_at_end: "Время постановки на сборку по:"
notified_at_start: "Время последнего обновления от BS с:"
@ -291,11 +324,12 @@ ru:
items_header: Элементы сборки
no_items_data: Данных нет
show: Просмотр
confirm_publish: Вы уверены, что хотите опубликовать контейнер?
cancel_button_header: Действие
cancel_button: Отмена
cancel_successed: 'Сборка отменена.'
cancel_failed: 'При отмене сборки произошла ошибка!'
cancel_success: 'Сборка отменена.'
cancel_fail: 'При отмене сборки произошла ошибка!'
publish_success: 'Сборка поставлена в очередь на публикацию.'
publish_fail: 'При публикации сборки произошла ошибка!'
build_server_status:
header: Статус сборочного сервера
@ -314,6 +348,8 @@ ru:
statuses:
build_error: ошибка сборки
build_published: опубликован
build_publish: публикуется
failed_publish: ошибка публикации
dependencies_fail: зависимости не найдены
waiting_for_response: ожидает ответа
build_pending: ожидает сборку
@ -328,6 +364,10 @@ ru:
project_version_not_found: версия не найден
flash:
subscribe:
saved: Вы подписаны на оповещения для этой задачи
destroyed: Подписка на оповещения для этой задачи убрана
exception_message: У Вас нет доступа к этой странице!
downloads:
@ -359,6 +399,16 @@ ru:
save_error: Не удалось сохранить категорию
destroyed: Категория успешно удалена
comment:
saved: Комментарий успешно сохранен
save_error: Ошибка сохранения комментария
destroyed: Комментарий удален
issue:
saved: Задача успешно сохранена
save_error: Не удалось сохранить задачу
destroyed: Задача успешно удалена
project:
saved: Проект успешно сохранен
save_error: Не удалось сохранить проект
@ -378,10 +428,6 @@ ru:
destroyed: Группа успешно удалена
user_uname_exists: Пользователь с таким именем уже зарегестрирован
role:
saved: Роль успешно сохранена
save_error: Ошибка сохранения роли
repository:
saved: Репозиторий успешно добавлен
save_error: Не удалось добавить репозиторий
@ -399,7 +445,7 @@ ru:
platform:
saved: Платформа успешно добавлена
saved_error: Не удалось создать платформу
save_error: Не удалось создать платформу
freezed: Платформа успешно заморожена
freeze_error: Не удалось заморозить платформу, попробуйте еще раз
unfreezed: Платформа успешно разморожена
@ -412,8 +458,7 @@ ru:
save_error: Не удалось сохранить билд лист для версии '%{project_version}', платформы '%{bpl}' и архитектуры '%{arch}'
no_project_version_selected: Выберите какую-нибудь версию
no_project_version_found: Выбранная версия '%{project_version}' не найдена
no_arch_selected: Выберите хотя бы одну ахритектуру
no_arch_found: Выбранные ахритектуры не найдены
no_arch_or_platform_selected: Выберите хотя бы одну ахритектуру и платформу
wrong_platform: Для основного репозитория (main) может быть выбран только его же основная платформа!
can_not_published: Опубликовать сборку можно только со статусом "Собран"
@ -456,6 +501,18 @@ ru:
arch_id: Архитектура
arch: Архитектура
comment:
body: Содержание
user: Автор
issue:
title: Заголовок
body: Содержание
user: Назначена
user_id: Назначена
project: Проект
status: Статус
private_user:
login: Логин
password: Пароль
@ -536,6 +593,7 @@ ru:
repository: Репозиторий
created_at: Создан
updated_at: Обновлен
has_issues: Включить трэкер
rpm:
name: Название
@ -590,6 +648,7 @@ ru:
is_circle: Циклическая сборка
notified_at: Информация получена
additional_repos: Дополнительные репозитории
include_repos: Подключаемые репозитории
updated_at: Обновлен
created_at: Создан
pl: Репозиторий для сохранения пакетов
@ -598,6 +657,7 @@ ru:
bpl_id: Платформа
update_type: Критичность обновления
build_requires: Пересборка с зависимостями
auto_publish: Автоматическая публикация
project_version: Версия
user: Пользователь
@ -613,3 +673,9 @@ ru:
distro: Дистрибутив
platform: Архитектура
counter: Закачки
notifications:
subjects:
new_comment_notification: Новый комментарий к Вашей задаче
new_issue_notification: Новая задача добавлена к проекту
new_user_notification: Регистрация на проекте «%{ project_name }»
issue_assign_notification: Вам назначили задачу

View File

@ -24,23 +24,17 @@ Rosa::Application.routes.draw do
match '/private/:platform_name/*file_path' => 'privates#show'
# match 'build_lists/' => 'build_lists#index', :as => :all_build_lists
# match 'build_lists/:id/cancel/' => 'build_lists#cancel', :as => :build_list_cancel
# match 'build_lists/status_build', :to => "build_lists#status_build"
# match 'build_lists/post_build', :to => "build_lists#post_build"
# match 'build_lists/pre_build', :to => "build_lists#pre_build"
# match 'build_lists/circle_build', :to => "build_lists#circle_build"
# match 'build_lists/new_bbdt', :to => "build_lists#new_bbdt"
match 'build_lists/publish_build', :to => "build_lists#publish_build"
match 'build_lists/status_build', :to => "build_lists#status_build"
match 'build_lists/post_build', :to => "build_lists#post_build"
match 'build_lists/pre_build', :to => "build_lists#pre_build"
match 'build_lists/circle_build', :to => "build_lists#circle_build"
match 'build_lists/new_bbdt', :to => "build_lists#new_bbdt"
resources :build_lists, :only => [:index, :cancel, :status_build, :post_build, :pre_build, :circle_build, :new_bbdt] do
resources :build_lists, :only => [:index, :show] do
member do
get :cancel
get :status_build
get :post_build
get :pre_build
get :circle_build
get :new_bbdt
put :cancel
put :publish
end
end
@ -85,7 +79,6 @@ Rosa::Application.routes.draw do
end
resources :projects do
resource :repo, :controller => "git/repositories", :only => [:show]
resources :wiki do
collection do
# Uncomment if gollum can revert page without name
@ -104,15 +97,12 @@ Rosa::Application.routes.draw do
match 'compare/*versions' => 'wiki#compare', :as => :compare_versions, :via => :get
end
end
resources :build_lists, :only => [:index, :show] do
collection do
get :recent
post :filter
end
member do
post :publish
end
resources :issues do
resources :comments, :only => [:edit, :create, :update, :destroy]
resources :subscribes, :only => [:create, :destroy]
end
resource :repo, :controller => "git/repositories", :only => [:show]
resources :build_lists, :only => [:index, :new, :create]
resources :collaborators, :only => [:index, :edit, :update, :add] do
collection do
@ -128,8 +118,6 @@ Rosa::Application.routes.draw do
# end
member do
get :build
post :process_build
post :fork
end
collection do
@ -141,6 +129,7 @@ Rosa::Application.routes.draw do
member do
get :add_project
get :remove_project
get :projects_list
end
end

View File

@ -0,0 +1,20 @@
class CreateIssues < ActiveRecord::Migration
def self.up
create_table :issues do |t|
t.integer :serial_id
t.integer :project_id
t.integer :user_id
t.string :title
t.text :body
t.string :status
t.timestamps
end
add_index :issues, [:project_id, :serial_id], :unique => true
end
def self.down
drop_table :issues
end
end

View File

@ -0,0 +1,16 @@
class CreateComments < ActiveRecord::Migration
def self.up
create_table :comments do |t|
t.integer :commentable_id
t.string :commentable_type
t.integer :user_id
t.text :body
t.timestamps
end
end
def self.down
drop_table :comments
end
end

View File

@ -0,0 +1,9 @@
class AddHasIssuesToProjects < ActiveRecord::Migration
def self.up
add_column :projects, :has_issues, :boolean, :default => true
end
def self.down
remove_column :projects, :has_issues
end
end

View File

@ -0,0 +1,9 @@
class AddIncludeReposToBuildLists < ActiveRecord::Migration
def self.up
add_column :build_lists, :include_repos, :text
end
def self.down
remove_column :build_lists, :include_repos
end
end

View File

@ -0,0 +1,9 @@
class AddUserIdToBuildLists < ActiveRecord::Migration
def self.up
add_column :build_lists, :user_id, :integer
end
def self.down
remove_column :build_lists, :user_id
end
end

View File

@ -0,0 +1,14 @@
class CreateSubscribes < ActiveRecord::Migration
def self.up
create_table :subscribes do |t|
t.integer :subscribeable_id
t.string :subscribeable_type
t.integer :user_id
t.timestamps
end
end
def self.down
drop_table :subscribes
end
end

View File

@ -0,0 +1,9 @@
class AddAutoPublishToBuildLists < ActiveRecord::Migration
def self.up
add_column :build_lists, :auto_publish, :boolean, :default => true
end
def self.down
remove_column :build_lists, :auto_publish
end
end

View File

@ -10,7 +10,7 @@
#
# It's strongly recommended to check this file into your version control system.
ActiveRecord::Schema.define(:version => 20111221194422) do
ActiveRecord::Schema.define(:version => 20111228182425) do
create_table "arches", :force => true do |t|
t.string "name", :null => false
@ -71,6 +71,7 @@ ActiveRecord::Schema.define(:version => 20111221194422) do
t.integer "pl_id"
t.text "include_repos"
t.integer "user_id"
t.boolean "auto_publish", :default => true
end
add_index "build_lists", ["arch_id"], :name => "index_build_lists_on_arch_id"
@ -85,6 +86,15 @@ ActiveRecord::Schema.define(:version => 20111221194422) do
t.datetime "updated_at"
end
create_table "comments", :force => true do |t|
t.integer "commentable_id"
t.string "commentable_type"
t.integer "user_id"
t.text "body"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "containers", :force => true do |t|
t.string "name", :null => false
t.integer "project_id", :null => false
@ -142,6 +152,19 @@ ActiveRecord::Schema.define(:version => 20111221194422) do
t.string "uname"
end
create_table "issues", :force => true do |t|
t.integer "serial_id"
t.integer "project_id"
t.integer "user_id"
t.string "title"
t.text "body"
t.string "status"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "issues", ["project_id", "serial_id"], :name => "index_issues_on_project_id_and_serial_id", :unique => true
create_table "platforms", :force => true do |t|
t.string "description"
t.string "name"
@ -214,6 +237,7 @@ ActiveRecord::Schema.define(:version => 20111221194422) do
t.text "description"
t.string "ancestry"
t.boolean "has_wiki"
t.boolean "has_issues", :default => true
end
add_index "projects", ["category_id"], :name => "index_projects_on_category_id"
@ -249,6 +273,14 @@ ActiveRecord::Schema.define(:version => 20111221194422) do
add_index "rpms", ["project_id", "arch_id"], :name => "index_rpms_on_project_id_and_arch_id"
add_index "rpms", ["project_id"], :name => "index_rpms_on_project_id"
create_table "subscribes", :force => true do |t|
t.integer "subscribeable_id"
t.string "subscribeable_type"
t.integer "user_id"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "users", :force => true do |t|
t.string "name"
t.string "email", :default => "", :null => false

View File

@ -1,8 +1,20 @@
=== Basic bootstrap
* rake db:drop db:setup
==
=== Setup autostart
Add to /etc/rc.d/rc.local following /srv/rosa_build/current/bin/autostart.sh
==
Add to /etc/rc.d/rc.local
/srv/rosa_build/current/bin/autostart.sh
Add to /etc/rc.d/rc.sysinit
# force run rc.local
if [ -f /etc/rc.local ]; then
. /etc/rc.local
fi
==

View File

@ -72,8 +72,15 @@ class BuildServer
self.client.call('add_to_repo', name, repo_name)
end
def self.add_build_list project_name, project_version, plname, arch, bplname, update_type, build_requires, id_web
self.client.call('add_build_list', project_name, project_version, plname, arch, bplname, update_type, build_requires, id_web)
def self.add_build_list project_name, project_version, plname, arch, bplname, update_type, build_requires, id_web, include_repos
include_repos_hash = {}.tap do |h|
include_repos.each do |r|
repo = Repository.find r
h[repo.name] = repo.platform.public_downloads_url(nil, arch, repo.name)
end
end
# raise include_repos_hash.inspect
self.client.call('add_build_list', project_name, project_version, plname, arch, bplname, update_type, build_requires, id_web, include_repos_hash)
end
def self.delete_build_list idlist

View File

@ -28,7 +28,7 @@ module Grack
end
def action
write? ? :update : :read
write? ? :write : :read
end
def project

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 612 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 807 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 894 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 635 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 852 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 B

View File

@ -1,42 +1,25 @@
function check_by_ids(ids) {
for(var i = 0; i < ids.length; i++){
$('#'+ids[i]).attr('checked', true);
}
return false;
}
function uncheck_by_ids(ids) {
for(var i = 0; i < ids.length; i++){
$('#'+ids[i]).attr('checked', false);
}
return false;
}
$(document).ready(function() {
$('.pl_ids_container input[type="hidden"]').remove();
$('select#build_list_pl_id').change(function() {
var platform_id = $(this).val();
var base_platforms = $('.base_platforms input[type=checkbox]');
$('select#build_pl').change(function() {
var is_pl_main = false;
var granted_bpl_id = '';
var pl_id = $('select#build_pl').val();
$('#include_repos').html($('.preloaded_include_repos .include_repos_' + platform_id).html());
$('input.build_bpl_ids').each(function(i,el) {
var bpl_id = $(el).attr('bpl_id');
if (pl_id == bpl_id) {
is_pl_main = true;
//granted_bpl_id = $(el).attr('bpl_id');
}
});
if (is_pl_main) {
$('input.build_bpl_ids').attr('disabled', 'disabled');
$('input.build_bpl_ids[bpl_id="'+pl_id+'"]').removeAttr('disabled');
base_platforms.each(function(){
if ($.inArray(platform_id, base_platforms.map(function(){ return $(this).val() }).get()) >= 0) {
if ($(this).val() == platform_id) {
$(this).attr('checked', 'checked');
$(this).removeAttr('disabled');
} else {
$('input.build_bpl_ids').removeAttr('disabled');
$(this).removeAttr('checked');
$(this).attr('disabled', 'disabled');
}
} else {
$(this).removeAttr('disabled');
}
});
$('select#build_pl').trigger('change');
});
$('select#build_list_pl_id').trigger('change');
$('input.user_role_chbx').click(function() {
var current = $(this);

11349
public/javascripts/jquery.dataTables.js vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,151 @@
/*
* File: jquery.dataTables.min.js
* Version: 1.8.2
* Author: Allan Jardine (www.sprymedia.co.uk)
* Info: www.datatables.net
*
* Copyright 2008-2011 Allan Jardine, all rights reserved.
*
* This source file is free software, under either the GPL v2 license or a
* BSD style license, as supplied with this software.
*
* This source file is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
*/
(function(i,za,p){i.fn.dataTableSettings=[];var D=i.fn.dataTableSettings;i.fn.dataTableExt={};var n=i.fn.dataTableExt;n.sVersion="1.8.2";n.sErrMode="alert";n.iApiIndex=0;n.oApi={};n.afnFiltering=[];n.aoFeatures=[];n.ofnSearch={};n.afnSortData=[];n.oStdClasses={sPagePrevEnabled:"paginate_enabled_previous",sPagePrevDisabled:"paginate_disabled_previous",sPageNextEnabled:"paginate_enabled_next",sPageNextDisabled:"paginate_disabled_next",sPageJUINext:"",sPageJUIPrev:"",sPageButton:"paginate_button",sPageButtonActive:"paginate_active",
sPageButtonStaticDisabled:"paginate_button paginate_button_disabled",sPageFirst:"first",sPagePrevious:"previous",sPageNext:"next",sPageLast:"last",sStripeOdd:"odd",sStripeEven:"even",sRowEmpty:"dataTables_empty",sWrapper:"dataTables_wrapper",sFilter:"dataTables_filter",sInfo:"dataTables_info",sPaging:"dataTables_paginate paging_",sLength:"dataTables_length",sProcessing:"dataTables_processing",sSortAsc:"sorting_asc",sSortDesc:"sorting_desc",sSortable:"sorting",sSortableAsc:"sorting_asc_disabled",sSortableDesc:"sorting_desc_disabled",
sSortableNone:"sorting_disabled",sSortColumn:"sorting_",sSortJUIAsc:"",sSortJUIDesc:"",sSortJUI:"",sSortJUIAscAllowed:"",sSortJUIDescAllowed:"",sSortJUIWrapper:"",sSortIcon:"",sScrollWrapper:"dataTables_scroll",sScrollHead:"dataTables_scrollHead",sScrollHeadInner:"dataTables_scrollHeadInner",sScrollBody:"dataTables_scrollBody",sScrollFoot:"dataTables_scrollFoot",sScrollFootInner:"dataTables_scrollFootInner",sFooterTH:""};n.oJUIClasses={sPagePrevEnabled:"fg-button ui-button ui-state-default ui-corner-left",
sPagePrevDisabled:"fg-button ui-button ui-state-default ui-corner-left ui-state-disabled",sPageNextEnabled:"fg-button ui-button ui-state-default ui-corner-right",sPageNextDisabled:"fg-button ui-button ui-state-default ui-corner-right ui-state-disabled",sPageJUINext:"ui-icon ui-icon-circle-arrow-e",sPageJUIPrev:"ui-icon ui-icon-circle-arrow-w",sPageButton:"fg-button ui-button ui-state-default",sPageButtonActive:"fg-button ui-button ui-state-default ui-state-disabled",sPageButtonStaticDisabled:"fg-button ui-button ui-state-default ui-state-disabled",
sPageFirst:"first ui-corner-tl ui-corner-bl",sPagePrevious:"previous",sPageNext:"next",sPageLast:"last ui-corner-tr ui-corner-br",sStripeOdd:"odd",sStripeEven:"even",sRowEmpty:"dataTables_empty",sWrapper:"dataTables_wrapper",sFilter:"dataTables_filter",sInfo:"dataTables_info",sPaging:"dataTables_paginate fg-buttonset ui-buttonset fg-buttonset-multi ui-buttonset-multi paging_",sLength:"dataTables_length",sProcessing:"dataTables_processing",sSortAsc:"ui-state-default",sSortDesc:"ui-state-default",sSortable:"ui-state-default",
sSortableAsc:"ui-state-default",sSortableDesc:"ui-state-default",sSortableNone:"ui-state-default",sSortColumn:"sorting_",sSortJUIAsc:"css_right ui-icon ui-icon-triangle-1-n",sSortJUIDesc:"css_right ui-icon ui-icon-triangle-1-s",sSortJUI:"css_right ui-icon ui-icon-carat-2-n-s",sSortJUIAscAllowed:"css_right ui-icon ui-icon-carat-1-n",sSortJUIDescAllowed:"css_right ui-icon ui-icon-carat-1-s",sSortJUIWrapper:"DataTables_sort_wrapper",sSortIcon:"DataTables_sort_icon",sScrollWrapper:"dataTables_scroll",
sScrollHead:"dataTables_scrollHead ui-state-default",sScrollHeadInner:"dataTables_scrollHeadInner",sScrollBody:"dataTables_scrollBody",sScrollFoot:"dataTables_scrollFoot ui-state-default",sScrollFootInner:"dataTables_scrollFootInner",sFooterTH:"ui-state-default"};n.oPagination={two_button:{fnInit:function(g,l,s){var t,w,y;if(g.bJUI){t=p.createElement("a");w=p.createElement("a");y=p.createElement("span");y.className=g.oClasses.sPageJUINext;w.appendChild(y);y=p.createElement("span");y.className=g.oClasses.sPageJUIPrev;
t.appendChild(y)}else{t=p.createElement("div");w=p.createElement("div")}t.className=g.oClasses.sPagePrevDisabled;w.className=g.oClasses.sPageNextDisabled;t.title=g.oLanguage.oPaginate.sPrevious;w.title=g.oLanguage.oPaginate.sNext;l.appendChild(t);l.appendChild(w);i(t).bind("click.DT",function(){g.oApi._fnPageChange(g,"previous")&&s(g)});i(w).bind("click.DT",function(){g.oApi._fnPageChange(g,"next")&&s(g)});i(t).bind("selectstart.DT",function(){return false});i(w).bind("selectstart.DT",function(){return false});
if(g.sTableId!==""&&typeof g.aanFeatures.p=="undefined"){l.setAttribute("id",g.sTableId+"_paginate");t.setAttribute("id",g.sTableId+"_previous");w.setAttribute("id",g.sTableId+"_next")}},fnUpdate:function(g){if(g.aanFeatures.p)for(var l=g.aanFeatures.p,s=0,t=l.length;s<t;s++)if(l[s].childNodes.length!==0){l[s].childNodes[0].className=g._iDisplayStart===0?g.oClasses.sPagePrevDisabled:g.oClasses.sPagePrevEnabled;l[s].childNodes[1].className=g.fnDisplayEnd()==g.fnRecordsDisplay()?g.oClasses.sPageNextDisabled:
g.oClasses.sPageNextEnabled}}},iFullNumbersShowPages:5,full_numbers:{fnInit:function(g,l,s){var t=p.createElement("span"),w=p.createElement("span"),y=p.createElement("span"),F=p.createElement("span"),x=p.createElement("span");t.innerHTML=g.oLanguage.oPaginate.sFirst;w.innerHTML=g.oLanguage.oPaginate.sPrevious;F.innerHTML=g.oLanguage.oPaginate.sNext;x.innerHTML=g.oLanguage.oPaginate.sLast;var v=g.oClasses;t.className=v.sPageButton+" "+v.sPageFirst;w.className=v.sPageButton+" "+v.sPagePrevious;F.className=
v.sPageButton+" "+v.sPageNext;x.className=v.sPageButton+" "+v.sPageLast;l.appendChild(t);l.appendChild(w);l.appendChild(y);l.appendChild(F);l.appendChild(x);i(t).bind("click.DT",function(){g.oApi._fnPageChange(g,"first")&&s(g)});i(w).bind("click.DT",function(){g.oApi._fnPageChange(g,"previous")&&s(g)});i(F).bind("click.DT",function(){g.oApi._fnPageChange(g,"next")&&s(g)});i(x).bind("click.DT",function(){g.oApi._fnPageChange(g,"last")&&s(g)});i("span",l).bind("mousedown.DT",function(){return false}).bind("selectstart.DT",
function(){return false});if(g.sTableId!==""&&typeof g.aanFeatures.p=="undefined"){l.setAttribute("id",g.sTableId+"_paginate");t.setAttribute("id",g.sTableId+"_first");w.setAttribute("id",g.sTableId+"_previous");F.setAttribute("id",g.sTableId+"_next");x.setAttribute("id",g.sTableId+"_last")}},fnUpdate:function(g,l){if(g.aanFeatures.p){var s=n.oPagination.iFullNumbersShowPages,t=Math.floor(s/2),w=Math.ceil(g.fnRecordsDisplay()/g._iDisplayLength),y=Math.ceil(g._iDisplayStart/g._iDisplayLength)+1,F=
"",x,v=g.oClasses;if(w<s){t=1;x=w}else if(y<=t){t=1;x=s}else if(y>=w-t){t=w-s+1;x=w}else{t=y-Math.ceil(s/2)+1;x=t+s-1}for(s=t;s<=x;s++)F+=y!=s?'<span class="'+v.sPageButton+'">'+s+"</span>":'<span class="'+v.sPageButtonActive+'">'+s+"</span>";x=g.aanFeatures.p;var z,$=function(M){g._iDisplayStart=(this.innerHTML*1-1)*g._iDisplayLength;l(g);M.preventDefault()},X=function(){return false};s=0;for(t=x.length;s<t;s++)if(x[s].childNodes.length!==0){z=i("span:eq(2)",x[s]);z.html(F);i("span",z).bind("click.DT",
$).bind("mousedown.DT",X).bind("selectstart.DT",X);z=x[s].getElementsByTagName("span");z=[z[0],z[1],z[z.length-2],z[z.length-1]];i(z).removeClass(v.sPageButton+" "+v.sPageButtonActive+" "+v.sPageButtonStaticDisabled);if(y==1){z[0].className+=" "+v.sPageButtonStaticDisabled;z[1].className+=" "+v.sPageButtonStaticDisabled}else{z[0].className+=" "+v.sPageButton;z[1].className+=" "+v.sPageButton}if(w===0||y==w||g._iDisplayLength==-1){z[2].className+=" "+v.sPageButtonStaticDisabled;z[3].className+=" "+
v.sPageButtonStaticDisabled}else{z[2].className+=" "+v.sPageButton;z[3].className+=" "+v.sPageButton}}}}}};n.oSort={"string-asc":function(g,l){if(typeof g!="string")g="";if(typeof l!="string")l="";g=g.toLowerCase();l=l.toLowerCase();return g<l?-1:g>l?1:0},"string-desc":function(g,l){if(typeof g!="string")g="";if(typeof l!="string")l="";g=g.toLowerCase();l=l.toLowerCase();return g<l?1:g>l?-1:0},"html-asc":function(g,l){g=g.replace(/<.*?>/g,"").toLowerCase();l=l.replace(/<.*?>/g,"").toLowerCase();return g<
l?-1:g>l?1:0},"html-desc":function(g,l){g=g.replace(/<.*?>/g,"").toLowerCase();l=l.replace(/<.*?>/g,"").toLowerCase();return g<l?1:g>l?-1:0},"date-asc":function(g,l){g=Date.parse(g);l=Date.parse(l);if(isNaN(g)||g==="")g=Date.parse("01/01/1970 00:00:00");if(isNaN(l)||l==="")l=Date.parse("01/01/1970 00:00:00");return g-l},"date-desc":function(g,l){g=Date.parse(g);l=Date.parse(l);if(isNaN(g)||g==="")g=Date.parse("01/01/1970 00:00:00");if(isNaN(l)||l==="")l=Date.parse("01/01/1970 00:00:00");return l-
g},"numeric-asc":function(g,l){return(g=="-"||g===""?0:g*1)-(l=="-"||l===""?0:l*1)},"numeric-desc":function(g,l){return(l=="-"||l===""?0:l*1)-(g=="-"||g===""?0:g*1)}};n.aTypes=[function(g){if(typeof g=="number")return"numeric";else if(typeof g!="string")return null;var l,s=false;l=g.charAt(0);if("0123456789-".indexOf(l)==-1)return null;for(var t=1;t<g.length;t++){l=g.charAt(t);if("0123456789.".indexOf(l)==-1)return null;if(l=="."){if(s)return null;s=true}}return"numeric"},function(g){var l=Date.parse(g);
if(l!==null&&!isNaN(l)||typeof g=="string"&&g.length===0)return"date";return null},function(g){if(typeof g=="string"&&g.indexOf("<")!=-1&&g.indexOf(">")!=-1)return"html";return null}];n.fnVersionCheck=function(g){var l=function(x,v){for(;x.length<v;)x+="0";return x},s=n.sVersion.split(".");g=g.split(".");for(var t="",w="",y=0,F=g.length;y<F;y++){t+=l(s[y],3);w+=l(g[y],3)}return parseInt(t,10)>=parseInt(w,10)};n._oExternConfig={iNextUnique:0};i.fn.dataTable=function(g){function l(){this.fnRecordsTotal=
function(){return this.oFeatures.bServerSide?parseInt(this._iRecordsTotal,10):this.aiDisplayMaster.length};this.fnRecordsDisplay=function(){return this.oFeatures.bServerSide?parseInt(this._iRecordsDisplay,10):this.aiDisplay.length};this.fnDisplayEnd=function(){return this.oFeatures.bServerSide?this.oFeatures.bPaginate===false||this._iDisplayLength==-1?this._iDisplayStart+this.aiDisplay.length:Math.min(this._iDisplayStart+this._iDisplayLength,this._iRecordsDisplay):this._iDisplayEnd};this.sInstance=
this.oInstance=null;this.oFeatures={bPaginate:true,bLengthChange:true,bFilter:true,bSort:true,bInfo:true,bAutoWidth:true,bProcessing:false,bSortClasses:true,bStateSave:false,bServerSide:false,bDeferRender:false};this.oScroll={sX:"",sXInner:"",sY:"",bCollapse:false,bInfinite:false,iLoadGap:100,iBarWidth:0,bAutoCss:true};this.aanFeatures=[];this.oLanguage={sProcessing:"Processing...",sLengthMenu:"Show _MENU_ entries",sZeroRecords:"No matching records found",sEmptyTable:"No data available in table",
sLoadingRecords:"Loading...",sInfo:"Showing _START_ to _END_ of _TOTAL_ entries",sInfoEmpty:"Showing 0 to 0 of 0 entries",sInfoFiltered:"(filtered from _MAX_ total entries)",sInfoPostFix:"",sInfoThousands:",",sSearch:"Search:",sUrl:"",oPaginate:{sFirst:"First",sPrevious:"Previous",sNext:"Next",sLast:"Last"},fnInfoCallback:null};this.aoData=[];this.aiDisplay=[];this.aiDisplayMaster=[];this.aoColumns=[];this.aoHeader=[];this.aoFooter=[];this.iNextId=0;this.asDataSearch=[];this.oPreviousSearch={sSearch:"",
bRegex:false,bSmart:true};this.aoPreSearchCols=[];this.aaSorting=[[0,"asc",0]];this.aaSortingFixed=null;this.asStripeClasses=[];this.asDestroyStripes=[];this.sDestroyWidth=0;this.fnFooterCallback=this.fnHeaderCallback=this.fnRowCallback=null;this.aoDrawCallback=[];this.fnInitComplete=this.fnPreDrawCallback=null;this.sTableId="";this.nTableWrapper=this.nTBody=this.nTFoot=this.nTHead=this.nTable=null;this.bInitialised=this.bDeferLoading=false;this.aoOpenRows=[];this.sDom="lfrtip";this.sPaginationType=
"two_button";this.iCookieDuration=7200;this.sCookiePrefix="SpryMedia_DataTables_";this.fnCookieCallback=null;this.aoStateSave=[];this.aoStateLoad=[];this.sAjaxSource=this.oLoadedState=null;this.sAjaxDataProp="aaData";this.bAjaxDataGet=true;this.jqXHR=null;this.fnServerData=function(a,b,c,d){d.jqXHR=i.ajax({url:a,data:b,success:function(f){i(d.oInstance).trigger("xhr",d);c(f)},dataType:"json",cache:false,error:function(f,e){e=="parsererror"&&alert("DataTables warning: JSON data from server could not be parsed. This is caused by a JSON formatting error.")}})};
this.aoServerParams=[];this.fnFormatNumber=function(a){if(a<1E3)return a;else{var b=a+"";a=b.split("");var c="";b=b.length;for(var d=0;d<b;d++){if(d%3===0&&d!==0)c=this.oLanguage.sInfoThousands+c;c=a[b-d-1]+c}}return c};this.aLengthMenu=[10,25,50,100];this.bDrawing=this.iDraw=0;this.iDrawError=-1;this._iDisplayLength=10;this._iDisplayStart=0;this._iDisplayEnd=10;this._iRecordsDisplay=this._iRecordsTotal=0;this.bJUI=false;this.oClasses=n.oStdClasses;this.bSortCellsTop=this.bSorted=this.bFiltered=false;
this.oInit=null;this.aoDestroyCallback=[]}function s(a){return function(){var b=[A(this[n.iApiIndex])].concat(Array.prototype.slice.call(arguments));return n.oApi[a].apply(this,b)}}function t(a){var b,c,d=a.iInitDisplayStart;if(a.bInitialised===false)setTimeout(function(){t(a)},200);else{Aa(a);X(a);M(a,a.aoHeader);a.nTFoot&&M(a,a.aoFooter);K(a,true);a.oFeatures.bAutoWidth&&ga(a);b=0;for(c=a.aoColumns.length;b<c;b++)if(a.aoColumns[b].sWidth!==null)a.aoColumns[b].nTh.style.width=q(a.aoColumns[b].sWidth);
if(a.oFeatures.bSort)R(a);else if(a.oFeatures.bFilter)N(a,a.oPreviousSearch);else{a.aiDisplay=a.aiDisplayMaster.slice();E(a);C(a)}if(a.sAjaxSource!==null&&!a.oFeatures.bServerSide){c=[];ha(a,c);a.fnServerData.call(a.oInstance,a.sAjaxSource,c,function(f){var e=f;if(a.sAjaxDataProp!=="")e=aa(a.sAjaxDataProp)(f);for(b=0;b<e.length;b++)v(a,e[b]);a.iInitDisplayStart=d;if(a.oFeatures.bSort)R(a);else{a.aiDisplay=a.aiDisplayMaster.slice();E(a);C(a)}K(a,false);w(a,f)},a)}else if(!a.oFeatures.bServerSide){K(a,
false);w(a)}}}function w(a,b){a._bInitComplete=true;if(typeof a.fnInitComplete=="function")typeof b!="undefined"?a.fnInitComplete.call(a.oInstance,a,b):a.fnInitComplete.call(a.oInstance,a)}function y(a,b,c){a.oLanguage=i.extend(true,a.oLanguage,b);typeof b.sEmptyTable=="undefined"&&typeof b.sZeroRecords!="undefined"&&o(a.oLanguage,b,"sZeroRecords","sEmptyTable");typeof b.sLoadingRecords=="undefined"&&typeof b.sZeroRecords!="undefined"&&o(a.oLanguage,b,"sZeroRecords","sLoadingRecords");c&&t(a)}function F(a,
b){var c=a.aoColumns.length;b={sType:null,_bAutoType:true,bVisible:true,bSearchable:true,bSortable:true,asSorting:["asc","desc"],sSortingClass:a.oClasses.sSortable,sSortingClassJUI:a.oClasses.sSortJUI,sTitle:b?b.innerHTML:"",sName:"",sWidth:null,sWidthOrig:null,sClass:null,fnRender:null,bUseRendered:true,iDataSort:c,mDataProp:c,fnGetData:null,fnSetData:null,sSortDataType:"std",sDefaultContent:null,sContentPadding:"",nTh:b?b:p.createElement("th"),nTf:null};a.aoColumns.push(b);if(typeof a.aoPreSearchCols[c]==
"undefined"||a.aoPreSearchCols[c]===null)a.aoPreSearchCols[c]={sSearch:"",bRegex:false,bSmart:true};else{if(typeof a.aoPreSearchCols[c].bRegex=="undefined")a.aoPreSearchCols[c].bRegex=true;if(typeof a.aoPreSearchCols[c].bSmart=="undefined")a.aoPreSearchCols[c].bSmart=true}x(a,c,null)}function x(a,b,c){b=a.aoColumns[b];if(typeof c!="undefined"&&c!==null){if(typeof c.sType!="undefined"){b.sType=c.sType;b._bAutoType=false}o(b,c,"bVisible");o(b,c,"bSearchable");o(b,c,"bSortable");o(b,c,"sTitle");o(b,
c,"sName");o(b,c,"sWidth");o(b,c,"sWidth","sWidthOrig");o(b,c,"sClass");o(b,c,"fnRender");o(b,c,"bUseRendered");o(b,c,"iDataSort");o(b,c,"mDataProp");o(b,c,"asSorting");o(b,c,"sSortDataType");o(b,c,"sDefaultContent");o(b,c,"sContentPadding")}b.fnGetData=aa(b.mDataProp);b.fnSetData=Ba(b.mDataProp);if(!a.oFeatures.bSort)b.bSortable=false;if(!b.bSortable||i.inArray("asc",b.asSorting)==-1&&i.inArray("desc",b.asSorting)==-1){b.sSortingClass=a.oClasses.sSortableNone;b.sSortingClassJUI=""}else if(b.bSortable||
i.inArray("asc",b.asSorting)==-1&&i.inArray("desc",b.asSorting)==-1){b.sSortingClass=a.oClasses.sSortable;b.sSortingClassJUI=a.oClasses.sSortJUI}else if(i.inArray("asc",b.asSorting)!=-1&&i.inArray("desc",b.asSorting)==-1){b.sSortingClass=a.oClasses.sSortableAsc;b.sSortingClassJUI=a.oClasses.sSortJUIAscAllowed}else if(i.inArray("asc",b.asSorting)==-1&&i.inArray("desc",b.asSorting)!=-1){b.sSortingClass=a.oClasses.sSortableDesc;b.sSortingClassJUI=a.oClasses.sSortJUIDescAllowed}}function v(a,b){var c;
c=i.isArray(b)?b.slice():i.extend(true,{},b);b=a.aoData.length;var d={nTr:null,_iId:a.iNextId++,_aData:c,_anHidden:[],_sRowStripe:""};a.aoData.push(d);for(var f,e=0,h=a.aoColumns.length;e<h;e++){c=a.aoColumns[e];typeof c.fnRender=="function"&&c.bUseRendered&&c.mDataProp!==null&&O(a,b,e,c.fnRender({iDataRow:b,iDataColumn:e,aData:d._aData,oSettings:a}));if(c._bAutoType&&c.sType!="string"){f=G(a,b,e,"type");if(f!==null&&f!==""){f=ia(f);if(c.sType===null)c.sType=f;else if(c.sType!=f&&c.sType!="html")c.sType=
"string"}}}a.aiDisplayMaster.push(b);a.oFeatures.bDeferRender||z(a,b);return b}function z(a,b){var c=a.aoData[b],d;if(c.nTr===null){c.nTr=p.createElement("tr");typeof c._aData.DT_RowId!="undefined"&&c.nTr.setAttribute("id",c._aData.DT_RowId);typeof c._aData.DT_RowClass!="undefined"&&i(c.nTr).addClass(c._aData.DT_RowClass);for(var f=0,e=a.aoColumns.length;f<e;f++){var h=a.aoColumns[f];d=p.createElement("td");d.innerHTML=typeof h.fnRender=="function"&&(!h.bUseRendered||h.mDataProp===null)?h.fnRender({iDataRow:b,
iDataColumn:f,aData:c._aData,oSettings:a}):G(a,b,f,"display");if(h.sClass!==null)d.className=h.sClass;if(h.bVisible){c.nTr.appendChild(d);c._anHidden[f]=null}else c._anHidden[f]=d}}}function $(a){var b,c,d,f,e,h,j,k,m;if(a.bDeferLoading||a.sAjaxSource===null){j=a.nTBody.childNodes;b=0;for(c=j.length;b<c;b++)if(j[b].nodeName.toUpperCase()=="TR"){k=a.aoData.length;a.aoData.push({nTr:j[b],_iId:a.iNextId++,_aData:[],_anHidden:[],_sRowStripe:""});a.aiDisplayMaster.push(k);h=j[b].childNodes;d=e=0;for(f=
h.length;d<f;d++){m=h[d].nodeName.toUpperCase();if(m=="TD"||m=="TH"){O(a,k,e,i.trim(h[d].innerHTML));e++}}}}j=ba(a);h=[];b=0;for(c=j.length;b<c;b++){d=0;for(f=j[b].childNodes.length;d<f;d++){e=j[b].childNodes[d];m=e.nodeName.toUpperCase();if(m=="TD"||m=="TH")h.push(e)}}h.length!=j.length*a.aoColumns.length&&J(a,1,"Unexpected number of TD elements. Expected "+j.length*a.aoColumns.length+" and got "+h.length+". DataTables does not support rowspan / colspan in the table body, and there must be one cell for each row/column combination.");
d=0;for(f=a.aoColumns.length;d<f;d++){if(a.aoColumns[d].sTitle===null)a.aoColumns[d].sTitle=a.aoColumns[d].nTh.innerHTML;j=a.aoColumns[d]._bAutoType;m=typeof a.aoColumns[d].fnRender=="function";e=a.aoColumns[d].sClass!==null;k=a.aoColumns[d].bVisible;var u,r;if(j||m||e||!k){b=0;for(c=a.aoData.length;b<c;b++){u=h[b*f+d];if(j&&a.aoColumns[d].sType!="string"){r=G(a,b,d,"type");if(r!==""){r=ia(r);if(a.aoColumns[d].sType===null)a.aoColumns[d].sType=r;else if(a.aoColumns[d].sType!=r&&a.aoColumns[d].sType!=
"html")a.aoColumns[d].sType="string"}}if(m){r=a.aoColumns[d].fnRender({iDataRow:b,iDataColumn:d,aData:a.aoData[b]._aData,oSettings:a});u.innerHTML=r;a.aoColumns[d].bUseRendered&&O(a,b,d,r)}if(e)u.className+=" "+a.aoColumns[d].sClass;if(k)a.aoData[b]._anHidden[d]=null;else{a.aoData[b]._anHidden[d]=u;u.parentNode.removeChild(u)}}}}}function X(a){var b,c,d;a.nTHead.getElementsByTagName("tr");if(a.nTHead.getElementsByTagName("th").length!==0){b=0;for(d=a.aoColumns.length;b<d;b++){c=a.aoColumns[b].nTh;
a.aoColumns[b].sClass!==null&&i(c).addClass(a.aoColumns[b].sClass);if(a.aoColumns[b].sTitle!=c.innerHTML)c.innerHTML=a.aoColumns[b].sTitle}}else{var f=p.createElement("tr");b=0;for(d=a.aoColumns.length;b<d;b++){c=a.aoColumns[b].nTh;c.innerHTML=a.aoColumns[b].sTitle;a.aoColumns[b].sClass!==null&&i(c).addClass(a.aoColumns[b].sClass);f.appendChild(c)}i(a.nTHead).html("")[0].appendChild(f);Y(a.aoHeader,a.nTHead)}if(a.bJUI){b=0;for(d=a.aoColumns.length;b<d;b++){c=a.aoColumns[b].nTh;f=p.createElement("div");
f.className=a.oClasses.sSortJUIWrapper;i(c).contents().appendTo(f);var e=p.createElement("span");e.className=a.oClasses.sSortIcon;f.appendChild(e);c.appendChild(f)}}d=function(){this.onselectstart=function(){return false};return false};if(a.oFeatures.bSort)for(b=0;b<a.aoColumns.length;b++)if(a.aoColumns[b].bSortable!==false){ja(a,a.aoColumns[b].nTh,b);i(a.aoColumns[b].nTh).bind("mousedown.DT",d)}else i(a.aoColumns[b].nTh).addClass(a.oClasses.sSortableNone);a.oClasses.sFooterTH!==""&&i(a.nTFoot).children("tr").children("th").addClass(a.oClasses.sFooterTH);
if(a.nTFoot!==null){c=S(a,null,a.aoFooter);b=0;for(d=a.aoColumns.length;b<d;b++)if(typeof c[b]!="undefined")a.aoColumns[b].nTf=c[b]}}function M(a,b,c){var d,f,e,h=[],j=[],k=a.aoColumns.length;if(typeof c=="undefined")c=false;d=0;for(f=b.length;d<f;d++){h[d]=b[d].slice();h[d].nTr=b[d].nTr;for(e=k-1;e>=0;e--)!a.aoColumns[e].bVisible&&!c&&h[d].splice(e,1);j.push([])}d=0;for(f=h.length;d<f;d++){if(h[d].nTr){a=0;for(e=h[d].nTr.childNodes.length;a<e;a++)h[d].nTr.removeChild(h[d].nTr.childNodes[0])}e=0;
for(b=h[d].length;e<b;e++){k=c=1;if(typeof j[d][e]=="undefined"){h[d].nTr.appendChild(h[d][e].cell);for(j[d][e]=1;typeof h[d+c]!="undefined"&&h[d][e].cell==h[d+c][e].cell;){j[d+c][e]=1;c++}for(;typeof h[d][e+k]!="undefined"&&h[d][e].cell==h[d][e+k].cell;){for(a=0;a<c;a++)j[d+a][e+k]=1;k++}h[d][e].cell.rowSpan=c;h[d][e].cell.colSpan=k}}}}function C(a){var b,c,d=[],f=0,e=false;b=a.asStripeClasses.length;c=a.aoOpenRows.length;if(!(a.fnPreDrawCallback!==null&&a.fnPreDrawCallback.call(a.oInstance,a)===
false)){a.bDrawing=true;if(typeof a.iInitDisplayStart!="undefined"&&a.iInitDisplayStart!=-1){a._iDisplayStart=a.oFeatures.bServerSide?a.iInitDisplayStart:a.iInitDisplayStart>=a.fnRecordsDisplay()?0:a.iInitDisplayStart;a.iInitDisplayStart=-1;E(a)}if(a.bDeferLoading){a.bDeferLoading=false;a.iDraw++}else if(a.oFeatures.bServerSide){if(!a.bDestroying&&!Ca(a))return}else a.iDraw++;if(a.aiDisplay.length!==0){var h=a._iDisplayStart,j=a._iDisplayEnd;if(a.oFeatures.bServerSide){h=0;j=a.aoData.length}for(h=
h;h<j;h++){var k=a.aoData[a.aiDisplay[h]];k.nTr===null&&z(a,a.aiDisplay[h]);var m=k.nTr;if(b!==0){var u=a.asStripeClasses[f%b];if(k._sRowStripe!=u){i(m).removeClass(k._sRowStripe).addClass(u);k._sRowStripe=u}}if(typeof a.fnRowCallback=="function"){m=a.fnRowCallback.call(a.oInstance,m,a.aoData[a.aiDisplay[h]]._aData,f,h);if(!m&&!e){J(a,0,"A node was not returned by fnRowCallback");e=true}}d.push(m);f++;if(c!==0)for(k=0;k<c;k++)m==a.aoOpenRows[k].nParent&&d.push(a.aoOpenRows[k].nTr)}}else{d[0]=p.createElement("tr");
if(typeof a.asStripeClasses[0]!="undefined")d[0].className=a.asStripeClasses[0];e=a.oLanguage.sZeroRecords.replace("_MAX_",a.fnFormatNumber(a.fnRecordsTotal()));if(a.iDraw==1&&a.sAjaxSource!==null&&!a.oFeatures.bServerSide)e=a.oLanguage.sLoadingRecords;else if(typeof a.oLanguage.sEmptyTable!="undefined"&&a.fnRecordsTotal()===0)e=a.oLanguage.sEmptyTable;b=p.createElement("td");b.setAttribute("valign","top");b.colSpan=Z(a);b.className=a.oClasses.sRowEmpty;b.innerHTML=e;d[f].appendChild(b)}typeof a.fnHeaderCallback==
"function"&&a.fnHeaderCallback.call(a.oInstance,i(a.nTHead).children("tr")[0],ca(a),a._iDisplayStart,a.fnDisplayEnd(),a.aiDisplay);typeof a.fnFooterCallback=="function"&&a.fnFooterCallback.call(a.oInstance,i(a.nTFoot).children("tr")[0],ca(a),a._iDisplayStart,a.fnDisplayEnd(),a.aiDisplay);f=p.createDocumentFragment();b=p.createDocumentFragment();if(a.nTBody){e=a.nTBody.parentNode;b.appendChild(a.nTBody);if(!a.oScroll.bInfinite||!a._bInitComplete||a.bSorted||a.bFiltered){c=a.nTBody.childNodes;for(b=
c.length-1;b>=0;b--)c[b].parentNode.removeChild(c[b])}b=0;for(c=d.length;b<c;b++)f.appendChild(d[b]);a.nTBody.appendChild(f);e!==null&&e.appendChild(a.nTBody)}for(b=a.aoDrawCallback.length-1;b>=0;b--)a.aoDrawCallback[b].fn.call(a.oInstance,a);i(a.oInstance).trigger("draw",a);a.bSorted=false;a.bFiltered=false;a.bDrawing=false;if(a.oFeatures.bServerSide){K(a,false);typeof a._bInitComplete=="undefined"&&w(a)}}}function da(a){if(a.oFeatures.bSort)R(a,a.oPreviousSearch);else if(a.oFeatures.bFilter)N(a,
a.oPreviousSearch);else{E(a);C(a)}}function Ca(a){if(a.bAjaxDataGet){a.iDraw++;K(a,true);var b=Da(a);ha(a,b);a.fnServerData.call(a.oInstance,a.sAjaxSource,b,function(c){Ea(a,c)},a);return false}else return true}function Da(a){var b=a.aoColumns.length,c=[],d,f;c.push({name:"sEcho",value:a.iDraw});c.push({name:"iColumns",value:b});c.push({name:"sColumns",value:ka(a)});c.push({name:"iDisplayStart",value:a._iDisplayStart});c.push({name:"iDisplayLength",value:a.oFeatures.bPaginate!==false?a._iDisplayLength:
-1});for(f=0;f<b;f++){d=a.aoColumns[f].mDataProp;c.push({name:"mDataProp_"+f,value:typeof d=="function"?"function":d})}if(a.oFeatures.bFilter!==false){c.push({name:"sSearch",value:a.oPreviousSearch.sSearch});c.push({name:"bRegex",value:a.oPreviousSearch.bRegex});for(f=0;f<b;f++){c.push({name:"sSearch_"+f,value:a.aoPreSearchCols[f].sSearch});c.push({name:"bRegex_"+f,value:a.aoPreSearchCols[f].bRegex});c.push({name:"bSearchable_"+f,value:a.aoColumns[f].bSearchable})}}if(a.oFeatures.bSort!==false){d=
a.aaSortingFixed!==null?a.aaSortingFixed.length:0;var e=a.aaSorting.length;c.push({name:"iSortingCols",value:d+e});for(f=0;f<d;f++){c.push({name:"iSortCol_"+f,value:a.aaSortingFixed[f][0]});c.push({name:"sSortDir_"+f,value:a.aaSortingFixed[f][1]})}for(f=0;f<e;f++){c.push({name:"iSortCol_"+(f+d),value:a.aaSorting[f][0]});c.push({name:"sSortDir_"+(f+d),value:a.aaSorting[f][1]})}for(f=0;f<b;f++)c.push({name:"bSortable_"+f,value:a.aoColumns[f].bSortable})}return c}function ha(a,b){for(var c=0,d=a.aoServerParams.length;c<
d;c++)a.aoServerParams[c].fn.call(a.oInstance,b)}function Ea(a,b){if(typeof b.sEcho!="undefined")if(b.sEcho*1<a.iDraw)return;else a.iDraw=b.sEcho*1;if(!a.oScroll.bInfinite||a.oScroll.bInfinite&&(a.bSorted||a.bFiltered))la(a);a._iRecordsTotal=b.iTotalRecords;a._iRecordsDisplay=b.iTotalDisplayRecords;var c=ka(a);if(c=typeof b.sColumns!="undefined"&&c!==""&&b.sColumns!=c)var d=Fa(a,b.sColumns);b=aa(a.sAjaxDataProp)(b);for(var f=0,e=b.length;f<e;f++)if(c){for(var h=[],j=0,k=a.aoColumns.length;j<k;j++)h.push(b[f][d[j]]);
v(a,h)}else v(a,b[f]);a.aiDisplay=a.aiDisplayMaster.slice();a.bAjaxDataGet=false;C(a);a.bAjaxDataGet=true;K(a,false)}function Aa(a){var b=p.createElement("div");a.nTable.parentNode.insertBefore(b,a.nTable);a.nTableWrapper=p.createElement("div");a.nTableWrapper.className=a.oClasses.sWrapper;a.sTableId!==""&&a.nTableWrapper.setAttribute("id",a.sTableId+"_wrapper");a.nTableReinsertBefore=a.nTable.nextSibling;for(var c=a.nTableWrapper,d=a.sDom.split(""),f,e,h,j,k,m,u,r=0;r<d.length;r++){e=0;h=d[r];if(h==
"<"){j=p.createElement("div");k=d[r+1];if(k=="'"||k=='"'){m="";for(u=2;d[r+u]!=k;){m+=d[r+u];u++}if(m=="H")m="fg-toolbar ui-toolbar ui-widget-header ui-corner-tl ui-corner-tr ui-helper-clearfix";else if(m=="F")m="fg-toolbar ui-toolbar ui-widget-header ui-corner-bl ui-corner-br ui-helper-clearfix";if(m.indexOf(".")!=-1){k=m.split(".");j.setAttribute("id",k[0].substr(1,k[0].length-1));j.className=k[1]}else if(m.charAt(0)=="#")j.setAttribute("id",m.substr(1,m.length-1));else j.className=m;r+=u}c.appendChild(j);
c=j}else if(h==">")c=c.parentNode;else if(h=="l"&&a.oFeatures.bPaginate&&a.oFeatures.bLengthChange){f=Ga(a);e=1}else if(h=="f"&&a.oFeatures.bFilter){f=Ha(a);e=1}else if(h=="r"&&a.oFeatures.bProcessing){f=Ia(a);e=1}else if(h=="t"){f=Ja(a);e=1}else if(h=="i"&&a.oFeatures.bInfo){f=Ka(a);e=1}else if(h=="p"&&a.oFeatures.bPaginate){f=La(a);e=1}else if(n.aoFeatures.length!==0){j=n.aoFeatures;u=0;for(k=j.length;u<k;u++)if(h==j[u].cFeature){if(f=j[u].fnInit(a))e=1;break}}if(e==1&&f!==null){if(typeof a.aanFeatures[h]!=
"object")a.aanFeatures[h]=[];a.aanFeatures[h].push(f);c.appendChild(f)}}b.parentNode.replaceChild(a.nTableWrapper,b)}function Ja(a){if(a.oScroll.sX===""&&a.oScroll.sY==="")return a.nTable;var b=p.createElement("div"),c=p.createElement("div"),d=p.createElement("div"),f=p.createElement("div"),e=p.createElement("div"),h=p.createElement("div"),j=a.nTable.cloneNode(false),k=a.nTable.cloneNode(false),m=a.nTable.getElementsByTagName("thead")[0],u=a.nTable.getElementsByTagName("tfoot").length===0?null:a.nTable.getElementsByTagName("tfoot")[0],
r=typeof g.bJQueryUI!="undefined"&&g.bJQueryUI?n.oJUIClasses:n.oStdClasses;c.appendChild(d);e.appendChild(h);f.appendChild(a.nTable);b.appendChild(c);b.appendChild(f);d.appendChild(j);j.appendChild(m);if(u!==null){b.appendChild(e);h.appendChild(k);k.appendChild(u)}b.className=r.sScrollWrapper;c.className=r.sScrollHead;d.className=r.sScrollHeadInner;f.className=r.sScrollBody;e.className=r.sScrollFoot;h.className=r.sScrollFootInner;if(a.oScroll.bAutoCss){c.style.overflow="hidden";c.style.position="relative";
e.style.overflow="hidden";f.style.overflow="auto"}c.style.border="0";c.style.width="100%";e.style.border="0";d.style.width="150%";j.removeAttribute("id");j.style.marginLeft="0";a.nTable.style.marginLeft="0";if(u!==null){k.removeAttribute("id");k.style.marginLeft="0"}d=i(a.nTable).children("caption");h=0;for(k=d.length;h<k;h++)j.appendChild(d[h]);if(a.oScroll.sX!==""){c.style.width=q(a.oScroll.sX);f.style.width=q(a.oScroll.sX);if(u!==null)e.style.width=q(a.oScroll.sX);i(f).scroll(function(){c.scrollLeft=
this.scrollLeft;if(u!==null)e.scrollLeft=this.scrollLeft})}if(a.oScroll.sY!=="")f.style.height=q(a.oScroll.sY);a.aoDrawCallback.push({fn:Ma,sName:"scrolling"});a.oScroll.bInfinite&&i(f).scroll(function(){if(!a.bDrawing)if(i(this).scrollTop()+i(this).height()>i(a.nTable).height()-a.oScroll.iLoadGap)if(a.fnDisplayEnd()<a.fnRecordsDisplay()){ma(a,"next");E(a);C(a)}});a.nScrollHead=c;a.nScrollFoot=e;return b}function Ma(a){var b=a.nScrollHead.getElementsByTagName("div")[0],c=b.getElementsByTagName("table")[0],
d=a.nTable.parentNode,f,e,h,j,k,m,u,r,H=[],L=a.nTFoot!==null?a.nScrollFoot.getElementsByTagName("div")[0]:null,T=a.nTFoot!==null?L.getElementsByTagName("table")[0]:null,B=i.browser.msie&&i.browser.version<=7;h=a.nTable.getElementsByTagName("thead");h.length>0&&a.nTable.removeChild(h[0]);if(a.nTFoot!==null){k=a.nTable.getElementsByTagName("tfoot");k.length>0&&a.nTable.removeChild(k[0])}h=a.nTHead.cloneNode(true);a.nTable.insertBefore(h,a.nTable.childNodes[0]);if(a.nTFoot!==null){k=a.nTFoot.cloneNode(true);
a.nTable.insertBefore(k,a.nTable.childNodes[1])}if(a.oScroll.sX===""){d.style.width="100%";b.parentNode.style.width="100%"}var U=S(a,h);f=0;for(e=U.length;f<e;f++){u=Na(a,f);U[f].style.width=a.aoColumns[u].sWidth}a.nTFoot!==null&&P(function(I){I.style.width=""},k.getElementsByTagName("tr"));f=i(a.nTable).outerWidth();if(a.oScroll.sX===""){a.nTable.style.width="100%";if(B&&(d.scrollHeight>d.offsetHeight||i(d).css("overflow-y")=="scroll"))a.nTable.style.width=q(i(a.nTable).outerWidth()-a.oScroll.iBarWidth)}else if(a.oScroll.sXInner!==
"")a.nTable.style.width=q(a.oScroll.sXInner);else if(f==i(d).width()&&i(d).height()<i(a.nTable).height()){a.nTable.style.width=q(f-a.oScroll.iBarWidth);if(i(a.nTable).outerWidth()>f-a.oScroll.iBarWidth)a.nTable.style.width=q(f)}else a.nTable.style.width=q(f);f=i(a.nTable).outerWidth();e=a.nTHead.getElementsByTagName("tr");h=h.getElementsByTagName("tr");P(function(I,na){m=I.style;m.paddingTop="0";m.paddingBottom="0";m.borderTopWidth="0";m.borderBottomWidth="0";m.height=0;r=i(I).width();na.style.width=
q(r);H.push(r)},h,e);i(h).height(0);if(a.nTFoot!==null){j=k.getElementsByTagName("tr");k=a.nTFoot.getElementsByTagName("tr");P(function(I,na){m=I.style;m.paddingTop="0";m.paddingBottom="0";m.borderTopWidth="0";m.borderBottomWidth="0";m.height=0;r=i(I).width();na.style.width=q(r);H.push(r)},j,k);i(j).height(0)}P(function(I){I.innerHTML="";I.style.width=q(H.shift())},h);a.nTFoot!==null&&P(function(I){I.innerHTML="";I.style.width=q(H.shift())},j);if(i(a.nTable).outerWidth()<f){j=d.scrollHeight>d.offsetHeight||
i(d).css("overflow-y")=="scroll"?f+a.oScroll.iBarWidth:f;if(B&&(d.scrollHeight>d.offsetHeight||i(d).css("overflow-y")=="scroll"))a.nTable.style.width=q(j-a.oScroll.iBarWidth);d.style.width=q(j);b.parentNode.style.width=q(j);if(a.nTFoot!==null)L.parentNode.style.width=q(j);if(a.oScroll.sX==="")J(a,1,"The table cannot fit into the current element which will cause column misalignment. The table has been drawn at its minimum possible width.");else a.oScroll.sXInner!==""&&J(a,1,"The table cannot fit into the current element which will cause column misalignment. Increase the sScrollXInner value or remove it to allow automatic calculation")}else{d.style.width=
q("100%");b.parentNode.style.width=q("100%");if(a.nTFoot!==null)L.parentNode.style.width=q("100%")}if(a.oScroll.sY==="")if(B)d.style.height=q(a.nTable.offsetHeight+a.oScroll.iBarWidth);if(a.oScroll.sY!==""&&a.oScroll.bCollapse){d.style.height=q(a.oScroll.sY);B=a.oScroll.sX!==""&&a.nTable.offsetWidth>d.offsetWidth?a.oScroll.iBarWidth:0;if(a.nTable.offsetHeight<d.offsetHeight)d.style.height=q(i(a.nTable).height()+B)}B=i(a.nTable).outerWidth();c.style.width=q(B);b.style.width=q(B+a.oScroll.iBarWidth);
if(a.nTFoot!==null){L.style.width=q(a.nTable.offsetWidth+a.oScroll.iBarWidth);T.style.width=q(a.nTable.offsetWidth)}if(a.bSorted||a.bFiltered)d.scrollTop=0}function ea(a){if(a.oFeatures.bAutoWidth===false)return false;ga(a);for(var b=0,c=a.aoColumns.length;b<c;b++)a.aoColumns[b].nTh.style.width=a.aoColumns[b].sWidth}function Ha(a){var b=a.oLanguage.sSearch;b=b.indexOf("_INPUT_")!==-1?b.replace("_INPUT_",'<input type="text" />'):b===""?'<input type="text" />':b+' <input type="text" />';var c=p.createElement("div");
c.className=a.oClasses.sFilter;c.innerHTML="<label>"+b+"</label>";a.sTableId!==""&&typeof a.aanFeatures.f=="undefined"&&c.setAttribute("id",a.sTableId+"_filter");b=i("input",c);b.val(a.oPreviousSearch.sSearch.replace('"',"&quot;"));b.bind("keyup.DT",function(){for(var d=a.aanFeatures.f,f=0,e=d.length;f<e;f++)d[f]!=i(this).parents("div.dataTables_filter")[0]&&i("input",d[f]).val(this.value);this.value!=a.oPreviousSearch.sSearch&&N(a,{sSearch:this.value,bRegex:a.oPreviousSearch.bRegex,bSmart:a.oPreviousSearch.bSmart})});
b.bind("keypress.DT",function(d){if(d.keyCode==13)return false});return c}function N(a,b,c){Oa(a,b.sSearch,c,b.bRegex,b.bSmart);for(b=0;b<a.aoPreSearchCols.length;b++)Pa(a,a.aoPreSearchCols[b].sSearch,b,a.aoPreSearchCols[b].bRegex,a.aoPreSearchCols[b].bSmart);n.afnFiltering.length!==0&&Qa(a);a.bFiltered=true;i(a.oInstance).trigger("filter",a);a._iDisplayStart=0;E(a);C(a);oa(a,0)}function Qa(a){for(var b=n.afnFiltering,c=0,d=b.length;c<d;c++)for(var f=0,e=0,h=a.aiDisplay.length;e<h;e++){var j=a.aiDisplay[e-
f];if(!b[c](a,fa(a,j,"filter"),j)){a.aiDisplay.splice(e-f,1);f++}}}function Pa(a,b,c,d,f){if(b!==""){var e=0;b=pa(b,d,f);for(d=a.aiDisplay.length-1;d>=0;d--){f=qa(G(a,a.aiDisplay[d],c,"filter"),a.aoColumns[c].sType);if(!b.test(f)){a.aiDisplay.splice(d,1);e++}}}}function Oa(a,b,c,d,f){var e=pa(b,d,f);if(typeof c=="undefined"||c===null)c=0;if(n.afnFiltering.length!==0)c=1;if(b.length<=0){a.aiDisplay.splice(0,a.aiDisplay.length);a.aiDisplay=a.aiDisplayMaster.slice()}else if(a.aiDisplay.length==a.aiDisplayMaster.length||
a.oPreviousSearch.sSearch.length>b.length||c==1||b.indexOf(a.oPreviousSearch.sSearch)!==0){a.aiDisplay.splice(0,a.aiDisplay.length);oa(a,1);for(c=0;c<a.aiDisplayMaster.length;c++)e.test(a.asDataSearch[c])&&a.aiDisplay.push(a.aiDisplayMaster[c])}else{var h=0;for(c=0;c<a.asDataSearch.length;c++)if(!e.test(a.asDataSearch[c])){a.aiDisplay.splice(c-h,1);h++}}a.oPreviousSearch.sSearch=b;a.oPreviousSearch.bRegex=d;a.oPreviousSearch.bSmart=f}function oa(a,b){if(!a.oFeatures.bServerSide){a.asDataSearch.splice(0,
a.asDataSearch.length);b=typeof b!="undefined"&&b==1?a.aiDisplayMaster:a.aiDisplay;for(var c=0,d=b.length;c<d;c++)a.asDataSearch[c]=ra(a,fa(a,b[c],"filter"))}}function ra(a,b){var c="";if(typeof a.__nTmpFilter=="undefined")a.__nTmpFilter=p.createElement("div");for(var d=a.__nTmpFilter,f=0,e=a.aoColumns.length;f<e;f++)if(a.aoColumns[f].bSearchable)c+=qa(b[f],a.aoColumns[f].sType)+" ";if(c.indexOf("&")!==-1){d.innerHTML=c;c=d.textContent?d.textContent:d.innerText;c=c.replace(/\n/g," ").replace(/\r/g,
"")}return c}function pa(a,b,c){if(c){a=b?a.split(" "):sa(a).split(" ");a="^(?=.*?"+a.join(")(?=.*?")+").*$";return new RegExp(a,"i")}else{a=b?a:sa(a);return new RegExp(a,"i")}}function qa(a,b){if(typeof n.ofnSearch[b]=="function")return n.ofnSearch[b](a);else if(b=="html")return a.replace(/\n/g," ").replace(/<.*?>/g,"");else if(typeof a=="string")return a.replace(/\n/g," ");else if(a===null)return"";return a}function R(a,b){var c,d,f,e,h=[],j=[],k=n.oSort;d=a.aoData;var m=a.aoColumns;if(!a.oFeatures.bServerSide&&
(a.aaSorting.length!==0||a.aaSortingFixed!==null)){h=a.aaSortingFixed!==null?a.aaSortingFixed.concat(a.aaSorting):a.aaSorting.slice();for(c=0;c<h.length;c++){var u=h[c][0];f=ta(a,u);e=a.aoColumns[u].sSortDataType;if(typeof n.afnSortData[e]!="undefined"){var r=n.afnSortData[e](a,u,f);f=0;for(e=d.length;f<e;f++)O(a,f,u,r[f])}}c=0;for(d=a.aiDisplayMaster.length;c<d;c++)j[a.aiDisplayMaster[c]]=c;var H=h.length;a.aiDisplayMaster.sort(function(L,T){var B,U;for(c=0;c<H;c++){B=m[h[c][0]].iDataSort;U=m[B].sType;
B=k[(U?U:"string")+"-"+h[c][1]](G(a,L,B,"sort"),G(a,T,B,"sort"));if(B!==0)return B}return k["numeric-asc"](j[L],j[T])})}if((typeof b=="undefined"||b)&&!a.oFeatures.bDeferRender)V(a);a.bSorted=true;i(a.oInstance).trigger("sort",a);if(a.oFeatures.bFilter)N(a,a.oPreviousSearch,1);else{a.aiDisplay=a.aiDisplayMaster.slice();a._iDisplayStart=0;E(a);C(a)}}function ja(a,b,c,d){i(b).bind("click.DT",function(f){if(a.aoColumns[c].bSortable!==false){var e=function(){var h,j;if(f.shiftKey){for(var k=false,m=0;m<
a.aaSorting.length;m++)if(a.aaSorting[m][0]==c){k=true;h=a.aaSorting[m][0];j=a.aaSorting[m][2]+1;if(typeof a.aoColumns[h].asSorting[j]=="undefined")a.aaSorting.splice(m,1);else{a.aaSorting[m][1]=a.aoColumns[h].asSorting[j];a.aaSorting[m][2]=j}break}k===false&&a.aaSorting.push([c,a.aoColumns[c].asSorting[0],0])}else if(a.aaSorting.length==1&&a.aaSorting[0][0]==c){h=a.aaSorting[0][0];j=a.aaSorting[0][2]+1;if(typeof a.aoColumns[h].asSorting[j]=="undefined")j=0;a.aaSorting[0][1]=a.aoColumns[h].asSorting[j];
a.aaSorting[0][2]=j}else{a.aaSorting.splice(0,a.aaSorting.length);a.aaSorting.push([c,a.aoColumns[c].asSorting[0],0])}R(a)};if(a.oFeatures.bProcessing){K(a,true);setTimeout(function(){e();a.oFeatures.bServerSide||K(a,false)},0)}else e();typeof d=="function"&&d(a)}})}function V(a){var b,c,d,f,e,h=a.aoColumns.length,j=a.oClasses;for(b=0;b<h;b++)a.aoColumns[b].bSortable&&i(a.aoColumns[b].nTh).removeClass(j.sSortAsc+" "+j.sSortDesc+" "+a.aoColumns[b].sSortingClass);f=a.aaSortingFixed!==null?a.aaSortingFixed.concat(a.aaSorting):
a.aaSorting.slice();for(b=0;b<a.aoColumns.length;b++)if(a.aoColumns[b].bSortable){e=a.aoColumns[b].sSortingClass;d=-1;for(c=0;c<f.length;c++)if(f[c][0]==b){e=f[c][1]=="asc"?j.sSortAsc:j.sSortDesc;d=c;break}i(a.aoColumns[b].nTh).addClass(e);if(a.bJUI){c=i("span",a.aoColumns[b].nTh);c.removeClass(j.sSortJUIAsc+" "+j.sSortJUIDesc+" "+j.sSortJUI+" "+j.sSortJUIAscAllowed+" "+j.sSortJUIDescAllowed);c.addClass(d==-1?a.aoColumns[b].sSortingClassJUI:f[d][1]=="asc"?j.sSortJUIAsc:j.sSortJUIDesc)}}else i(a.aoColumns[b].nTh).addClass(a.aoColumns[b].sSortingClass);
e=j.sSortColumn;if(a.oFeatures.bSort&&a.oFeatures.bSortClasses){d=Q(a);if(a.oFeatures.bDeferRender)i(d).removeClass(e+"1 "+e+"2 "+e+"3");else if(d.length>=h)for(b=0;b<h;b++)if(d[b].className.indexOf(e+"1")!=-1){c=0;for(a=d.length/h;c<a;c++)d[h*c+b].className=i.trim(d[h*c+b].className.replace(e+"1",""))}else if(d[b].className.indexOf(e+"2")!=-1){c=0;for(a=d.length/h;c<a;c++)d[h*c+b].className=i.trim(d[h*c+b].className.replace(e+"2",""))}else if(d[b].className.indexOf(e+"3")!=-1){c=0;for(a=d.length/
h;c<a;c++)d[h*c+b].className=i.trim(d[h*c+b].className.replace(" "+e+"3",""))}j=1;var k;for(b=0;b<f.length;b++){k=parseInt(f[b][0],10);c=0;for(a=d.length/h;c<a;c++)d[h*c+k].className+=" "+e+j;j<3&&j++}}}function La(a){if(a.oScroll.bInfinite)return null;var b=p.createElement("div");b.className=a.oClasses.sPaging+a.sPaginationType;n.oPagination[a.sPaginationType].fnInit(a,b,function(c){E(c);C(c)});typeof a.aanFeatures.p=="undefined"&&a.aoDrawCallback.push({fn:function(c){n.oPagination[c.sPaginationType].fnUpdate(c,
function(d){E(d);C(d)})},sName:"pagination"});return b}function ma(a,b){var c=a._iDisplayStart;if(b=="first")a._iDisplayStart=0;else if(b=="previous"){a._iDisplayStart=a._iDisplayLength>=0?a._iDisplayStart-a._iDisplayLength:0;if(a._iDisplayStart<0)a._iDisplayStart=0}else if(b=="next")if(a._iDisplayLength>=0){if(a._iDisplayStart+a._iDisplayLength<a.fnRecordsDisplay())a._iDisplayStart+=a._iDisplayLength}else a._iDisplayStart=0;else if(b=="last")if(a._iDisplayLength>=0){b=parseInt((a.fnRecordsDisplay()-
1)/a._iDisplayLength,10)+1;a._iDisplayStart=(b-1)*a._iDisplayLength}else a._iDisplayStart=0;else J(a,0,"Unknown paging action: "+b);i(a.oInstance).trigger("page",a);return c!=a._iDisplayStart}function Ka(a){var b=p.createElement("div");b.className=a.oClasses.sInfo;if(typeof a.aanFeatures.i=="undefined"){a.aoDrawCallback.push({fn:Ra,sName:"information"});a.sTableId!==""&&b.setAttribute("id",a.sTableId+"_info")}return b}function Ra(a){if(!(!a.oFeatures.bInfo||a.aanFeatures.i.length===0)){var b=a._iDisplayStart+
1,c=a.fnDisplayEnd(),d=a.fnRecordsTotal(),f=a.fnRecordsDisplay(),e=a.fnFormatNumber(b),h=a.fnFormatNumber(c),j=a.fnFormatNumber(d),k=a.fnFormatNumber(f);if(a.oScroll.bInfinite)e=a.fnFormatNumber(1);e=a.fnRecordsDisplay()===0&&a.fnRecordsDisplay()==a.fnRecordsTotal()?a.oLanguage.sInfoEmpty+a.oLanguage.sInfoPostFix:a.fnRecordsDisplay()===0?a.oLanguage.sInfoEmpty+" "+a.oLanguage.sInfoFiltered.replace("_MAX_",j)+a.oLanguage.sInfoPostFix:a.fnRecordsDisplay()==a.fnRecordsTotal()?a.oLanguage.sInfo.replace("_START_",
e).replace("_END_",h).replace("_TOTAL_",k)+a.oLanguage.sInfoPostFix:a.oLanguage.sInfo.replace("_START_",e).replace("_END_",h).replace("_TOTAL_",k)+" "+a.oLanguage.sInfoFiltered.replace("_MAX_",a.fnFormatNumber(a.fnRecordsTotal()))+a.oLanguage.sInfoPostFix;if(a.oLanguage.fnInfoCallback!==null)e=a.oLanguage.fnInfoCallback(a,b,c,d,f,e);a=a.aanFeatures.i;b=0;for(c=a.length;b<c;b++)i(a[b]).html(e)}}function Ga(a){if(a.oScroll.bInfinite)return null;var b='<select size="1" '+(a.sTableId===""?"":'name="'+
a.sTableId+'_length"')+">",c,d;if(a.aLengthMenu.length==2&&typeof a.aLengthMenu[0]=="object"&&typeof a.aLengthMenu[1]=="object"){c=0;for(d=a.aLengthMenu[0].length;c<d;c++)b+='<option value="'+a.aLengthMenu[0][c]+'">'+a.aLengthMenu[1][c]+"</option>"}else{c=0;for(d=a.aLengthMenu.length;c<d;c++)b+='<option value="'+a.aLengthMenu[c]+'">'+a.aLengthMenu[c]+"</option>"}b+="</select>";var f=p.createElement("div");a.sTableId!==""&&typeof a.aanFeatures.l=="undefined"&&f.setAttribute("id",a.sTableId+"_length");
f.className=a.oClasses.sLength;f.innerHTML="<label>"+a.oLanguage.sLengthMenu.replace("_MENU_",b)+"</label>";i('select option[value="'+a._iDisplayLength+'"]',f).attr("selected",true);i("select",f).bind("change.DT",function(){var e=i(this).val(),h=a.aanFeatures.l;c=0;for(d=h.length;c<d;c++)h[c]!=this.parentNode&&i("select",h[c]).val(e);a._iDisplayLength=parseInt(e,10);E(a);if(a.fnDisplayEnd()==a.fnRecordsDisplay()){a._iDisplayStart=a.fnDisplayEnd()-a._iDisplayLength;if(a._iDisplayStart<0)a._iDisplayStart=
0}if(a._iDisplayLength==-1)a._iDisplayStart=0;C(a)});return f}function Ia(a){var b=p.createElement("div");a.sTableId!==""&&typeof a.aanFeatures.r=="undefined"&&b.setAttribute("id",a.sTableId+"_processing");b.innerHTML=a.oLanguage.sProcessing;b.className=a.oClasses.sProcessing;a.nTable.parentNode.insertBefore(b,a.nTable);return b}function K(a,b){if(a.oFeatures.bProcessing){a=a.aanFeatures.r;for(var c=0,d=a.length;c<d;c++)a[c].style.visibility=b?"visible":"hidden"}}function Na(a,b){for(var c=-1,d=0;d<
a.aoColumns.length;d++){a.aoColumns[d].bVisible===true&&c++;if(c==b)return d}return null}function ta(a,b){for(var c=-1,d=0;d<a.aoColumns.length;d++){a.aoColumns[d].bVisible===true&&c++;if(d==b)return a.aoColumns[d].bVisible===true?c:null}return null}function W(a,b){var c,d;c=a._iDisplayStart;for(d=a._iDisplayEnd;c<d;c++)if(a.aoData[a.aiDisplay[c]].nTr==b)return a.aiDisplay[c];c=0;for(d=a.aoData.length;c<d;c++)if(a.aoData[c].nTr==b)return c;return null}function Z(a){for(var b=0,c=0;c<a.aoColumns.length;c++)a.aoColumns[c].bVisible===
true&&b++;return b}function E(a){a._iDisplayEnd=a.oFeatures.bPaginate===false?a.aiDisplay.length:a._iDisplayStart+a._iDisplayLength>a.aiDisplay.length||a._iDisplayLength==-1?a.aiDisplay.length:a._iDisplayStart+a._iDisplayLength}function Sa(a,b){if(!a||a===null||a==="")return 0;if(typeof b=="undefined")b=p.getElementsByTagName("body")[0];var c=p.createElement("div");c.style.width=q(a);b.appendChild(c);a=c.offsetWidth;b.removeChild(c);return a}function ga(a){var b=0,c,d=0,f=a.aoColumns.length,e,h=i("th",
a.nTHead);for(e=0;e<f;e++)if(a.aoColumns[e].bVisible){d++;if(a.aoColumns[e].sWidth!==null){c=Sa(a.aoColumns[e].sWidthOrig,a.nTable.parentNode);if(c!==null)a.aoColumns[e].sWidth=q(c);b++}}if(f==h.length&&b===0&&d==f&&a.oScroll.sX===""&&a.oScroll.sY==="")for(e=0;e<a.aoColumns.length;e++){c=i(h[e]).width();if(c!==null)a.aoColumns[e].sWidth=q(c)}else{b=a.nTable.cloneNode(false);e=a.nTHead.cloneNode(true);d=p.createElement("tbody");c=p.createElement("tr");b.removeAttribute("id");b.appendChild(e);if(a.nTFoot!==
null){b.appendChild(a.nTFoot.cloneNode(true));P(function(k){k.style.width=""},b.getElementsByTagName("tr"))}b.appendChild(d);d.appendChild(c);d=i("thead th",b);if(d.length===0)d=i("tbody tr:eq(0)>td",b);h=S(a,e);for(e=d=0;e<f;e++){var j=a.aoColumns[e];if(j.bVisible&&j.sWidthOrig!==null&&j.sWidthOrig!=="")h[e-d].style.width=q(j.sWidthOrig);else if(j.bVisible)h[e-d].style.width="";else d++}for(e=0;e<f;e++)if(a.aoColumns[e].bVisible){d=Ta(a,e);if(d!==null){d=d.cloneNode(true);if(a.aoColumns[e].sContentPadding!==
"")d.innerHTML+=a.aoColumns[e].sContentPadding;c.appendChild(d)}}f=a.nTable.parentNode;f.appendChild(b);if(a.oScroll.sX!==""&&a.oScroll.sXInner!=="")b.style.width=q(a.oScroll.sXInner);else if(a.oScroll.sX!==""){b.style.width="";if(i(b).width()<f.offsetWidth)b.style.width=q(f.offsetWidth)}else if(a.oScroll.sY!=="")b.style.width=q(f.offsetWidth);b.style.visibility="hidden";Ua(a,b);f=i("tbody tr:eq(0)",b).children();if(f.length===0)f=S(a,i("thead",b)[0]);if(a.oScroll.sX!==""){for(e=d=c=0;e<a.aoColumns.length;e++)if(a.aoColumns[e].bVisible){c+=
a.aoColumns[e].sWidthOrig===null?i(f[d]).outerWidth():parseInt(a.aoColumns[e].sWidth.replace("px",""),10)+(i(f[d]).outerWidth()-i(f[d]).width());d++}b.style.width=q(c);a.nTable.style.width=q(c)}for(e=d=0;e<a.aoColumns.length;e++)if(a.aoColumns[e].bVisible){c=i(f[d]).width();if(c!==null&&c>0)a.aoColumns[e].sWidth=q(c);d++}a.nTable.style.width=q(i(b).outerWidth());b.parentNode.removeChild(b)}}function Ua(a,b){if(a.oScroll.sX===""&&a.oScroll.sY!==""){i(b).width();b.style.width=q(i(b).outerWidth()-a.oScroll.iBarWidth)}else if(a.oScroll.sX!==
"")b.style.width=q(i(b).outerWidth())}function Ta(a,b){var c=Va(a,b);if(c<0)return null;if(a.aoData[c].nTr===null){var d=p.createElement("td");d.innerHTML=G(a,c,b,"");return d}return Q(a,c)[b]}function Va(a,b){for(var c=-1,d=-1,f=0;f<a.aoData.length;f++){var e=G(a,f,b,"display")+"";e=e.replace(/<.*?>/g,"");if(e.length>c){c=e.length;d=f}}return d}function q(a){if(a===null)return"0px";if(typeof a=="number"){if(a<0)return"0px";return a+"px"}var b=a.charCodeAt(a.length-1);if(b<48||b>57)return a;return a+
"px"}function Za(a,b){if(a.length!=b.length)return 1;for(var c=0;c<a.length;c++)if(a[c]!=b[c])return 2;return 0}function ia(a){for(var b=n.aTypes,c=b.length,d=0;d<c;d++){var f=b[d](a);if(f!==null)return f}return"string"}function A(a){for(var b=0;b<D.length;b++)if(D[b].nTable==a)return D[b];return null}function ca(a){for(var b=[],c=a.aoData.length,d=0;d<c;d++)b.push(a.aoData[d]._aData);return b}function ba(a){for(var b=[],c=0,d=a.aoData.length;c<d;c++)a.aoData[c].nTr!==null&&b.push(a.aoData[c].nTr);
return b}function Q(a,b){var c=[],d,f,e,h,j;f=0;var k=a.aoData.length;if(typeof b!="undefined"){f=b;k=b+1}for(f=f;f<k;f++){j=a.aoData[f];if(j.nTr!==null){b=[];e=0;for(h=j.nTr.childNodes.length;e<h;e++){d=j.nTr.childNodes[e].nodeName.toLowerCase();if(d=="td"||d=="th")b.push(j.nTr.childNodes[e])}e=d=0;for(h=a.aoColumns.length;e<h;e++)if(a.aoColumns[e].bVisible)c.push(b[e-d]);else{c.push(j._anHidden[e]);d++}}}return c}function sa(a){return a.replace(new RegExp("(\\/|\\.|\\*|\\+|\\?|\\||\\(|\\)|\\[|\\]|\\{|\\}|\\\\|\\$|\\^)",
"g"),"\\$1")}function ua(a,b){for(var c=-1,d=0,f=a.length;d<f;d++)if(a[d]==b)c=d;else a[d]>b&&a[d]--;c!=-1&&a.splice(c,1)}function Fa(a,b){b=b.split(",");for(var c=[],d=0,f=a.aoColumns.length;d<f;d++)for(var e=0;e<f;e++)if(a.aoColumns[d].sName==b[e]){c.push(e);break}return c}function ka(a){for(var b="",c=0,d=a.aoColumns.length;c<d;c++)b+=a.aoColumns[c].sName+",";if(b.length==d)return"";return b.slice(0,-1)}function J(a,b,c){a=a.sTableId===""?"DataTables warning: "+c:"DataTables warning (table id = '"+
a.sTableId+"'): "+c;if(b===0)if(n.sErrMode=="alert")alert(a);else throw a;else typeof console!="undefined"&&typeof console.log!="undefined"&&console.log(a)}function la(a){a.aoData.splice(0,a.aoData.length);a.aiDisplayMaster.splice(0,a.aiDisplayMaster.length);a.aiDisplay.splice(0,a.aiDisplay.length);E(a)}function va(a){if(!(!a.oFeatures.bStateSave||typeof a.bDestroying!="undefined")){var b,c,d,f="{";f+='"iCreate":'+(new Date).getTime()+",";f+='"iStart":'+(a.oScroll.bInfinite?0:a._iDisplayStart)+",";
f+='"iEnd":'+(a.oScroll.bInfinite?a._iDisplayLength:a._iDisplayEnd)+",";f+='"iLength":'+a._iDisplayLength+",";f+='"sFilter":"'+encodeURIComponent(a.oPreviousSearch.sSearch)+'",';f+='"sFilterEsc":'+!a.oPreviousSearch.bRegex+",";f+='"aaSorting":[ ';for(b=0;b<a.aaSorting.length;b++)f+="["+a.aaSorting[b][0]+',"'+a.aaSorting[b][1]+'"],';f=f.substring(0,f.length-1);f+="],";f+='"aaSearchCols":[ ';for(b=0;b<a.aoPreSearchCols.length;b++)f+='["'+encodeURIComponent(a.aoPreSearchCols[b].sSearch)+'",'+!a.aoPreSearchCols[b].bRegex+
"],";f=f.substring(0,f.length-1);f+="],";f+='"abVisCols":[ ';for(b=0;b<a.aoColumns.length;b++)f+=a.aoColumns[b].bVisible+",";f=f.substring(0,f.length-1);f+="]";b=0;for(c=a.aoStateSave.length;b<c;b++){d=a.aoStateSave[b].fn(a,f);if(d!=="")f=d}f+="}";Wa(a.sCookiePrefix+a.sInstance,f,a.iCookieDuration,a.sCookiePrefix,a.fnCookieCallback)}}function Xa(a,b){if(a.oFeatures.bStateSave){var c,d,f;d=wa(a.sCookiePrefix+a.sInstance);if(d!==null&&d!==""){try{c=typeof i.parseJSON=="function"?i.parseJSON(d.replace(/'/g,
'"')):eval("("+d+")")}catch(e){return}d=0;for(f=a.aoStateLoad.length;d<f;d++)if(!a.aoStateLoad[d].fn(a,c))return;a.oLoadedState=i.extend(true,{},c);a._iDisplayStart=c.iStart;a.iInitDisplayStart=c.iStart;a._iDisplayEnd=c.iEnd;a._iDisplayLength=c.iLength;a.oPreviousSearch.sSearch=decodeURIComponent(c.sFilter);a.aaSorting=c.aaSorting.slice();a.saved_aaSorting=c.aaSorting.slice();if(typeof c.sFilterEsc!="undefined")a.oPreviousSearch.bRegex=!c.sFilterEsc;if(typeof c.aaSearchCols!="undefined")for(d=0;d<
c.aaSearchCols.length;d++)a.aoPreSearchCols[d]={sSearch:decodeURIComponent(c.aaSearchCols[d][0]),bRegex:!c.aaSearchCols[d][1]};if(typeof c.abVisCols!="undefined"){b.saved_aoColumns=[];for(d=0;d<c.abVisCols.length;d++){b.saved_aoColumns[d]={};b.saved_aoColumns[d].bVisible=c.abVisCols[d]}}}}}function Wa(a,b,c,d,f){var e=new Date;e.setTime(e.getTime()+c*1E3);c=za.location.pathname.split("/");a=a+"_"+c.pop().replace(/[\/:]/g,"").toLowerCase();var h;if(f!==null){h=typeof i.parseJSON=="function"?i.parseJSON(b):
eval("("+b+")");b=f(a,h,e.toGMTString(),c.join("/")+"/")}else b=a+"="+encodeURIComponent(b)+"; expires="+e.toGMTString()+"; path="+c.join("/")+"/";f="";e=9999999999999;if((wa(a)!==null?p.cookie.length:b.length+p.cookie.length)+10>4096){a=p.cookie.split(";");for(var j=0,k=a.length;j<k;j++)if(a[j].indexOf(d)!=-1){var m=a[j].split("=");try{h=eval("("+decodeURIComponent(m[1])+")")}catch(u){continue}if(typeof h.iCreate!="undefined"&&h.iCreate<e){f=m[0];e=h.iCreate}}if(f!=="")p.cookie=f+"=; expires=Thu, 01-Jan-1970 00:00:01 GMT; path="+
c.join("/")+"/"}p.cookie=b}function wa(a){var b=za.location.pathname.split("/");a=a+"_"+b[b.length-1].replace(/[\/:]/g,"").toLowerCase()+"=";b=p.cookie.split(";");for(var c=0;c<b.length;c++){for(var d=b[c];d.charAt(0)==" ";)d=d.substring(1,d.length);if(d.indexOf(a)===0)return decodeURIComponent(d.substring(a.length,d.length))}return null}function Y(a,b){b=i(b).children("tr");var c,d,f,e,h,j,k,m,u=function(L,T,B){for(;typeof L[T][B]!="undefined";)B++;return B};a.splice(0,a.length);d=0;for(j=b.length;d<
j;d++)a.push([]);d=0;for(j=b.length;d<j;d++){f=0;for(k=b[d].childNodes.length;f<k;f++){c=b[d].childNodes[f];if(c.nodeName.toUpperCase()=="TD"||c.nodeName.toUpperCase()=="TH"){var r=c.getAttribute("colspan")*1,H=c.getAttribute("rowspan")*1;r=!r||r===0||r===1?1:r;H=!H||H===0||H===1?1:H;m=u(a,d,0);for(h=0;h<r;h++)for(e=0;e<H;e++){a[d+e][m+h]={cell:c,unique:r==1?true:false};a[d+e].nTr=b[d]}}}}}function S(a,b,c){var d=[];if(typeof c=="undefined"){c=a.aoHeader;if(typeof b!="undefined"){c=[];Y(c,b)}}b=0;
for(var f=c.length;b<f;b++)for(var e=0,h=c[b].length;e<h;e++)if(c[b][e].unique&&(typeof d[e]=="undefined"||!a.bSortCellsTop))d[e]=c[b][e].cell;return d}function Ya(){var a=p.createElement("p"),b=a.style;b.width="100%";b.height="200px";b.padding="0px";var c=p.createElement("div");b=c.style;b.position="absolute";b.top="0px";b.left="0px";b.visibility="hidden";b.width="200px";b.height="150px";b.padding="0px";b.overflow="hidden";c.appendChild(a);p.body.appendChild(c);b=a.offsetWidth;c.style.overflow="scroll";
a=a.offsetWidth;if(b==a)a=c.clientWidth;p.body.removeChild(c);return b-a}function P(a,b,c){for(var d=0,f=b.length;d<f;d++)for(var e=0,h=b[d].childNodes.length;e<h;e++)if(b[d].childNodes[e].nodeType==1)typeof c!="undefined"?a(b[d].childNodes[e],c[d].childNodes[e]):a(b[d].childNodes[e])}function o(a,b,c,d){if(typeof d=="undefined")d=c;if(typeof b[c]!="undefined")a[d]=b[c]}function fa(a,b,c){for(var d=[],f=0,e=a.aoColumns.length;f<e;f++)d.push(G(a,b,f,c));return d}function G(a,b,c,d){var f=a.aoColumns[c];
if((c=f.fnGetData(a.aoData[b]._aData))===undefined){if(a.iDrawError!=a.iDraw&&f.sDefaultContent===null){J(a,0,"Requested unknown parameter '"+f.mDataProp+"' from the data source for row "+b);a.iDrawError=a.iDraw}return f.sDefaultContent}if(c===null&&f.sDefaultContent!==null)c=f.sDefaultContent;else if(typeof c=="function")return c();if(d=="display"&&c===null)return"";return c}function O(a,b,c,d){a.aoColumns[c].fnSetData(a.aoData[b]._aData,d)}function aa(a){if(a===null)return function(){return null};
else if(typeof a=="function")return function(c){return a(c)};else if(typeof a=="string"&&a.indexOf(".")!=-1){var b=a.split(".");return b.length==2?function(c){return c[b[0]][b[1]]}:b.length==3?function(c){return c[b[0]][b[1]][b[2]]}:function(c){for(var d=0,f=b.length;d<f;d++)c=c[b[d]];return c}}else return function(c){return c[a]}}function Ba(a){if(a===null)return function(){};else if(typeof a=="function")return function(c,d){return a(c,d)};else if(typeof a=="string"&&a.indexOf(".")!=-1){var b=a.split(".");
return b.length==2?function(c,d){c[b[0]][b[1]]=d}:b.length==3?function(c,d){c[b[0]][b[1]][b[2]]=d}:function(c,d){for(var f=0,e=b.length-1;f<e;f++)c=c[b[f]];c[b[b.length-1]]=d}}else return function(c,d){c[a]=d}}this.oApi={};this.fnDraw=function(a){var b=A(this[n.iApiIndex]);if(typeof a!="undefined"&&a===false){E(b);C(b)}else da(b)};this.fnFilter=function(a,b,c,d,f){var e=A(this[n.iApiIndex]);if(e.oFeatures.bFilter){if(typeof c=="undefined")c=false;if(typeof d=="undefined")d=true;if(typeof f=="undefined")f=
true;if(typeof b=="undefined"||b===null){N(e,{sSearch:a,bRegex:c,bSmart:d},1);if(f&&typeof e.aanFeatures.f!="undefined"){b=e.aanFeatures.f;c=0;for(d=b.length;c<d;c++)i("input",b[c]).val(a)}}else{e.aoPreSearchCols[b].sSearch=a;e.aoPreSearchCols[b].bRegex=c;e.aoPreSearchCols[b].bSmart=d;N(e,e.oPreviousSearch,1)}}};this.fnSettings=function(){return A(this[n.iApiIndex])};this.fnVersionCheck=n.fnVersionCheck;this.fnSort=function(a){var b=A(this[n.iApiIndex]);b.aaSorting=a;R(b)};this.fnSortListener=function(a,
b,c){ja(A(this[n.iApiIndex]),a,b,c)};this.fnAddData=function(a,b){if(a.length===0)return[];var c=[],d,f=A(this[n.iApiIndex]);if(typeof a[0]=="object")for(var e=0;e<a.length;e++){d=v(f,a[e]);if(d==-1)return c;c.push(d)}else{d=v(f,a);if(d==-1)return c;c.push(d)}f.aiDisplay=f.aiDisplayMaster.slice();if(typeof b=="undefined"||b)da(f);return c};this.fnDeleteRow=function(a,b,c){var d=A(this[n.iApiIndex]);a=typeof a=="object"?W(d,a):a;var f=d.aoData.splice(a,1),e=i.inArray(a,d.aiDisplay);d.asDataSearch.splice(e,
1);ua(d.aiDisplayMaster,a);ua(d.aiDisplay,a);typeof b=="function"&&b.call(this,d,f);if(d._iDisplayStart>=d.aiDisplay.length){d._iDisplayStart-=d._iDisplayLength;if(d._iDisplayStart<0)d._iDisplayStart=0}if(typeof c=="undefined"||c){E(d);C(d)}return f};this.fnClearTable=function(a){var b=A(this[n.iApiIndex]);la(b);if(typeof a=="undefined"||a)C(b)};this.fnOpen=function(a,b,c){var d=A(this[n.iApiIndex]);this.fnClose(a);var f=p.createElement("tr"),e=p.createElement("td");f.appendChild(e);e.className=c;
e.colSpan=Z(d);if(typeof b.jquery!="undefined"||typeof b=="object")e.appendChild(b);else e.innerHTML=b;b=i("tr",d.nTBody);i.inArray(a,b)!=-1&&i(f).insertAfter(a);d.aoOpenRows.push({nTr:f,nParent:a});return f};this.fnClose=function(a){for(var b=A(this[n.iApiIndex]),c=0;c<b.aoOpenRows.length;c++)if(b.aoOpenRows[c].nParent==a){(a=b.aoOpenRows[c].nTr.parentNode)&&a.removeChild(b.aoOpenRows[c].nTr);b.aoOpenRows.splice(c,1);return 0}return 1};this.fnGetData=function(a,b){var c=A(this[n.iApiIndex]);if(typeof a!=
"undefined"){a=typeof a=="object"?W(c,a):a;if(typeof b!="undefined")return G(c,a,b,"");return typeof c.aoData[a]!="undefined"?c.aoData[a]._aData:null}return ca(c)};this.fnGetNodes=function(a){var b=A(this[n.iApiIndex]);if(typeof a!="undefined")return typeof b.aoData[a]!="undefined"?b.aoData[a].nTr:null;return ba(b)};this.fnGetPosition=function(a){var b=A(this[n.iApiIndex]),c=a.nodeName.toUpperCase();if(c=="TR")return W(b,a);else if(c=="TD"||c=="TH"){c=W(b,a.parentNode);for(var d=Q(b,c),f=0;f<b.aoColumns.length;f++)if(d[f]==
a)return[c,ta(b,f),f]}return null};this.fnUpdate=function(a,b,c,d,f){var e=A(this[n.iApiIndex]);b=typeof b=="object"?W(e,b):b;if(i.isArray(a)&&typeof a=="object"){e.aoData[b]._aData=a.slice();for(c=0;c<e.aoColumns.length;c++)this.fnUpdate(G(e,b,c),b,c,false,false)}else if(a!==null&&typeof a=="object"){e.aoData[b]._aData=i.extend(true,{},a);for(c=0;c<e.aoColumns.length;c++)this.fnUpdate(G(e,b,c),b,c,false,false)}else{a=a;O(e,b,c,a);if(e.aoColumns[c].fnRender!==null){a=e.aoColumns[c].fnRender({iDataRow:b,
iDataColumn:c,aData:e.aoData[b]._aData,oSettings:e});e.aoColumns[c].bUseRendered&&O(e,b,c,a)}if(e.aoData[b].nTr!==null)Q(e,b)[c].innerHTML=a}c=i.inArray(b,e.aiDisplay);e.asDataSearch[c]=ra(e,fa(e,b,"filter"));if(typeof f=="undefined"||f)ea(e);if(typeof d=="undefined"||d)da(e);return 0};this.fnSetColumnVis=function(a,b,c){var d=A(this[n.iApiIndex]),f,e;e=d.aoColumns.length;var h,j;if(d.aoColumns[a].bVisible!=b){if(b){for(f=j=0;f<a;f++)d.aoColumns[f].bVisible&&j++;j=j>=Z(d);if(!j)for(f=a;f<e;f++)if(d.aoColumns[f].bVisible){h=
f;break}f=0;for(e=d.aoData.length;f<e;f++)if(d.aoData[f].nTr!==null)j?d.aoData[f].nTr.appendChild(d.aoData[f]._anHidden[a]):d.aoData[f].nTr.insertBefore(d.aoData[f]._anHidden[a],Q(d,f)[h])}else{f=0;for(e=d.aoData.length;f<e;f++)if(d.aoData[f].nTr!==null){h=Q(d,f)[a];d.aoData[f]._anHidden[a]=h;h.parentNode.removeChild(h)}}d.aoColumns[a].bVisible=b;M(d,d.aoHeader);d.nTFoot&&M(d,d.aoFooter);f=0;for(e=d.aoOpenRows.length;f<e;f++)d.aoOpenRows[f].nTr.colSpan=Z(d);if(typeof c=="undefined"||c){ea(d);C(d)}va(d)}};
this.fnPageChange=function(a,b){var c=A(this[n.iApiIndex]);ma(c,a);E(c);if(typeof b=="undefined"||b)C(c)};this.fnDestroy=function(){var a=A(this[n.iApiIndex]),b=a.nTableWrapper.parentNode,c=a.nTBody,d,f;a.bDestroying=true;d=0;for(f=a.aoDestroyCallback.length;d<f;d++)a.aoDestroyCallback[d].fn();d=0;for(f=a.aoColumns.length;d<f;d++)a.aoColumns[d].bVisible===false&&this.fnSetColumnVis(d,true);i(a.nTableWrapper).find("*").andSelf().unbind(".DT");i("tbody>tr>td."+a.oClasses.sRowEmpty,a.nTable).parent().remove();
if(a.nTable!=a.nTHead.parentNode){i(a.nTable).children("thead").remove();a.nTable.appendChild(a.nTHead)}if(a.nTFoot&&a.nTable!=a.nTFoot.parentNode){i(a.nTable).children("tfoot").remove();a.nTable.appendChild(a.nTFoot)}a.nTable.parentNode.removeChild(a.nTable);i(a.nTableWrapper).remove();a.aaSorting=[];a.aaSortingFixed=[];V(a);i(ba(a)).removeClass(a.asStripeClasses.join(" "));if(a.bJUI){i("th",a.nTHead).removeClass([n.oStdClasses.sSortable,n.oJUIClasses.sSortableAsc,n.oJUIClasses.sSortableDesc,n.oJUIClasses.sSortableNone].join(" "));
i("th span."+n.oJUIClasses.sSortIcon,a.nTHead).remove();i("th",a.nTHead).each(function(){var e=i("div."+n.oJUIClasses.sSortJUIWrapper,this),h=e.contents();i(this).append(h);e.remove()})}else i("th",a.nTHead).removeClass([n.oStdClasses.sSortable,n.oStdClasses.sSortableAsc,n.oStdClasses.sSortableDesc,n.oStdClasses.sSortableNone].join(" "));a.nTableReinsertBefore?b.insertBefore(a.nTable,a.nTableReinsertBefore):b.appendChild(a.nTable);d=0;for(f=a.aoData.length;d<f;d++)a.aoData[d].nTr!==null&&c.appendChild(a.aoData[d].nTr);
if(a.oFeatures.bAutoWidth===true)a.nTable.style.width=q(a.sDestroyWidth);i(c).children("tr:even").addClass(a.asDestroyStripes[0]);i(c).children("tr:odd").addClass(a.asDestroyStripes[1]);d=0;for(f=D.length;d<f;d++)D[d]==a&&D.splice(d,1);a=null};this.fnAdjustColumnSizing=function(a){var b=A(this[n.iApiIndex]);ea(b);if(typeof a=="undefined"||a)this.fnDraw(false);else if(b.oScroll.sX!==""||b.oScroll.sY!=="")this.oApi._fnScrollDraw(b)};for(var xa in n.oApi)if(xa)this[xa]=s(xa);this.oApi._fnExternApiFunc=
s;this.oApi._fnInitialise=t;this.oApi._fnInitComplete=w;this.oApi._fnLanguageProcess=y;this.oApi._fnAddColumn=F;this.oApi._fnColumnOptions=x;this.oApi._fnAddData=v;this.oApi._fnCreateTr=z;this.oApi._fnGatherData=$;this.oApi._fnBuildHead=X;this.oApi._fnDrawHead=M;this.oApi._fnDraw=C;this.oApi._fnReDraw=da;this.oApi._fnAjaxUpdate=Ca;this.oApi._fnAjaxParameters=Da;this.oApi._fnAjaxUpdateDraw=Ea;this.oApi._fnServerParams=ha;this.oApi._fnAddOptionsHtml=Aa;this.oApi._fnFeatureHtmlTable=Ja;this.oApi._fnScrollDraw=
Ma;this.oApi._fnAdjustColumnSizing=ea;this.oApi._fnFeatureHtmlFilter=Ha;this.oApi._fnFilterComplete=N;this.oApi._fnFilterCustom=Qa;this.oApi._fnFilterColumn=Pa;this.oApi._fnFilter=Oa;this.oApi._fnBuildSearchArray=oa;this.oApi._fnBuildSearchRow=ra;this.oApi._fnFilterCreateSearch=pa;this.oApi._fnDataToSearch=qa;this.oApi._fnSort=R;this.oApi._fnSortAttachListener=ja;this.oApi._fnSortingClasses=V;this.oApi._fnFeatureHtmlPaginate=La;this.oApi._fnPageChange=ma;this.oApi._fnFeatureHtmlInfo=Ka;this.oApi._fnUpdateInfo=
Ra;this.oApi._fnFeatureHtmlLength=Ga;this.oApi._fnFeatureHtmlProcessing=Ia;this.oApi._fnProcessingDisplay=K;this.oApi._fnVisibleToColumnIndex=Na;this.oApi._fnColumnIndexToVisible=ta;this.oApi._fnNodeToDataIndex=W;this.oApi._fnVisbleColumns=Z;this.oApi._fnCalculateEnd=E;this.oApi._fnConvertToWidth=Sa;this.oApi._fnCalculateColumnWidths=ga;this.oApi._fnScrollingWidthAdjust=Ua;this.oApi._fnGetWidestNode=Ta;this.oApi._fnGetMaxLenString=Va;this.oApi._fnStringToCss=q;this.oApi._fnArrayCmp=Za;this.oApi._fnDetectType=
ia;this.oApi._fnSettingsFromNode=A;this.oApi._fnGetDataMaster=ca;this.oApi._fnGetTrNodes=ba;this.oApi._fnGetTdNodes=Q;this.oApi._fnEscapeRegex=sa;this.oApi._fnDeleteIndex=ua;this.oApi._fnReOrderIndex=Fa;this.oApi._fnColumnOrdering=ka;this.oApi._fnLog=J;this.oApi._fnClearTable=la;this.oApi._fnSaveState=va;this.oApi._fnLoadState=Xa;this.oApi._fnCreateCookie=Wa;this.oApi._fnReadCookie=wa;this.oApi._fnDetectHeader=Y;this.oApi._fnGetUniqueThs=S;this.oApi._fnScrollBarWidth=Ya;this.oApi._fnApplyToChildren=
P;this.oApi._fnMap=o;this.oApi._fnGetRowData=fa;this.oApi._fnGetCellData=G;this.oApi._fnSetCellData=O;this.oApi._fnGetObjectDataFn=aa;this.oApi._fnSetObjectDataFn=Ba;var ya=this;return this.each(function(){var a=0,b,c,d,f;a=0;for(b=D.length;a<b;a++){if(D[a].nTable==this)if(typeof g=="undefined"||typeof g.bRetrieve!="undefined"&&g.bRetrieve===true)return D[a].oInstance;else if(typeof g.bDestroy!="undefined"&&g.bDestroy===true){D[a].oInstance.fnDestroy();break}else{J(D[a],0,"Cannot reinitialise DataTable.\n\nTo retrieve the DataTables object for this table, please pass either no arguments to the dataTable() function, or set bRetrieve to true. Alternatively, to destory the old table and create a new one, set bDestroy to true (note that a lot of changes to the configuration can be made through the API which is usually much faster).");
return}if(D[a].sTableId!==""&&D[a].sTableId==this.getAttribute("id")){D.splice(a,1);break}}var e=new l;D.push(e);var h=false,j=false;a=this.getAttribute("id");if(a!==null){e.sTableId=a;e.sInstance=a}else e.sInstance=n._oExternConfig.iNextUnique++;if(this.nodeName.toLowerCase()!="table")J(e,0,"Attempted to initialise DataTables on a node which is not a table: "+this.nodeName);else{e.nTable=this;e.oInstance=ya.length==1?ya:i(this).dataTable();e.oApi=ya.oApi;e.sDestroyWidth=i(this).width();if(typeof g!=
"undefined"&&g!==null){e.oInit=g;o(e.oFeatures,g,"bPaginate");o(e.oFeatures,g,"bLengthChange");o(e.oFeatures,g,"bFilter");o(e.oFeatures,g,"bSort");o(e.oFeatures,g,"bInfo");o(e.oFeatures,g,"bProcessing");o(e.oFeatures,g,"bAutoWidth");o(e.oFeatures,g,"bSortClasses");o(e.oFeatures,g,"bServerSide");o(e.oFeatures,g,"bDeferRender");o(e.oScroll,g,"sScrollX","sX");o(e.oScroll,g,"sScrollXInner","sXInner");o(e.oScroll,g,"sScrollY","sY");o(e.oScroll,g,"bScrollCollapse","bCollapse");o(e.oScroll,g,"bScrollInfinite",
"bInfinite");o(e.oScroll,g,"iScrollLoadGap","iLoadGap");o(e.oScroll,g,"bScrollAutoCss","bAutoCss");o(e,g,"asStripClasses","asStripeClasses");o(e,g,"asStripeClasses");o(e,g,"fnPreDrawCallback");o(e,g,"fnRowCallback");o(e,g,"fnHeaderCallback");o(e,g,"fnFooterCallback");o(e,g,"fnCookieCallback");o(e,g,"fnInitComplete");o(e,g,"fnServerData");o(e,g,"fnFormatNumber");o(e,g,"aaSorting");o(e,g,"aaSortingFixed");o(e,g,"aLengthMenu");o(e,g,"sPaginationType");o(e,g,"sAjaxSource");o(e,g,"sAjaxDataProp");o(e,
g,"iCookieDuration");o(e,g,"sCookiePrefix");o(e,g,"sDom");o(e,g,"bSortCellsTop");o(e,g,"oSearch","oPreviousSearch");o(e,g,"aoSearchCols","aoPreSearchCols");o(e,g,"iDisplayLength","_iDisplayLength");o(e,g,"bJQueryUI","bJUI");o(e.oLanguage,g,"fnInfoCallback");typeof g.fnDrawCallback=="function"&&e.aoDrawCallback.push({fn:g.fnDrawCallback,sName:"user"});typeof g.fnServerParams=="function"&&e.aoServerParams.push({fn:g.fnServerParams,sName:"user"});typeof g.fnStateSaveCallback=="function"&&e.aoStateSave.push({fn:g.fnStateSaveCallback,
sName:"user"});typeof g.fnStateLoadCallback=="function"&&e.aoStateLoad.push({fn:g.fnStateLoadCallback,sName:"user"});if(e.oFeatures.bServerSide&&e.oFeatures.bSort&&e.oFeatures.bSortClasses)e.aoDrawCallback.push({fn:V,sName:"server_side_sort_classes"});else e.oFeatures.bDeferRender&&e.aoDrawCallback.push({fn:V,sName:"defer_sort_classes"});if(typeof g.bJQueryUI!="undefined"&&g.bJQueryUI){e.oClasses=n.oJUIClasses;if(typeof g.sDom=="undefined")e.sDom='<"H"lfr>t<"F"ip>'}if(e.oScroll.sX!==""||e.oScroll.sY!==
"")e.oScroll.iBarWidth=Ya();if(typeof g.iDisplayStart!="undefined"&&typeof e.iInitDisplayStart=="undefined"){e.iInitDisplayStart=g.iDisplayStart;e._iDisplayStart=g.iDisplayStart}if(typeof g.bStateSave!="undefined"){e.oFeatures.bStateSave=g.bStateSave;Xa(e,g);e.aoDrawCallback.push({fn:va,sName:"state_save"})}if(typeof g.iDeferLoading!="undefined"){e.bDeferLoading=true;e._iRecordsTotal=g.iDeferLoading;e._iRecordsDisplay=g.iDeferLoading}if(typeof g.aaData!="undefined")j=true;if(typeof g!="undefined"&&
typeof g.aoData!="undefined")g.aoColumns=g.aoData;if(typeof g.oLanguage!="undefined")if(typeof g.oLanguage.sUrl!="undefined"&&g.oLanguage.sUrl!==""){e.oLanguage.sUrl=g.oLanguage.sUrl;i.getJSON(e.oLanguage.sUrl,null,function(u){y(e,u,true)});h=true}else y(e,g.oLanguage,false)}else g={};if(typeof g.asStripClasses=="undefined"&&typeof g.asStripeClasses=="undefined"){e.asStripeClasses.push(e.oClasses.sStripeOdd);e.asStripeClasses.push(e.oClasses.sStripeEven)}c=false;d=i(this).children("tbody").children("tr");
a=0;for(b=e.asStripeClasses.length;a<b;a++)if(d.filter(":lt(2)").hasClass(e.asStripeClasses[a])){c=true;break}if(c){e.asDestroyStripes=["",""];if(i(d[0]).hasClass(e.oClasses.sStripeOdd))e.asDestroyStripes[0]+=e.oClasses.sStripeOdd+" ";if(i(d[0]).hasClass(e.oClasses.sStripeEven))e.asDestroyStripes[0]+=e.oClasses.sStripeEven;if(i(d[1]).hasClass(e.oClasses.sStripeOdd))e.asDestroyStripes[1]+=e.oClasses.sStripeOdd+" ";if(i(d[1]).hasClass(e.oClasses.sStripeEven))e.asDestroyStripes[1]+=e.oClasses.sStripeEven;
d.removeClass(e.asStripeClasses.join(" "))}c=[];var k;a=this.getElementsByTagName("thead");if(a.length!==0){Y(e.aoHeader,a[0]);c=S(e)}if(typeof g.aoColumns=="undefined"){k=[];a=0;for(b=c.length;a<b;a++)k.push(null)}else k=g.aoColumns;a=0;for(b=k.length;a<b;a++){if(typeof g.saved_aoColumns!="undefined"&&g.saved_aoColumns.length==b){if(k[a]===null)k[a]={};k[a].bVisible=g.saved_aoColumns[a].bVisible}F(e,c?c[a]:null)}if(typeof g.aoColumnDefs!="undefined")for(a=g.aoColumnDefs.length-1;a>=0;a--){var m=
g.aoColumnDefs[a].aTargets;i.isArray(m)||J(e,1,"aTargets must be an array of targets, not a "+typeof m);c=0;for(d=m.length;c<d;c++)if(typeof m[c]=="number"&&m[c]>=0){for(;e.aoColumns.length<=m[c];)F(e);x(e,m[c],g.aoColumnDefs[a])}else if(typeof m[c]=="number"&&m[c]<0)x(e,e.aoColumns.length+m[c],g.aoColumnDefs[a]);else if(typeof m[c]=="string"){b=0;for(f=e.aoColumns.length;b<f;b++)if(m[c]=="_all"||i(e.aoColumns[b].nTh).hasClass(m[c]))x(e,b,g.aoColumnDefs[a])}}if(typeof k!="undefined"){a=0;for(b=k.length;a<
b;a++)x(e,a,k[a])}a=0;for(b=e.aaSorting.length;a<b;a++){if(e.aaSorting[a][0]>=e.aoColumns.length)e.aaSorting[a][0]=0;k=e.aoColumns[e.aaSorting[a][0]];if(typeof e.aaSorting[a][2]=="undefined")e.aaSorting[a][2]=0;if(typeof g.aaSorting=="undefined"&&typeof e.saved_aaSorting=="undefined")e.aaSorting[a][1]=k.asSorting[0];c=0;for(d=k.asSorting.length;c<d;c++)if(e.aaSorting[a][1]==k.asSorting[c]){e.aaSorting[a][2]=c;break}}V(e);a=i(this).children("thead");if(a.length===0){a=[p.createElement("thead")];this.appendChild(a[0])}e.nTHead=
a[0];a=i(this).children("tbody");if(a.length===0){a=[p.createElement("tbody")];this.appendChild(a[0])}e.nTBody=a[0];a=i(this).children("tfoot");if(a.length>0){e.nTFoot=a[0];Y(e.aoFooter,e.nTFoot)}if(j)for(a=0;a<g.aaData.length;a++)v(e,g.aaData[a]);else $(e);e.aiDisplay=e.aiDisplayMaster.slice();e.bInitialised=true;h===false&&t(e)}})}})(jQuery,window,document);

View File

@ -0,0 +1,550 @@
/*
* File: demo_table.css
* CVS: $Id$
* Description: CSS descriptions for DataTables demo pages
* Author: Allan Jardine
* Created: Tue May 12 06:47:22 BST 2009
* Modified: $Date$ by $Author$
* Language: CSS
* Project: DataTables
*
* Copyright 2009 Allan Jardine. All Rights Reserved.
*
* ***************************************************************************
* DESCRIPTION
*
* The styles given here are suitable for the demos that are used with the standard DataTables
* distribution (see www.datatables.net). You will most likely wish to modify these styles to
* meet the layout requirements of your site.
*
* Common issues:
* 'full_numbers' pagination - I use an extra selector on the body tag to ensure that there is
* no conflict between the two pagination types. If you want to use full_numbers pagination
* ensure that you either have "example_alt_pagination" as a body class name, or better yet,
* modify that selector.
* Note that the path used for Images is relative. All images are by default located in
* ../images/ - relative to this CSS file.
*/
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* DataTables features
*/
.dataTables_wrapper {
position: relative;
min-height: 302px;
clear: both;
_height: 302px;
zoom: 1; /* Feeling sorry for IE */
}
.dataTables_processing {
position: absolute;
top: 50%;
left: 50%;
width: 250px;
height: 30px;
margin-left: -125px;
margin-top: -15px;
padding: 14px 0 2px 0;
border: 1px solid #ddd;
text-align: center;
color: #999;
font-size: 14px;
background-color: white;
}
.dataTables_length {
width: 40%;
float: left;
}
.dataTables_filter {
width: 50%;
float: right;
text-align: right;
margin-bottom: 10px;
}
.dataTables_info {
width: 40%;
height: 30px;
float: left;
}
.dataTables_paginate {
width: 44px;
* width: 50px;
float: right;
text-align: right;
}
/* Pagination nested */
.paginate_disabled_previous, .paginate_enabled_previous, .paginate_disabled_next, .paginate_enabled_next {
height: 19px;
width: 19px;
margin-left: 3px;
float: left;
}
.paginate_disabled_previous {
background-image: url('/images/datatables/back_disabled.jpg');
}
.paginate_enabled_previous {
background-image: url('/images/datatables/back_enabled.jpg');
}
.paginate_disabled_next {
background-image: url('/images/datatables/forward_disabled.jpg');
}
.paginate_enabled_next {
background-image: url('/images/datatables/forward_enabled.jpg');
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* DataTables display
*/
table.datatable.display {
margin: 0 auto;
clear: both;
width: 100%;
/* Note Firefox 3.5 and before have a bug with border-collapse
* ( https://bugzilla.mozilla.org/show%5Fbug.cgi?id=155955 )
* border-spacing: 0; is one possible option. Conditional-css.com is
* useful for this kind of thing
*
* Further note IE 6/7 has problems when calculating widths with border width.
* It subtracts one px relative to the other browsers from the first column, and
* adds one to the end...
*
* If you want that effect I'd suggest setting a border-top/left on th/td's and
* then filling in the gaps with other borders.
*/
}
table.datatable.display thead th {
padding: 3px 18px 3px 10px;
border-bottom: 1px solid black;
font-weight: bold;
cursor: pointer;
* cursor: hand;
}
table.datatable.display tfoot th {
padding: 3px 18px 3px 10px;
border-top: 1px solid black;
font-weight: bold;
}
table.datatable.display tr.heading2 td {
border-bottom: 1px solid #aaa;
}
table.datatable.display td {
padding: 3px 10px;
}
table.datatable.display td.center {
text-align: center;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* DataTables sorting
*/
.datatable .sorting_asc {
background: url('/images/datatables/sort_asc.png') no-repeat center right #EAEAEA;
}
.datatable .sorting_desc {
background: url('/images/datatables/sort_desc.png') no-repeat center right #EAEAEA;
}
.datatable .sorting {
background: url('/images/datatables/sort_both.png') no-repeat center right #EAEAEA;
}
.datatable .sorting_asc_disabled {
background: url('/images/datatables/sort_asc_disabled.png') no-repeat center right #EAEAEA;
}
.datatable .sorting_desc_disabled {
background: url('/images/datatables/sort_desc_disabled.png') no-repeat center right #EAEAEA;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* DataTables row classes
*/
table.datatable.display tr.odd.gradeA {
background-color: #ddffdd;
}
table.datatable.display tr.even.gradeA {
background-color: #eeffee;
}
table.datatable.display tr.odd.gradeC {
background-color: #ddddff;
}
table.datatable.display tr.even.gradeC {
background-color: #eeeeff;
}
table.datatable.display tr.odd.gradeX {
background-color: #ffdddd;
}
table.datatable.display tr.even.gradeX {
background-color: #ffeeee;
}
table.datatable.display tr.odd.gradeU {
background-color: #ddd;
}
table.datatable.display tr.even.gradeU {
background-color: #eee;
}
/*
tr.odd {
background-color: #E2E4FF;
}
tr.even {
background-color: white;
}
*/
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Misc
*/
.dataTables_scroll {
clear: both;
}
.dataTables_scrollBody {
*margin-top: -1px;
}
.top, .bottom {
padding: 15px;
background-color: #F5F5F5;
border: 1px solid #CCCCCC;
}
.top .dataTables_info {
float: none;
}
.clear {
clear: both;
}
.dataTables_empty {
text-align: center;
}
tfoot input {
margin: 0.5em 0;
width: 100%;
color: #444;
}
tfoot input.search_init {
color: #999;
}
td.group {
background-color: #d1cfd0;
border-bottom: 2px solid #A19B9E;
border-top: 2px solid #A19B9E;
}
td.details {
background-color: #d1cfd0;
border: 2px solid #A19B9E;
}
.example_alt_pagination div.dataTables_info {
width: 40%;
}
.paging_full_numbers {
width: 400px;
height: 22px;
line-height: 22px;
}
.paging_full_numbers span.paginate_button,
.paging_full_numbers span.paginate_active {
border-radius: 2px 2px 2px 2px;
background-color: #F4F4F4;
background-image: url("/stylesheets/web-app-theme/themes/default/images/button-background.png");
border: 1px solid #C3C4BA;
color: #111111;
margin-right: 5px;
min-width: 15px;
padding: 6px 10px;
text-align: center;
cursor: pointer;
}
.paging_full_numbers span.paginate_button:hover{
border: 1px solid #818171;
box-shadow: 0 0 4px rgba(0, 0, 0, 0.3);
}
.paging_full_numbers span.paginate_button_disabled:hover{
border: 1px solid #C3C4BA;
box-shadow: 0 0 0;
}
.paging_full_numbers span.paginate_active {
background: none repeat scroll 0 0 #261F1F;
border: 1px solid #261F1F;
color: white;
}
table.display tr.even.row_selected td {
background-color: #B0BED9;
}
table.display tr.odd.row_selected td {
background-color: #9FAFD1;
}
/*
* Sorting classes for columns
*/
/* For the standard odd/even */
/*
tr.odd td.sorting_1 {
background-color: #D3D6FF;
}
tr.odd td.sorting_2 {
background-color: #DADCFF;
}
tr.odd td.sorting_3 {
background-color: #E0E2FF;
}
tr.even td.sorting_1 {
background-color: #EAEBFF;
}
tr.even td.sorting_2 {
background-color: #F2F3FF;
}
tr.even td.sorting_3 {
background-color: #F9F9FF;
}
*/
/* For the Conditional-CSS grading rows */
/*
Colour calculations (based off the main row colours)
Level 1:
dd > c4
ee > d5
Level 2:
dd > d1
ee > e2
*/
tr.odd.gradeA td.sorting_1 {
background-color: #c4ffc4;
}
tr.odd.gradeA td.sorting_2 {
background-color: #d1ffd1;
}
tr.odd.gradeA td.sorting_3 {
background-color: #d1ffd1;
}
tr.even.gradeA td.sorting_1 {
background-color: #d5ffd5;
}
tr.even.gradeA td.sorting_2 {
background-color: #e2ffe2;
}
tr.even.gradeA td.sorting_3 {
background-color: #e2ffe2;
}
tr.odd.gradeC td.sorting_1 {
background-color: #c4c4ff;
}
tr.odd.gradeC td.sorting_2 {
background-color: #d1d1ff;
}
tr.odd.gradeC td.sorting_3 {
background-color: #d1d1ff;
}
tr.even.gradeC td.sorting_1 {
background-color: #d5d5ff;
}
tr.even.gradeC td.sorting_2 {
background-color: #e2e2ff;
}
tr.even.gradeC td.sorting_3 {
background-color: #e2e2ff;
}
tr.odd.gradeX td.sorting_1 {
background-color: #ffc4c4;
}
tr.odd.gradeX td.sorting_2 {
background-color: #ffd1d1;
}
tr.odd.gradeX td.sorting_3 {
background-color: #ffd1d1;
}
tr.even.gradeX td.sorting_1 {
background-color: #ffd5d5;
}
tr.even.gradeX td.sorting_2 {
background-color: #ffe2e2;
}
tr.even.gradeX td.sorting_3 {
background-color: #ffe2e2;
}
tr.odd.gradeU td.sorting_1 {
background-color: #c4c4c4;
}
tr.odd.gradeU td.sorting_2 {
background-color: #d1d1d1;
}
tr.odd.gradeU td.sorting_3 {
background-color: #d1d1d1;
}
tr.even.gradeU td.sorting_1 {
background-color: #d5d5d5;
}
tr.even.gradeU td.sorting_2 {
background-color: #e2e2e2;
}
tr.even.gradeU td.sorting_3 {
background-color: #e2e2e2;
}
/*
* Row highlighting example
*/
.ex_highlight #example tbody tr.even:hover, #example tbody tr.even td.highlighted {
background-color: #ECFFB3;
}
.ex_highlight #example tbody tr.odd:hover, #example tbody tr.odd td.highlighted {
background-color: #E6FF99;
}
.ex_highlight_row #example tr.even:hover {
background-color: #ECFFB3;
}
.ex_highlight_row #example tr.even:hover td.sorting_1 {
background-color: #DDFF75;
}
.ex_highlight_row #example tr.even:hover td.sorting_2 {
background-color: #E7FF9E;
}
.ex_highlight_row #example tr.even:hover td.sorting_3 {
background-color: #E2FF89;
}
.ex_highlight_row #example tr.odd:hover {
background-color: #E6FF99;
}
.ex_highlight_row #example tr.odd:hover td.sorting_1 {
background-color: #D6FF5C;
}
.ex_highlight_row #example tr.odd:hover td.sorting_2 {
background-color: #E0FF84;
}
.ex_highlight_row #example tr.odd:hover td.sorting_3 {
background-color: #DBFF70;
}
/*
* KeyTable
*/
table.KeyTable td {
border: 3px solid transparent;
}
table.KeyTable td.focus {
border: 3px solid #3366FF;
}
table.display tr.gradeA {
background-color: #eeffee;
}
table.display tr.gradeC {
background-color: #ddddff;
}
table.display tr.gradeX {
background-color: #ffdddd;
}
table.display tr.gradeU {
background-color: #ddd;
}
div.box {
height: 100px;
padding: 10px;
overflow: auto;
border: 1px solid #8080FF;
background-color: #E5E5FF;
}

View File

@ -0,0 +1,4 @@
ul.list li .item {
margin-left: 180px;
}

View File

@ -26,7 +26,48 @@ describe BuildListsController do
end
end
shared_examples_for 'create build list' do
it 'should be able to perform new action' do
get :new, :project_id => @project.id
response.should render_template(:new)
end
it 'should be able to perform create action' do
post :create, {:project_id => @project.id}.merge(@create_params)
response.should redirect_to(@project)
end
end
shared_examples_for 'not create build list' do
it 'should not be able to perform new action' do
get :new, :project_id => @project.id
response.should redirect_to(forbidden_url)
end
it 'should not be able to perform create action' do
post :create, {:project_id => @project.id}.merge(@create_params)
response.should redirect_to(forbidden_url)
end
end
before { stub_rsync_methods }
context 'crud' do
before(:each) do
platform = Factory(:platform_with_repos)
@create_params = {
:build_list => {
:project_version => 'v1.0',
:pl_id => platform.id,
:update_type => 'security',
:include_repos => [platform.repositories.first.id]
},
:arches => [Factory(:arch).id],
:bpls => [platform.id]
}
any_instance_of(Project, :versions => ['v1.0', 'v2.0'])
end
context 'for guest' do
it 'should not be able to perform index action' do
get :index
@ -36,7 +77,6 @@ describe BuildListsController do
context 'for user' do
before(:each) do
stub_rsync_methods
@build_list = Factory(:build_list_core)
@project = @build_list.project
@owner_user = @project.owner
@ -47,7 +87,6 @@ describe BuildListsController do
@user = Factory(:user)
set_session_for(@user)
@show_params = {:project_id => @project.id, :id => @build_list.id}
end
context 'for all build lists' do
@ -55,9 +94,8 @@ describe BuildListsController do
@build_list1 = Factory(:build_list_core)
@build_list2 = Factory(:build_list_core, :project => Factory(:project, :visibility => 'hidden'))
@build_list3 = Factory(:build_list_core, :project => Factory(:project, :owner => @user, :visibility => 'hidden'))
b = Factory(:build_list_core, :project => Factory(:project, :visibility => 'hidden'))
b.project.relations.create :role => 'reader', :object_id => @user.id, :object_type => 'User'
@build_list4 = b
@build_list4 = Factory(:build_list_core, :project => Factory(:project, :visibility => 'hidden'))
@build_list4.project.relations.create :role => 'reader', :object_id => @user.id, :object_type => 'User'
end
it 'should be able to perform index action' do
@ -76,15 +114,18 @@ describe BuildListsController do
context 'for open project' do
it_should_behave_like 'show build list'
it_should_behave_like 'not create build list'
context 'if user is project owner' do
before(:each) {set_session_for(@owner_user)}
it_should_behave_like 'show build list'
it_should_behave_like 'create build list'
end
context 'if user is project member' do
context 'if user is project read member' do
before(:each) {set_session_for(@member_user)}
it_should_behave_like 'show build list'
it_should_behave_like 'not create build list'
end
end
@ -95,23 +136,24 @@ describe BuildListsController do
end
it_should_behave_like 'not show build list'
it_should_behave_like 'not create build list'
context 'if user is project owner' do
before(:each) {set_session_for(@owner_user)}
it_should_behave_like 'show build list'
it_should_behave_like 'create build list'
end
context 'if user is project member' do
context 'if user is project read member' do
before(:each) {set_session_for(@member_user)}
it_should_behave_like 'show build list'
it_should_behave_like 'not create build list'
end
end
end
context 'for group' do
before(:each) do
stub_rsync_methods
@owner_group = Factory(:group)
@owner_user = Factory(:user)
@owner_group.objects.create :role => 'reader', :object_id => @owner_user.id, :object_type => 'User'
@ -137,9 +179,8 @@ describe BuildListsController do
@build_list1 = Factory(:build_list_core)
@build_list2 = Factory(:build_list_core, :project => Factory(:project, :visibility => 'hidden'))
@build_list3 = Factory(:build_list_core, :project => Factory(:project, :owner => @group, :visibility => 'hidden'))
b = Factory(:build_list_core, :project => Factory(:project, :visibility => 'hidden'))
b.project.relations.create :role => 'reader', :object_id => @group.id, :object_type => 'Group'
@build_list4 = b
@build_list4 = Factory(:build_list_core, :project => Factory(:project, :visibility => 'hidden'))
@build_list4.project.relations.create :role => 'reader', :object_id => @group.id, :object_type => 'Group'
end
it 'should be able to perform index action' do
@ -158,15 +199,18 @@ describe BuildListsController do
context 'for open project' do
it_should_behave_like 'show build list'
it_should_behave_like 'not create build list'
context 'if user is group owner' do
before(:each) {set_session_for(@owner_user)}
it_should_behave_like 'show build list'
it_should_behave_like 'create build list'
end
context 'if user is group member' do
context 'if user is group read member' do
before(:each) {set_session_for(@member_user)}
it_should_behave_like 'show build list'
it_should_behave_like 'not create build list'
end
end
@ -177,15 +221,18 @@ describe BuildListsController do
end
it_should_behave_like 'not show build list'
it_should_behave_like 'not create build list'
context 'if user is group owner' do
before(:each) {set_session_for(@owner_user)}
it_should_behave_like 'show build list'
it_should_behave_like 'create build list'
end
context 'if user is group member' do
context 'if user is group read member' do
before(:each) {set_session_for(@member_user)}
it_should_behave_like 'show build list'
it_should_behave_like 'not create build list'
end
end
@ -210,7 +257,6 @@ describe BuildListsController do
context 'filter' do
before(:each) do
stub_rsync_methods
set_session_for Factory(:admin)
@build_list1 = Factory(:build_list_core)
@ -250,5 +296,99 @@ describe BuildListsController do
end
context 'callbacks' do
let(:build_list) { Factory(:build_list_core) }
describe 'publish_build' do
def do_get(status)
get :publish_build, :id => build_list.bs_id, :status => status
build_list.reload
end
it { do_get(BuildServer::SUCCESS); response.should be_ok }
it { lambda{ do_get(BuildServer::SUCCESS) }.should change(build_list, :status).to(BuildList::BUILD_PUBLISHED) }
it { lambda{ do_get(BuildServer::ERROR) }.should change(build_list, :status).to(BuildList::FAILED_PUBLISH) }
it { lambda{ do_get(BuildServer::ERROR) }.should change(build_list, :notified_at) }
end
describe 'status_build' do
before { @item = build_list.items.create(:name => build_list.project.name, :version => build_list.project_version, :level => 0) }
def do_get
get :status_build, :id => build_list.bs_id, :package_name => build_list.project.name, :status => BuildServer::SUCCESS, :container_path => '/path/to'
build_list.reload
@item.reload
end
it { do_get; response.should be_ok }
it { lambda{ do_get }.should change(@item, :status) }
it { lambda{ do_get }.should change(build_list, :container_path) }
it { lambda{ do_get }.should change(build_list, :notified_at) }
end
describe 'pre_build' do
def do_get
get :pre_build, :id => build_list.bs_id
build_list.reload
end
it { do_get; response.should be_ok }
it { lambda{ do_get }.should change(build_list, :status).to(BuildServer::BUILD_STARTED) }
it { lambda{ do_get }.should change(build_list, :notified_at) }
end
describe 'post_build' do
def do_get(status)
get :post_build, :id => build_list.bs_id, :status => status, :container_path => '/path/to'
build_list.reload
end
it { do_get(BuildServer::SUCCESS); response.should be_ok }
it { lambda{ do_get(BuildServer::SUCCESS) }.should change(build_list, :container_path) }
it { lambda{ do_get(BuildServer::SUCCESS) }.should change(build_list, :notified_at) }
context 'with auto_publish' do
it { lambda{ do_get(BuildServer::SUCCESS) }.should change(build_list, :status).to(BuildList::BUILD_PUBLISH) }
it { lambda{ do_get(BuildServer::ERROR) }.should change(build_list, :status).to(BuildServer::ERROR) }
end
context 'without auto_publish' do
before { build_list.update_attribute(:auto_publish, false) }
it { lambda{ do_get(BuildServer::SUCCESS) }.should change(build_list, :status).to(BuildServer::SUCCESS) }
it { lambda{ do_get(BuildServer::ERROR) }.should change(build_list, :status).to(BuildServer::ERROR) }
end
end
describe 'circle_build' do
def do_get
get :circle_build, :id => build_list.bs_id, :container_path => '/path/to'
build_list.reload
end
it { do_get; response.should be_ok }
it { lambda{ do_get }.should change(build_list, :is_circle).to(true) }
it { lambda{ do_get }.should change(build_list, :container_path) }
it { lambda{ do_get }.should change(build_list, :notified_at) }
end
describe 'new_bbdt' do
before { @items = build_list.items }
def do_get
get :new_bbdt, :id => 123, :web_id => build_list.id, :name => build_list.project.name, :is_circular => 1,
:additional_repos => ActiveSupport::JSON.encode([{:name => 'build_repos'}, {:name => 'main'}]),
:items => ActiveSupport::JSON.encode(0 => [{:name => build_list.project.name, :version => build_list.project_version}])
build_list.reload
@items.reload
end
it { do_get; response.should be_ok }
it { lambda{ do_get }.should change(build_list, :name).to(build_list.project.name) }
it { lambda{ do_get }.should change(build_list, :additional_repos) }
it { lambda{ do_get }.should change(@items, :first) }
it { lambda{ do_get }.should change(build_list, :is_circle).to(true) }
it { lambda{ do_get }.should change(build_list, :bs_id).to(123) }
it { lambda{ do_get }.should change(build_list, :notified_at) }
end
end
end

View File

@ -0,0 +1,148 @@
require 'spec_helper'
shared_examples_for 'user with create comment rights' do
it 'should be able to perform create action' do
post :create, @create_params
response.should redirect_to(project_issue_path(@project, @issue))
end
it 'should create subscribe object into db' do
lambda{ post :create, @create_params }.should change{ Comment.count }.by(1)
end
end
shared_examples_for 'user with update own comment rights' do
it 'should be able to perform update action' do
put :update, {:id => @own_comment.id}.merge(@update_params)
response.should redirect_to([@project, @issue])
end
it 'should update subscribe body' do
put :update, {:id => @own_comment.id}.merge(@update_params)
@own_comment.reload.body.should == 'updated'
end
end
shared_examples_for 'user with update stranger comment rights' do
it 'should be able to perform update action' do
put :update, {:id => @comment.id}.merge(@update_params)
response.should redirect_to([@project, @issue])
end
it 'should update issue title' do
put :update, {:id => @comment.id}.merge(@update_params)
@comment.reload.body.should == 'updated'
end
end
shared_examples_for 'user without update stranger comment rights' do
it 'should not be able to perform update action' do
put :update, {:id => @comment.id}.merge(@update_params)
response.should redirect_to(forbidden_path)
end
it 'should not update issue title' do
put :update, {:id => @comment.id}.merge(@update_params)
@comment.reload.body.should_not == 'updated'
end
end
shared_examples_for 'user without destroy comment rights' do
it 'should not be able to perform destroy action' do
delete :destroy, :id => @comment.id, :issue_id => @issue.id, :project_id => @project.id
response.should redirect_to(forbidden_path)
end
it 'should not reduce comments count' do
lambda{ delete :destroy, :id => @comment.id, :issue_id => @issue.id, :project_id => @project.id }.should change{ Issue.count }.by(0)
end
end
#shared_examples_for 'user with destroy rights' do
# it 'should be able to perform destroy action' do
# delete :destroy, :id => @comment.id, :issue_id => @issue.id, :project_id => @project.id
# response.should redirect_to([@project, @issue])
# end
#
# it 'should reduce comments count' do
# lambda{ delete :destroy, :id => @comment.id, :issue_id => @issue.id, :project_id => @project.id }.should change{ Comment.count }.by(-1)
# end
#end
describe CommentsController do
before(:each) do
stub_rsync_methods
@project = Factory(:project)
@issue = Factory(:issue, :project_id => @project.id)
@comment = Factory(:comment, :commentable => @issue)
@create_params = {:comment => {:body => 'I am a comment!'}, :project_id => @project.id, :issue_id => @issue.id}
@update_params = {:comment => {:body => 'updated'}, :project_id => @project.id, :issue_id => @issue.id}
any_instance_of(Project, :versions => ['v1.0', 'v2.0'])
@request.env['HTTP_REFERER'] = project_issue_path(@project, @issue)
end
context 'for project admin user' do
before(:each) do
@user = Factory(:user)
set_session_for(@user)
@project.relations.create!(:object_type => 'User', :object_id => @user.id, :role => 'admin')
@own_comment = Factory(:comment, :commentable => @issue, :user => @user)
end
it_should_behave_like 'user with create comment rights'
it_should_behave_like 'user with update stranger comment rights'
it_should_behave_like 'user with update own comment rights'
it_should_behave_like 'user without destroy comment rights'
end
context 'for project owner user' do
before(:each) do
@user = Factory(:user)
set_session_for(@user)
@project.update_attribute(:owner, @user)
@project.relations.create!(:object_type => 'User', :object_id => @user.id, :role => 'admin')
@own_comment = Factory(:comment, :commentable => @issue, :user => @user)
end
it_should_behave_like 'user with create comment rights'
it_should_behave_like 'user with update stranger comment rights'
it_should_behave_like 'user with update own comment rights'
it_should_behave_like 'user without destroy comment rights'
end
context 'for project reader user' do
before(:each) do
@user = Factory(:user)
set_session_for(@user)
@project.relations.create!(:object_type => 'User', :object_id => @user.id, :role => 'reader')
@own_comment = Factory(:comment, :commentable => @issue, :user => @user)
end
it_should_behave_like 'user with create comment rights'
it_should_behave_like 'user without update stranger comment rights'
it_should_behave_like 'user with update own comment rights'
it_should_behave_like 'user without destroy comment rights'
end
context 'for project writer user' do
before(:each) do
@user = Factory(:user)
set_session_for(@user)
@project.relations.create!(:object_type => 'User', :object_id => @user.id, :role => 'writer')
@own_comment = Factory(:comment, :commentable => @issue, :user => @user)
end
it_should_behave_like 'user with create comment rights'
it_should_behave_like 'user without update stranger comment rights'
it_should_behave_like 'user with update own comment rights'
it_should_behave_like 'user without destroy comment rights'
end
end

View File

@ -0,0 +1,187 @@
require 'spec_helper'
shared_examples_for 'issue user with project reader rights' do
it 'should be able to perform index action' do
get :index, :project_id => @project.id
response.should render_template(:index)
end
it 'should be able to perform show action' do
get :show, :project_id => @project.id, :id => @issue.serial_id
response.should render_template(:show)
end
end
shared_examples_for 'issue user with project writer rights' do
it 'should be able to perform create action' do
post :create, @create_params
response.should redirect_to(project_issues_path(@project))
end
it 'should create issue object into db' do
lambda{ post :create, @create_params }.should change{ Issue.count }.by(1)
end
end
shared_examples_for 'user with issue update rights' do
it 'should be able to perform update action' do
put :update, {:id => @issue.serial_id}.merge(@update_params)
response.should redirect_to([@project, @issue])
end
it 'should update issue title' do
put :update, {:id => @issue.serial_id}.merge(@update_params)
@issue.reload.title.should == 'issue2'
end
end
shared_examples_for 'user without issue update rights' do
it 'should not be able to perform update action' do
put :update, {:id => @issue.serial_id}.merge(@update_params)
response.should redirect_to(forbidden_path)
end
it 'should not update issue title' do
put :update, {:id => @issue.serial_id}.merge(@update_params)
@issue.reload.title.should_not == 'issue2'
end
end
shared_examples_for 'user without issue destroy rights' do
it 'should not be able to perform destroy action' do
delete :destroy, :id => @issue.serial_id, :project_id => @project.id
response.should redirect_to(forbidden_path)
end
it 'should not reduce issues count' do
lambda{ delete :destroy, :id => @issue.serial_id, :project_id => @project.id }.should change{ Issue.count }.by(0)
end
end
shared_examples_for 'project with issues turned off' do
pending 'should not be able to perform index action' do
get :index, :project_id => @project_with_turned_off_issues.id
response.should render_template(:index)
end
it 'should not be able to perform show action' do
get :show, :project_id => @project_with_turned_off_issues.id, :id => @turned_of_issue.serial_id
response.should redirect_to(forbidden_path)
end
end
describe IssuesController do
before(:each) do
stub_rsync_methods
@project = Factory(:project)
@issue_user = Factory(:user)
any_instance_of(Project, :versions => ['v1.0', 'v2.0'])
@issue = Factory(:issue, :project_id => @project.id, :user_id => @issue_user.id)
@create_params = {
:project_id => @project.id,
:issue => {
:title => "issue1",
:body => "issue body",
:project_id => @project.id
},
:user_id => @issue_user.id,
:user_uname => @issue_user.uname
}
@update_params = {
:project_id => @project.id,
:issue => {
:title => "issue2"
}
}
@project_with_turned_off_issues = Factory(:project, :has_issues => false)
@turned_of_issue = Factory(:issue, :project_id => @project_with_turned_off_issues.id, :user_id => @issue_user.id)
end
context 'for global admin user' do
before(:each) do
@admin = Factory(:admin)
set_session_for(@admin)
end
it_should_behave_like 'user without issue destroy rights'
end
context 'for project admin user' do
before(:each) do
@user = Factory(:user)
set_session_for(@user)
@project.relations.create!(:object_type => 'User', :object_id => @user.id, :role => 'admin')
end
it_should_behave_like 'issue user with project reader rights'
it_should_behave_like 'issue user with project writer rights'
it_should_behave_like 'user with issue update rights'
it_should_behave_like 'user without issue destroy rights'
it_should_behave_like 'project with issues turned off'
end
context 'for project owner user' do
before(:each) do
@user = Factory(:user)
set_session_for(@user)
@project.update_attribute(:owner, @user)
@project.relations.create!(:object_type => 'User', :object_id => @user.id, :role => 'admin')
end
it_should_behave_like 'issue user with project reader rights'
it_should_behave_like 'issue user with project writer rights'
it_should_behave_like 'user with issue update rights'
it_should_behave_like 'user without issue destroy rights'
it_should_behave_like 'project with issues turned off'
end
context 'for project reader user' do
before(:each) do
@user = Factory(:user)
set_session_for(@user)
@project.relations.create!(:object_type => 'User', :object_id => @user.id, :role => 'reader')
end
it_should_behave_like 'issue user with project reader rights'
it_should_behave_like 'user without issue update rights'
it_should_behave_like 'user without issue destroy rights'
it_should_behave_like 'project with issues turned off'
it 'should not be able to perform create action' do
post :create, @create_params
response.should redirect_to(forbidden_path)
end
it 'should not create issue object into db' do
lambda{ post :create, @create_params }.should change{ Issue.count }.by(0)
end
end
context 'for project writer user' do
before(:each) do
@user = Factory(:user)
set_session_for(@user)
@project.relations.create!(:object_type => 'User', :object_id => @user.id, :role => 'writer')
end
it_should_behave_like 'issue user with project reader rights'
it_should_behave_like 'issue user with project writer rights'
it_should_behave_like 'user without issue update rights'
it_should_behave_like 'user without issue destroy rights'
it_should_behave_like 'project with issues turned off'
end
context 'for issue assign user' do
before(:each) do
set_session_for(@issue_user)
end
it_should_behave_like 'user with issue update rights'
it_should_behave_like 'user without issue destroy rights'
it_should_behave_like 'project with issues turned off'
end
end

View File

@ -61,8 +61,7 @@ describe ProductsController do
before(:each) do
@user = Factory(:user)
set_session_for(@user)
r = @product.relations.create!(:object_type => 'User', :object_id => @user.id, :role => 'admin')
r = @platform.relations.create!(:object_type => 'User', :object_id => @user.id, :role => 'admin')
@platform.relations.create!(:object_type => 'User', :object_id => @user.id, :role => 'admin')
end
it 'should be able to perform create action' do

View File

@ -8,17 +8,6 @@ describe ProjectsController do
@another_user = Factory(:user)
@create_params = {:project => {:name => 'pro'}}
@update_params = {:project => {:name => 'pro2'}}
platform = Factory(:platform)
@process_build_params = {:build => {
:arches => {Factory(:arch).id => '1'},
:project_version => 'v1.0',
:bpl => {platform.id => '1'},
:pl => platform.id,
:update_type => 'security'
}}
any_instance_of(Project, :versions => ['v1.0', 'v2.0'])
end
context 'for guest' do
@ -39,7 +28,7 @@ describe ProjectsController do
set_session_for(@admin)
end
it_should_behave_like 'projects user with writer rights'
it_should_behave_like 'projects user with admin rights'
it_should_behave_like 'projects user with reader rights'
it 'should be able to perform create action' do
@ -60,7 +49,7 @@ describe ProjectsController do
@project.relations.create!(:object_type => 'User', :object_id => @user.id, :role => 'admin')
end
it_should_behave_like 'projects user with writer rights'
it_should_behave_like 'projects user with admin rights'
it_should_behave_like 'user with rights to view projects'
it 'should be able to perform destroy action' do
@ -101,7 +90,6 @@ describe ProjectsController do
@project.relations.create!(:object_type => 'User', :object_id => @user.id, :role => 'writer')
end
it_should_behave_like 'projects user with writer rights'
it_should_behave_like 'projects user with reader rights'
end

View File

@ -0,0 +1,109 @@
require 'spec_helper'
shared_examples_for 'can subscribe' do
it 'should be able to perform create action' do
post :create, @create_params
response.should redirect_to(project_issue_path(@project, @issue))
end
it 'should create subscribe object into db' do
lambda{ post :create, @create_params }.should change{ Subscribe.count }.by(1)
end
end
shared_examples_for 'can not subscribe' do
it 'should not be able to perform create action' do
post :create, @create_params
response.should redirect_to(forbidden_path)
end
it 'should not create subscribe object into db' do
lambda{ post :create, @create_params }.should change{ Subscribe.count }.by(0)
end
end
shared_examples_for 'can unsubscribe' do
it 'should be able to perform destroy action' do
delete :destroy, @destroy_params
response.should redirect_to([@project, @issue])
end
it 'should reduce subscribes count' do
lambda{ delete :destroy, @destroy_params }.should change{ Subscribe.count }.by(-1)
end
end
shared_examples_for 'can not unsubscribe' do
it 'should not be able to perform destroy action' do
delete :destroy, @destroy_params
response.should redirect_to(forbidden_path)
end
it 'should not reduce subscribes count' do
lambda{ delete :destroy, @destroy_params }.should change{ Subscribe.count }.by(0)
end
end
describe SubscribesController do
before(:each) do
stub_rsync_methods
@project = Factory(:project)
@issue = Factory(:issue, :project_id => @project.id)
@create_params = {:issue_id => @issue.serial_id, :project_id => @project.id}
@destroy_params = {:issue_id => @issue.serial_id, :project_id => @project.id}
any_instance_of(Project, :versions => ['v1.0', 'v2.0'])
@request.env['HTTP_REFERER'] = project_issue_path(@project, @issue)
end
context 'for global admin user' do
before(:each) do
@user = Factory(:admin)
set_session_for(@user)
@project.relations.create!(:object_type => 'User', :object_id => @user.id, :role => 'admin')
@destroy_params = @destroy_params.merge({:id => @user.id})
end
context 'subscribed' do
before(:each) do
ss = @issue.subscribes.build(:user => @user)
ss.save!
end
it_should_behave_like 'can unsubscribe'
it_should_behave_like 'can not subscribe'
end
context 'not subscribed' do
it_should_behave_like 'can subscribe'
end
end
context 'for simple user' do
before(:each) do
@user = Factory(:user)
set_session_for(@user)
@destroy_params = @destroy_params.merge({:id => @user.id})
end
context 'subscribed' do
before(:each) do
ss = @issue.subscribes.build(:user => @user)
ss.save!
end
it_should_behave_like 'can unsubscribe'
it_should_behave_like 'can not subscribe'
end
context 'not subscribed' do
it_should_behave_like 'can subscribe'
end
end
end

View File

@ -1,11 +1,13 @@
Factory.define(:build_list) do |p|
p.association :project, :factory => :project
p.association :pl, :factory => :platform
p.association :arch, :factory => :arch
p.bpl { |bl| bl.pl }
p.association :user
p.association :project
p.association :pl, :factory => :platform_with_repos
p.association :arch
p.bpl {|bl| bl.pl}
p.project_version "1.0"
p.build_requires true
p.update_type 'security'
p.include_repos {|bl| bl.pl.repositories.map(&:id)}
end
Factory.define(:build_list_core, :parent => :build_list) do |p|

View File

@ -0,0 +1,5 @@
Factory.define(:comment) do |p|
p.body { Factory.next(:string) }
p.association :user, :factory => :user
p.association :commentable, :factory => :issue
end

7
spec/factories/issues.rb Normal file
View File

@ -0,0 +1,7 @@
Factory.define(:issue) do |p|
p.title { Factory.next(:string) }
p.body { Factory.next(:string) }
p.association :project, :factory => :project
p.association :user, :factory => :user
p.status "open"
end

View File

@ -5,3 +5,7 @@ Factory.define(:platform) do |p|
p.distrib_type APP_CONFIG['distr_types'].first
p.association :owner, :factory => :user
end
Factory.define(:platform_with_repos, :parent => :platform) do |p|
p.repositories {|r| [r.association(:repository)]}
end

View File

@ -0,0 +1,4 @@
Factory.define(:subscribe) do |p|
p.association :subscribeable, :factory => :issue
p.association :user, :factory => :user
end

View File

@ -50,8 +50,8 @@ describe CanCan do
guest_create
end
it 'should be able to read open platform' do
@ability.should be_able_to(:read, open_platform)
it 'should not be able to read open platform' do
@ability.should_not be_able_to(:read, open_platform)
end
it 'should not be able to read hidden platform' do
@ -62,7 +62,7 @@ describe CanCan do
@ability.should be_able_to(:auto_build, Project)
end
[:status_build, :pre_build, :post_build, :circle_build, :new_bbdt].each do |action|
[:publish_build, :status_build, :pre_build, :post_build, :circle_build, :new_bbdt].each do |action|
it "should be able to #{ action } build list" do
@ability.should be_able_to(action, BuildList)
end
@ -78,12 +78,14 @@ describe CanCan do
user_create
end
[Platform, User, Repository].each do |model_name|
it "should not be able to create #{ model_name.to_s }" do
[Platform, Repository].each do |model_name|
it "should not be able to read #{model_name}" do
@ability.should be_able_to(:read, model_name)
end
end
it { @ability.should be_able_to(:show, User) }
it "shoud be able to read another user object" do
admin_create
@ability.should be_able_to(:read, @admin)
@ -118,6 +120,7 @@ describe CanCan do
context 'as project collaborator' do
before(:each) do
@project = Factory(:project)
@issue = Factory(:issue, :project_id => @project.id)
end
context 'with read rights' do
@ -129,21 +132,32 @@ describe CanCan do
@ability.should be_able_to(:read, @project)
end
it 'should be able to read project' do
it 'should be able to read open platform' do
@ability.should be_able_to(:read, open_platform)
end
it 'should be able to read issue' do
@ability.should be_able_to(:read, @issue)
end
end
context 'with write rights' do
context 'with writer rights' do
before(:each) do
@project.relations.create!(:object_id => @user.id, :object_type => 'User', :role => 'writer')
end
[:read, :update, :process_build, :build].each do |action|
[:read, :create, :new].each do |action|
it "should be able to #{ action } project" do
@ability.should be_able_to(action, @project)
end
end
[:new, :create].each do |action|
it "should be able to #{action} build_list" do
@build_list = Factory(:build_list, :project => @project)
@ability.should be_able_to(action, @build_list)
end
end
end
context 'with admin rights' do
@ -151,27 +165,54 @@ describe CanCan do
@project.relations.create!(:object_id => @user.id, :object_type => 'User', :role => 'admin')
end
[:read, :update, :process_build, :build].each do |action|
[:read, :update].each do |action|
it "should be able to #{ action } project" do
@ability.should be_able_to(action, @project)
end
end
[:new, :create].each do |action|
it "should be able to #{action} build_list" do
@build_list = Factory(:build_list, :project => @project)
@ability.should be_able_to(action, @build_list)
end
end
it "should be able to manage collaborators of project" do
@ability.should be_able_to(:manage_collaborators, @project)
end
[:read, :create, :new, :update, :edit].each do |action|
it "should be able to #{ action } issue" do
@ability.should be_able_to(action, @issue)
end
end
end
context 'with owner rights' do
before(:each) do
@project.update_attribute(:owner, @user)
@issue.project.reload
end
[:read, :update, :process_build, :build, :destroy].each do |action|
[:read, :update, :destroy].each do |action|
it "should be able to #{ action } project" do
@ability.should be_able_to(action, @project)
end
end
[:new, :create].each do |action|
it "should be able to #{action} build_list" do
@build_list = Factory(:build_list, :project => @project)
@ability.should be_able_to(action, @build_list)
end
end
[:read, :update, :edit].each do |action|
it "should be able to #{ action } issue" do
@ability.should be_able_to(action, @issue)
end
end
end
end
@ -186,8 +227,10 @@ describe CanCan do
@platform.update_attribute(:owner, @user)
end
it 'should be able to manage platform' do
@ability.should be_able_to(:manage, @platform)
[:read, :update, :destroy, :freeze, :unfreeze].each do |action|
it "should be able to #{action} platform" do
@ability.should be_able_to(action, @platform)
end
end
end
@ -212,11 +255,16 @@ describe CanCan do
@repository.update_attribute(:owner, @user)
end
[:manage, :add_project, :remove_project, :change_visibility, :settings].each do |action|
it "should be able to #{ action } repository" do
[:read, :update, :destroy, :add_project, :remove_project, :change_visibility, :settings].each do |action|
it "should be able to #{action} repository" do
@ability.should be_able_to(action, @repository)
end
end
it do
@repository.platform.update_attribute(:owner, @user)
@ability.should be_able_to(:create, @repository)
end
end
context 'with read rights' do
@ -233,7 +281,7 @@ describe CanCan do
context 'build list relations' do
before(:each) do
@project = Factory(:project)
@project.relations.create!(:object_id => @user.id, :object_type => 'User', :role => 'reader')
@project.relations.create!(:object_id => @user.id, :object_type => 'User', :role => 'writer')
@build_list = Factory(:build_list, :project => @project)
end

125
spec/models/comment_spec.rb Normal file
View File

@ -0,0 +1,125 @@
require 'spec_helper'
require "cancan/matchers"
def set_testable_data
@ability = Ability.new(@user)
@project = Factory(:project)
@issue = Factory(:issue, :project_id => @project.id)
@comment = Factory(:comment, :commentable => @issue, :user => @user)
@stranger_comment = Factory(:comment, :commentable => @issue, :user => @stranger)
any_instance_of(Project, :versions => ['v1.0', 'v2.0'])
end
describe Comment do
context 'for global admin user' do
before(:each) do
@user = Factory(:admin)
@stranger = Factory(:user)
set_testable_data
end
it 'should create comment' do
@ability.should be_able_to(:create, Comment.new(:commentable => @issue, :user => @user))
end
it 'should update comment' do
@ability.should be_able_to(:update, @comment)
end
it 'should update stranger comment' do
@ability.should be_able_to(:update, @stranger_comment)
end
it 'should destroy own comment' do
@ability.should be_able_to(:destroy, @comment)
end
it 'should destroy stranger comment' do
@ability.should be_able_to(:destroy, @stranger_comment)
end
end
context 'for project admin user' do
before(:each) do
@user = Factory(:user)
@stranger = Factory(:user)
set_testable_data
@project.relations.create!(:object_type => 'User', :object_id => @user.id, :role => 'admin')
end
it 'should create comment' do
@ability.should be_able_to(:create, Comment.new(:commentable => @issue, :user => @user))
end
it 'should update comment' do
@ability.should be_able_to(:update, @comment)
end
it 'should update stranger comment' do
@ability.should be_able_to(:update, @stranger_comment)
end
it 'should not destroy comment' do
@ability.should_not be_able_to(:destroy, @comment)
end
end
context 'for project owner user' do
before(:each) do
@user = Factory(:user)
@stranger = Factory(:user)
set_testable_data
@project.update_attribute(:owner, @user)
@project.relations.create!(:object_type => 'User', :object_id => @user.id, :role => 'admin')
end
it 'should create comment' do
@ability.should be_able_to(:create, Comment.new(:commentable => @issue, :user => @user))
end
it 'should update comment' do
@ability.should be_able_to(:update, @comment)
end
it 'should update stranger comment' do
@ability.should be_able_to(:update, @stranger_comment)
end
it 'should not destroy comment' do
@ability.should_not be_able_to(:destroy, @comment)
end
end
context 'for simple user' do
before(:each) do
@user = Factory(:user)
@stranger = Factory(:user)
set_testable_data
end
it 'should create comment' do
@ability.should be_able_to(:create, Comment.new(:commentable => @issue, :user => @user))
end
it 'should update comment' do
@ability.should be_able_to(:update, @comment)
end
it 'should not update stranger comment' do
@ability.should_not be_able_to(:update, @stranger_comment)
end
it 'should not destroy comment' do
@ability.should_not be_able_to(:destroy, @comment)
end
end
end

View File

@ -0,0 +1,5 @@
require 'spec_helper'
describe Issue do
pending "add some examples to (or delete) #{__FILE__}"
end

View File

@ -1,5 +1,18 @@
require 'spec_helper'
describe ProjectToRepository do
pending "add some examples to (or delete) #{__FILE__}"
before(:each) do
stub_rsync_methods
@platform = Factory(:platform)
@first_repo = Factory(:repository, :platform_id => @platform.id)
@second_repo = Factory(:repository, :platform_id => @platform.id)
@project = Factory(:project)
@first_repo.projects << @project
@first_repo.save
end
it 'should not add the same project in different repositories of same platform' do
p2r = @second_repo.project_to_repositories.build :project_id => @project.id
p2r.should_not be_valid
end
end

View File

@ -0,0 +1,77 @@
require 'spec_helper'
require "cancan/matchers"
def set_testable_data
@ability = Ability.new(@user)
@project = Factory(:project)
@issue = Factory(:issue, :project_id => @project.id)
any_instance_of(Project, :versions => ['v1.0', 'v2.0'])
end
describe Subscribe do
context 'for global admin user' do
before(:each) do
@user = Factory(:admin)
@stranger = Factory(:user)
set_testable_data
end
it 'should create subscribe' do
@ability.should be_able_to(:create, Subscribe.new(:subscribeable => @issue, :user => @user))
end
context 'destroy' do
before(:each) do
@subscribe = Factory(:subscribe, :subscribeable => @issue, :user => @user)
@stranger_subscribe = Factory(:subscribe, :subscribeable => @issue, :user => @stranger)
end
context 'own subscribe' do
it 'should destroy subscribe' do
@ability.should be_able_to(:destroy, @subscribe)
end
end
context 'stranger subscribe' do
it 'should not destroy subscribe' do
@ability.should_not be_able_to(:destroy, @stranger_subscribe)
end
end
end
end
context 'for simple user' do
before(:each) do
@user = Factory(:user)
@stranger = Factory(:user)
set_testable_data
end
it 'should create subscribe' do
@ability.should be_able_to(:create, Subscribe.new(:subscribeable => @issue, :user => @user))
end
context 'destroy' do
before(:each) do
@subscribe = Factory(:subscribe, :subscribeable => @issue, :user => @user)
@stranger_subscribe = Factory(:subscribe, :subscribeable => @issue, :user => @stranger)
end
context 'own subscribe' do
it 'should destroy subscribe' do
@ability.should be_able_to(:destroy, @subscribe)
end
end
context 'stranger subscribe' do
it 'should not destroy subscribe' do
@ability.should_not be_able_to(:destroy, @stranger_subscribe)
end
end
end
end
end

View File

@ -37,9 +37,9 @@ def stub_rsync_methods
any_instance_of(Platform, :umount_directory_for_rsync => true)
end
Delayed::Worker.delay_jobs = false # Execute all jobs realtime
# Add testing root_path
%x(rm -Rf #{Rails.root}/tmp/test_root)
%x(mkdir -p #{Rails.root}/tmp/test_root)
APP_CONFIG['root_path'] = "#{Rails.root}/tmp/test_root"

View File

@ -7,21 +7,11 @@ shared_examples_for 'projects user with reader rights' do
end
end
shared_examples_for 'projects user with writer rights' do
shared_examples_for 'projects user with admin rights' do
it 'should be able to perform update action' do
put :update, {:id => @project.id}.merge(@update_params)
response.should redirect_to(project_path(@project))
end
it 'should be able to perform build action' do
get :build, :id => @project.id
response.should render_template(:build)
end
it 'should be able to perform process_build action' do
post :process_build, {:id => @project.id}.merge(@process_build_params)
response.should redirect_to(project_path(@project))
end
end
shared_examples_for 'user with rights to view projects' do

View File

@ -0,0 +1,20 @@
Copyright (c) 2009 [name of plugin creator]
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,138 @@
## RailsDatatables
A simpler, Rails-friendly interface to using the [DataTables](http://datatables.net) jQuery library.
### Prerequisites
Make sure you have jQuery.js and jQuery.dataTables.js in /public/javascripts/ and that they're included in your layout.
### Setup
Give table a class of 'datatable' so that the Javascript knows which table to alter. NOTE: If you want to use multiple tables on a single page, include the :table_dom_id in the options hash to specify the ID table to be altered.
Add thead around the table header (These elements will associate to the columns array created below, allowing sorting).
Add tbody around the table rows (These are the elements that will be sorted and paginated.)
Activate using <%= datatable() %>, passing in the columns, how to filter them (sorting type), and any other settings (ajax source, search?, label for search, processing image)
<% columns = [{:type => 'html', :class => "first"}, {:type => 'html'}, {:type => 'html'}, {:type => nil, :class => "last"}] %>
<%= datatable(columns, {:sort_by => "[0, 'desc']", :processing => image_tag("spinner.gif") }) %>
<table id='users' class='datatable'>
<thead>
<tr>
<th>Name</th>
<th>Account Level</th>
<th>Email</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<%- @users.each do |user| -%>
<tr id="<%= dom_id(user) %>">
<td><%= user.name %></td>
<td><%= user.account.account_level.name %></td>
<td><%= user.email %></td>
<td><%= link_to "Edit", edit_system_user_path(user) %></td>
</tr>
<%- end -%>
</tbody>
</table>
### Options
#### Table Options
:sort_by - array, default column number (0 - n-1) and sort order. e.g. "[2, 'desc']". Defaults to initial order.
:search - boolean, display the search field. Defaults to true.
:search_label - string, the label for the search field. Defaults to "Search".
:processing - string, the text or image to display while processing data. Defaults to "Processing".
:persist_state - boolean, remember the sorting and page of the tables for the user. Defaults to true.
:additional_data - hash, pass along additional data, such as filter values. Default is none.
:table_dom_id - string, the ID of the table to alter. If nothing is passed, it will look for a class of 'datatable'. Necessary if you want to have multiple DataTables on a single page.
:per_page - the number of rows to show per page (renamed from display_length)
:append - functions to all at the end of the dataTable() call. Useful for [Datatables plugins](http://www.datatables.net/plug-ins/api)
:no_records_message - Message to display if no records are found, whether on load or after searching
:auto_width - Automatically adjust the width of the columns. Defaults to true.
:row_callback - a function to run on each row in the table. Inserted in to "'fnRowCallback': function( nRow, aData, iDisplayIndex ) { }". See [documentation for fnRowCallback](http://www.datatables.net/usage/callbacks) for more information.
#### Column Options
:class - string, the class to assign to the table cell. Default is none.
:type - string, the type of content in the column, for non-Ajax tables. 'html' will strip all HTML and sort on the inner value, as a string. Default is string.
:sortable - boolean, allow this column to be sorted on. Default is true.
:searchable - boolean, allow this column to be searched, for non-Ajax tables. Default is true.
#### AJAX Options
When you're working with large datasets it's not reasonable to load everything on page load. Use an :ajax_source to load just the records that are being displayed, do custom searching (DB, Solr, etc).
:ajax_source - string, for large datasets, use an ajax source to load each page on its own. For smaller datasets, just load the whole set and let datatable do the sorting
Add a datatable method on your controller to return JSON
* Return the objects to be displayed
* Return the total number of objects
* Add a method to handle sorting - DataTables returns the column that is being sorted (0 - n), so you need to know which column is which and sort on it.
### AJAX Example
#### Datatable view example - datatable.html.erb
{"sEcho": <%= params[:sEcho] || -1 %>,
"iTotalRecords": <%= @total_objects %>,
"iTotalDisplayRecords": <%= @total_object %>,
"aaData":[
<% @objects.each do |object| %>
['<%= link_to(object.user.name, user) %>',
'<%= object.description || "-" %>',
'<%= object.created_at %>'
],
<% end %>
]}
#### Controller example - using will_paginate
def datatable
@objects = current_objects(params)
@total_objectss = total_objects(params)
render :layout => false
end
private
def current_objects(params={})
current_page = (params[:iDisplayStart].to_i/params[:iDisplayLength].to_i rescue 0)+1
@current_objects = Object.paginate :page => current_page,
:include => [:user],
:order => "#{datatable_columns(params[:iSortCol_0])} #{params[:sSortDir_0] || "DESC"}",
:conditions => conditions,
:per_page => params[:iDisplayLength]
end
def total_objects(params={})
@total_objects = Object.count :include => [:user], :conditions => conditions
end
def datatable_columns(column_id)
case column_id.to_i
when 1
return "objects.description"
when 2
return "objects.created_at"
else
return "users.name"
end
end
def conditions
conditions = []
conditions << "(objects.description ILIKE '%#{params[:sSearch]}%' OR users.name ILIKE '%#{params[:sSearch]}%')" if(params[:sSearch])
return conditions.join(" AND ")
end
### Note
There is a more functionality offered by DataTables than this plugin currently provides. We add to it as we find need for other features. If there's a feature of DataTables that you'd like to see, fork this repo and add it so we can all benefit.
### Credits
Copyright (c) 2009 [Phronos](http://phronos.com), released under the MIT license

Some files were not shown because too many files have changed in this diff Show More