diff --git a/README b/README new file mode 100644 index 0000000..a1db73c --- /dev/null +++ b/README @@ -0,0 +1,203 @@ +== Welcome to Rails + +Rails is a web-application and persistence framework that includes everything +needed to create database-backed web-applications according to the +Model-View-Control pattern of separation. This pattern splits the view (also +called the presentation) into "dumb" templates that are primarily responsible +for inserting pre-built data in between HTML tags. The model contains the +"smart" domain objects (such as Account, Product, Person, Post) that holds all +the business logic and knows how to persist themselves to a database. The +controller handles the incoming requests (such as Save New Account, Update +Product, Show Post) by manipulating the model and directing data to the view. + +In Rails, the model is handled by what's called an object-relational mapping +layer entitled Active Record. This layer allows you to present the data from +database rows as objects and embellish these data objects with business logic +methods. You can read more about Active Record in +link:files/vendor/rails/activerecord/README.html. + +The controller and view are handled by the Action Pack, which handles both +layers by its two parts: Action View and Action Controller. These two layers +are bundled in a single package due to their heavy interdependence. This is +unlike the relationship between the Active Record and Action Pack that is much +more separate. Each of these packages can be used independently outside of +Rails. You can read more about Action Pack in +link:files/vendor/rails/actionpack/README.html. + + +== Getting Started + +1. At the command prompt, start a new Rails application using the rails command + and your application name. Ex: rails myapp + (If you've downloaded Rails in a complete tgz or zip, this step is already done) +2. Change directory into myapp and start the web server: script/server (run with --help for options) +3. Go to http://localhost:3000/ and get "Welcome aboard: You’re riding the Rails!" +4. Follow the guidelines to start developing your application + + +== Web Servers + +By default, Rails will try to use Mongrel and lighttpd if they are installed, otherwise +Rails will use WEBrick, the webserver that ships with Ruby. When you run script/server, +Rails will check if Mongrel exists, then lighttpd and finally fall back to WEBrick. This ensures +that you can always get up and running quickly. + +Mongrel is a Ruby-based webserver with a C component (which requires compilation) that is +suitable for development and deployment of Rails applications. If you have Ruby Gems installed, +getting up and running with mongrel is as easy as: gem install mongrel. +More info at: http://mongrel.rubyforge.org + +If Mongrel is not installed, Rails will look for lighttpd. It's considerably faster than +Mongrel and WEBrick and also suited for production use, but requires additional +installation and currently only works well on OS X/Unix (Windows users are encouraged +to start with Mongrel). We recommend version 1.4.11 and higher. You can download it from +http://www.lighttpd.net. + +And finally, if neither Mongrel or lighttpd are installed, Rails will use the built-in Ruby +web server, WEBrick. WEBrick is a small Ruby web server suitable for development, but not +for production. + +But of course its also possible to run Rails on any platform that supports FCGI. +Apache, LiteSpeed, IIS are just a few. For more information on FCGI, +please visit: http://wiki.rubyonrails.com/rails/pages/FastCGI + + +== Debugging Rails + +Sometimes your application goes wrong. Fortunately there are a lot of tools that +will help you debug it and get it back on the rails. + +First area to check is the application log files. Have "tail -f" commands running +on the server.log and development.log. Rails will automatically display debugging +and runtime information to these files. Debugging info will also be shown in the +browser on requests from 127.0.0.1. + +You can also log your own messages directly into the log file from your code using +the Ruby logger class from inside your controllers. Example: + + class WeblogController < ActionController::Base + def destroy + @weblog = Weblog.find(params[:id]) + @weblog.destroy + logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!") + end + end + +The result will be a message in your log file along the lines of: + + Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1 + +More information on how to use the logger is at http://www.ruby-doc.org/core/ + +Also, Ruby documentation can be found at http://www.ruby-lang.org/ including: + +* The Learning Ruby (Pickaxe) Book: http://www.ruby-doc.org/docs/ProgrammingRuby/ +* Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide) + +These two online (and free) books will bring you up to speed on the Ruby language +and also on programming in general. + + +== Debugger + +Debugger support is available through the debugger command when you start your Mongrel or +Webrick server with --debugger. This means that you can break out of execution at any point +in the code, investigate and change the model, AND then resume execution! Example: + + class WeblogController < ActionController::Base + def index + @posts = Post.find(:all) + debugger + end + end + +So the controller will accept the action, run the first line, then present you +with a IRB prompt in the server window. Here you can do things like: + + >> @posts.inspect + => "[#nil, \"body\"=>nil, \"id\"=>\"1\"}>, + #\"Rails you know!\", \"body\"=>\"Only ten..\", \"id\"=>\"2\"}>]" + >> @posts.first.title = "hello from a debugger" + => "hello from a debugger" + +...and even better is that you can examine how your runtime objects actually work: + + >> f = @posts.first + => #nil, "body"=>nil, "id"=>"1"}> + >> f. + Display all 152 possibilities? (y or n) + +Finally, when you're ready to resume execution, you enter "cont" + + +== Console + +You can interact with the domain model by starting the console through script/console. +Here you'll have all parts of the application configured, just like it is when the +application is running. You can inspect domain models, change values, and save to the +database. Starting the script without arguments will launch it in the development environment. +Passing an argument will specify a different environment, like script/console production. + +To reload your controllers and models after launching the console run reload! + + +== Description of Contents + +app + Holds all the code that's specific to this particular application. + +app/controllers + Holds controllers that should be named like weblogs_controller.rb for + automated URL mapping. All controllers should descend from ApplicationController + which itself descends from ActionController::Base. + +app/models + Holds models that should be named like post.rb. + Most models will descend from ActiveRecord::Base. + +app/views + Holds the template files for the view that should be named like + weblogs/index.erb for the WeblogsController#index action. All views use eRuby + syntax. + +app/views/layouts + Holds the template files for layouts to be used with views. This models the common + header/footer method of wrapping views. In your views, define a layout using the + layout :default and create a file named default.erb. Inside default.erb, + call <% yield %> to render the view using this layout. + +app/helpers + Holds view helpers that should be named like weblogs_helper.rb. These are generated + for you automatically when using script/generate for controllers. Helpers can be used to + wrap functionality for your views into methods. + +config + Configuration files for the Rails environment, the routing map, the database, and other dependencies. + +db + Contains the database schema in schema.rb. db/migrate contains all + the sequence of Migrations for your schema. + +doc + This directory is where your application documentation will be stored when generated + using rake doc:app + +lib + Application specific libraries. Basically, any kind of custom code that doesn't + belong under controllers, models, or helpers. This directory is in the load path. + +public + The directory available for the web server. Contains subdirectories for images, stylesheets, + and javascripts. Also contains the dispatchers and the default HTML files. This should be + set as the DOCUMENT_ROOT of your web server. + +script + Helper scripts for automation and generation. + +test + Unit and functional tests along with fixtures. When using the script/generate scripts, template + test files will be generated for you and placed in this directory. + +vendor + External libraries that the application depends on. Also includes the plugins subdirectory. + This directory is in the load path. diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..3bb0e85 --- /dev/null +++ b/Rakefile @@ -0,0 +1,10 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require(File.join(File.dirname(__FILE__), 'config', 'boot')) + +require 'rake' +require 'rake/testtask' +require 'rake/rdoctask' + +require 'tasks/rails' diff --git a/app/controllers/application.rb b/app/controllers/application.rb new file mode 100644 index 0000000..5b64276 --- /dev/null +++ b/app/controllers/application.rb @@ -0,0 +1,111 @@ +# Filters added to this controller apply to all controllers in the application. +# Likewise, all the methods added will be available for all controllers. + +class ApplicationController < ActionController::Base + helper :all # include all helpers, all the time + + # See ActionController::RequestForgeryProtection for details + # Uncomment the :secret if you're not using the cookie session store + protect_from_forgery # :secret => 'bb94fe1aac26787e53a52ea7be7335a1' + +#private methods +# +private + + def authorize + unless User.find_by_id(session[:user_id]) + flash[:notice] = "Please log in" + redirect_to(:controller => :login , :action => :sign_in) + end + end + + def authorize_admin + if session[:permission] == "admin" + return true + else + flash[:notice] = "You do not have administration priviliges to access this fucntion." + redirect_to(:controller => :menu) + return false + end + end + + def authorize_client + if session[:permission] == "client" + return true + else + flash[:notice] = "You do not have administration priviliges to access this fucntion." + redirect_to(:controller => :menu) + return false + end + + end + + def is_vz_running + @output = `uname -r` + if File.exist?("/proc/vz") + #if @output.include?("stab") || @output.include?("gc") || @output.include?("ovz") || @output.include?("openvz") + @status = `/etc/init.d/vz status` + unless @status.include?("running") + flash[:notice] = "VZ service is not running." + redirect_to :controller => :openvz, :action => :vz_status + end + else + flash[:notice] = "The kernel does not support OpenVZ." + redirect_to :controller => :menu, :action => :index + end + end + + def get_vps_id + @vps_id = params[:vps_id] + end + + def get_conf_files_names + all_conf = `ls /etc/vz/conf/*.conf-sample` + names = all_conf.split("\n") + @conf_names = [] + for file in names + n = file.split("ve-") + name = n[1].split(".conf-sample") + @conf_names << name[0] + end + return @conf_names + end + + def write_conf(name, conf_file) + cnf = Vps.new + cnf.write_to_conf_file(name, conf_file) + flash[:notice] = "#{name} configuration file was created/modified successfully." + redirect_to :controller => :configfile, :action => :list_conf_file + end + + def extract_vps_values(i, k) + array = i.split("\n") + array2 = k.split("\n") # to get the name of the container + @rows = [] + @names = [] + + #extract the names of the containers + for row in array2 + parts = row.split(" ") + @names << parts[4] + end + #extract other information like id, ip, hostname, etc + for row in array + r = row.split(" ") + #extract name of distro for the logo + r << distro_name(r[0]) + @rows << r + end + @rows + end + + def distro_name(vps_id) + first_line = "" + file = File.open("/vz/private/#{vps_id}/etc/issue") + file.each_line {|line| first_line = line; break } + file.close + x = first_line.split(" ") + dist_name = x[0] + end + +end diff --git a/app/controllers/configfile_controller.rb b/app/controllers/configfile_controller.rb new file mode 100644 index 0000000..88dd94e --- /dev/null +++ b/app/controllers/configfile_controller.rb @@ -0,0 +1,122 @@ +class ConfigfileController < ApplicationController + before_filter :authorize + before_filter :authorize_admin + + def split_resources + end + + def generate_conf + @info = params[:split] + #vzsplit [-f config_name] | [-n numves] | [-s swap_size] + cmd = "vzsplit " + if @info[:name].blank? || @info[:ve].blank? + flash[:notice] = "Incorrect or blank configuration file name and/or number of VEs." + redirect_to :action => :split_resources + elsif @info[:name].include?(" ") || @info[:name].include?("*") + flash[:notice] = "Wrong file name: space is not allowed." + redirect_to :action => :split_resources + else + cmd += " -f #{@info[:name]} " + cmd += " -n #{@info[:ve]} " + cmd += " -s #{@info[:swap]} " unless @info[:swap].blank? + @output = `#{cmd}` + flash[:notice] = " #{@info[:name]} configuration file was created successfully. You can use it now." + redirect_to :action => :list_conf_file + end + end + + def edit_conf_file + @file_name = params[:conf_name] + path = "/etc/sysconfig/vz-scripts/ve-#{@file_name}.conf-sample" + cnf = Vps.new + @conf_file = cnf.read_sample_conf_file(path) + end + + def delete_conf_file + @file_name = params[:conf_name] + `rm -rf /etc/sysconfig/vz-scripts/ve-#{@file_name}.conf-sample` + flash[:notice] = "#{@file_name} configuration file was deleted." + redirect_to :action => :list_conf_file + end +=begin + def validate_conf_file + @file_name = params[:conf_name] + output = `vzcfgvalidate /etc/sysconfig/vz-scripts/ve-#{@file_name}.conf-sample` + flash[:notice] = output + redirect_to :action => :list_conf_file + end +=end + def list_conf_file + @conf_names = get_conf_files_names + end + def new_conf_file + @conf_file = {} + @conf_file["NUMTCPSOCK"]="360:360" + @conf_file["KMEMSIZE"]="11055923:11377049" + @conf_file["LOCKEDPAGES"]="256:256" + @conf_file["PRIVVMPAGES"]="65536:69632" + @conf_file["SHMPAGES"]="21504:21504" + @conf_file["NUMPROC"]="240:240" + @conf_file["PHYSPAGES"]="0:2147483647" + @conf_file["VMGUARPAGES"]="33792:2147483647" + @conf_file["OOMGUARPAGES"]="26112:2147483647" + @conf_file["NUMFLOCK"]="188:206" + @conf_file["NUMPTY"]="16:16" + @conf_file["NUMSIGINFO"]="256:256" + @conf_file["TCPSNDBUF"]="1720320:2703360" + @conf_file["TCPRCVBUF"]="1720320:2703360" + @conf_file["OTHERSOCKBUF"]="1126080:2097152" + @conf_file["DGRAMRCVBUF"]="262144:262144" + @conf_file["NUMOTHERSOCK"]="360:360" + @conf_file["DCACHESIZE"]="3409920:3624960" + @conf_file["NUMFILE"]="9312:9312" + @conf_file["AVNUMPROC"]="180:180" + @conf_file["NUMIPTENT"]="128:128" + @conf_file["DISKSPACE"]="1048576:1153024" + @conf_file["DISKINODES"]="200000:220000" + @conf_file["QUOTATIME"]="0" + @conf_file["CPUUNITS"]="1000" + end + + def create_new_conf_file + @conf = params[:conf] + @conf_file = {} + name = params[:file][:name] + + if name.blank? + flash[:notice] = "Configuration file name cannot be blank." + redirect_to :action => :new_conf_file + elsif name.include?(" ") + flash[:notice] = "Wrong file name: space is not allowed." + redirect_to :action => :new_conf_file + else + @conf_file["ONBOOT"] = "\"" + "yes" + "\"\n" + @conf_file["AVNUMPROC"]= "\"" + "180:180" + "\"\n" + @conf_file["NUMTCPSOCK"] = "\"" + @conf["NUMTCPSOCK1"] + ":" + @conf["NUMTCPSOCK2"] + "\"\n" + @conf_file["TCPSNDBUF"] = "\"" + @conf["TCPSNDBUF1"] + ":" + @conf["TCPSNDBUF2"] + "\"\n" + @conf_file["OTHERSOCKBUF"] = "\"" + @conf["OTHERSOCKBUF1"] + ":" + @conf["OTHERSOCKBUF2"] + "\"\n" + @conf_file["DGRAMRCVBUF"] = "\"" + @conf["DGRAMRCVBUF1"] + ":" + @conf["DGRAMRCVBUF2"] + "\"\n" + @conf_file["TCPRCVBUF"] = "\"" + @conf["TCPRCVBUF1"] + ":" + @conf["TCPRCVBUF1"] + "\"\n" + @conf_file["NUMOTHERSOCK"] = "\"" + @conf["NUMOTHERSOCK1"] + ":" + @conf["NUMOTHERSOCK2"] + "\"\n" + @conf_file["DISKINODES"] = "\"" + @conf["DISKINODES1"] + ":" + @conf["DISKINODES2"] + "\"\n" + @conf_file["DISKSPACE"] = "\"" + @conf["DISKSPACE1"] + ":" + @conf["DISKSPACE2"] + "\"\n" + @conf_file["QUOTATIME"] = "\"" + @conf["QUOTATIME"] + "\"\n" unless @conf_file["QUOTATIME"].blank? + @conf_file["NUMPROC"] = "\"" + @conf["NUMPROC1"] + ":" + @conf["NUMPROC2"] + "\"\n" + @conf_file["CPUUNITS"] = "\"" + @conf["CPUUNITS"] + "\"\n" + @conf_file["KMEMSIZE"] = "\"" + @conf["KMEMSIZE1"] + ":" + @conf["KMEMSIZE2"] + "\"\n" + @conf_file["PHYSPAGES"] = "\"" + @conf["PHYSPAGES1"] + ":" + @conf["PHYSPAGES2"] + "\"\n" + @conf_file["DCACHESIZE"] = "\"" + @conf["DCACHESIZE1"] + ":" + @conf["DCACHESIZE2"] + "\"\n" + @conf_file["PRIVVMPAGES"] = "\"" + @conf["PRIVVMPAGES1"] + ":" + @conf["PRIVVMPAGES2"] + "\"\n" + @conf_file["LOCKEDPAGES"] = "\"" + @conf["LOCKEDPAGES1"] + ":" + @conf["LOCKEDPAGES2"] + "\"\n" + @conf_file["VMGUARPAGES"] = "\"" + @conf["VMGUARPAGES1"] + ":" + @conf["VMGUARPAGES2"] + "\"\n" + @conf_file["OOMGUARPAGES"] = "\"" + @conf["OOMGUARPAGES1"] + ":" + @conf["OOMGUARPAGES2"] + "\"\n" + @conf_file["SHMPAGES"] = "\"" + @conf["SHMPAGES1"] + ":" + @conf["SHMPAGES2"] + "\"\n" + @conf_file["NUMFLOCK"] = "\"" + @conf["NUMFLOCK1"] + ":" + @conf["NUMFLOCK2"] + "\"\n" + @conf_file["NUMSIGINFO"] = "\"" + @conf["NUMSIGINFO1"] + ":" + @conf["NUMSIGINFO2"] + "\"\n" + @conf_file["NUMIPTENT"] = "\"" + @conf["NUMIPTENT1"] + ":" + @conf["NUMIPTENT2"] + "\"\n" + @conf_file["NUMFILE"] = "\"" + @conf["NUMFILE1"] + ":" + @conf["NUMFILE2"] + "\"\n" + @conf_file["NUMPTY"] = "\"" + @conf["NUMPTY1"] + ":" + @conf["NUMPTY2"] + "\"\n" + write_conf(name, @conf_file) + end + end +end diff --git a/app/controllers/container_controller.rb b/app/controllers/container_controller.rb new file mode 100644 index 0000000..5d52698 --- /dev/null +++ b/app/controllers/container_controller.rb @@ -0,0 +1,286 @@ +class ContainerController < ApplicationController + + before_filter :authorize + before_filter :authorize_admin, :only => [:vzmigrate, :migrate_vps, :new_vps, :create_vps, :destroy_vps] + before_filter :get_vps_id, :only => [:start_vps, :destroy_vps, :stop_vps, :restart_vps, :migrate_vps, :vzmigrate, :assign_to_owner, :create_backup] + before_filter :is_vz_running, :except => [:index, :vz_status, :start_vz, :about] + + def vzmigrate + # --ssh=options look how to do it + @rec = params[:vps] + @cmd = "vzmigrate " + @cmd += " -r yes " if @rec[:r].include?("yes") + @cmd += " -v " if @rec[:v].include?("yes") + @cmd += " --online " if @rec[:online].include?("yes") + @cmd += " --keep-dst " if @rec[:keepdst].include?("yes") + if not @rec[:ip].blank? && @vps_id + @cmd += " #{@rec[:ip]} #{@vps_id} " + output = `#{@cmd}` + flash[:notice] = output + redirect_to :action => :list_vps + else + flash[:notice] = "IP Address incorrect" + redirect_to :action => :migrate_vps, :vps_id => @vps_id + end + end + + def migrate_vps + end + + def list_vps + @status = params[:status] + @status ||= "all" + if @status.include?("running") + i = `vzlist -H` + k = `vzlist -Hn` + elsif @status.include?("stopped") + i = `vzlist -SH` + k = `vzlist -SHn` + else + i = `vzlist -aH` + k = `vzlist -aHn` + @status = "" + end + @rows = extract_vps_values(i, k) + end + + def new_vps + get_users + #this is to get the template names + tmpl = `ls /vz/template/cache/` + names = tmpl.split("\n") + @new_names = [] + for name in names + n = name.split(".tar") + @new_names << [n[0]] + end + @conf_names = get_conf_files_names + end + + def create_vps + #This will create new vps based on the values provided by the user + temp_name = params[:vps][:os_name] + conf_file = params[:vps][:conf_file] + vps_id = params[:vps][:vps_id] + vps_name = params[:vps][:vps_name] + ip_address = params[:vps][:ipadd] + hostname = params[:vps][:hostname] + nameserver = params[:vps][:nameserver] + booting = params[:vps][:booting] + root_pass = params[:vps][:root_pass] + starting = params[:vps][:starting] + output = "" + user_id = params[:vps][:user_id] + + @the_vps = Vps.new + @the_vps.cnt_id = vps_id.to_i + @the_vps.user_id = user_id + + #here we start the checking of id, ip, hostname + i = `vzlist -aH` + k = `vzlist -aHn` + rows = extract_vps_values(i, k) + + # index 0, 3, 4 + ips = [] + ids = [] + hostnames = [] + + for row in rows + ids << row[0] + ips << row[3] + hostnames << row[4] + end + err = "" + + if vps_id.blank? or ids.include?(vps_id) + err = "#{vps_id} VPS ID is used by another VPS, please choose another one.
" + err = "Error: Blank or Invalid VPS ID.
" if vps_id.blank? + end + + if temp_name.blank? + err += "Choose OS template from the list" + end + + if ips.include?(ip_address) + err += "#{ip_address} IP Address is used by another VPS, please choose another one.
" + end + + if hostnames.include?(hostname) + err += "#{hostname} Hostname is used by another VPS, please choose another one.
" + end + + unless err.size.zero? + flash[:notice] = err + new_vps + render :action => :new_vps + else + #the creation process starts here + if conf_file.blank? + output = `vzctl create #{vps_id} --ostemplate #{temp_name}` + else + output += `vzctl create #{vps_id} --ostemplate #{temp_name} --config #{conf_file}` + end + + output += `vzctl set #{vps_id} --name #{vps_name} --save` unless vps_name.blank? + output += `vzctl set #{vps_id} --ipadd #{ip_address} --save` unless ip_address.blank? + output += `vzctl set #{vps_id} --hostname #{hostname} --save` unless hostname.blank? + output += `vzctl set #{vps_id} --nameserver #{nameserver} --save` unless nameserver.blank? + output += `vzctl set #{vps_id} --onboot #{booting} --save` unless booting.blank? + output += `vzctl set #{vps_id} --userpasswd root:#{root_pass}` unless root_pass.blank? + output += `vzctl start #{vps_id}` if starting == "yes" + if @the_vps.save + flash[:notice] = output + else + flash[:notice] = "Failed to assign the VPS to a user, however it was created successfully.
#{output}" + end + redirect_to :action => :list_vps + end + end + + def assign_to_owner + get_users + @owner = Vps.find_by_cnt_id(params[:vps_id]) + end + + def change_owner + vps = Vps.find_by_cnt_id(params[:vps_id]) + #if the container was not in the database + if vps + vps.user_id = params[:vps][:user_id] + else + vps = Vps.new + vps.cnt_id = params[:vps_id].to_i + vps.user_id = params[:vps][:user_id] + end + + #save the owner + if vps.save + flash[:notice] = "Owner of container #{vps.cnt_id} is #{vps.user.name}" + redirect_to :action => :list_vps + else + render :action => :assign_to_owner_ + end + end + + def vpses_of_user + @user = User.find_by_id(params[:user_id]) + ids = @user.vpss.map {|vps| [vps.cnt_id]} + i = `vzlist -aH #{ids.join(" ")}` + k = `vzlist -aHn #{ids.join(" ")}` + @rows = extract_vps_values(i, k) + @status = "" + render :action => :list_vps + end + + def start_vps + msg = `vzctl start #{@vps_id}` + redirect_msg(@vps_id, "started\n" + msg) + end + + def restart_vps + msg = `vzctl restart #{@vps_id}` + redirect_msg(@vps_id, "restarted\n" + msg) + end + + def stop_vps + msg = `vzctl stop #{@vps_id}` + redirect_msg(@vps_id, "stopped\n"+ msg) + end + + def destroy_vps + msg = `vzctl destroy #{@vps_id}` + #delete entry from database + Vps.find_by_cnt_id(@vps_id).destroy + redirect_msg(@vps_id, "desroyed\n"+ msg) + end + + def backups + files = `ls /vz/dump/*.tar` + @dumps = [] + for file in files + @dumps << file.split("/vz/dump/") + end + end + + def create_backup + msg = `vzdump --suspend #{@vps_id}` + redirect_msg(@vps_id, "has been backed up\n"+ msg) + end + + def restore + get_users + @dump_id = params[:dump_id] + end + + def restore_dump + get_users + if params[:dump][:id].blank? || params[:dump][:id] == params[:dump_id] + flash[:notice] = "The Container ID must be unique" + @dump_id = params[:dump_id] + render :action => :restore, :dump_id => params[:dump_id] + elsif params[:dump][:user_id].blank? + flash[:notice] = "Please assign the container into an owner." + @dump_id = params[:dump_id] + render :action => :restore, :dump_id => params[:dump_id] + else + msg = `vzdump --restore /vz/dump/vzdump-#{params[:dump_id]}.tar #{params[:dump][:id]}` + vps = Vps.new + vps.user_id = params[:dump][:user_id] + vps.cnt_id = params[:dump][:id] + if vps.save + flash[:notice] = "Container #{params[:dump_id]} has been restored into #{params[:dump][:id]}" + redirect_to :action => :list_vps + else + flash[:notice] = "Failed to save the owner of the container into database" + redirect_to :action => :list_vps + end + end + end + + def delete_dump + `rm -rf /vz/dump/vzdump-#{params[:dump_id]}.tar` + flash[:notice] = "vzdump-#{params[:dump_id]}.tar has been deleted successfuly." + redirect_to :action => :backups + end + + def backup_all_containers + msg = `vzdump --suspend --all --mailto root` + flash[:notice] = "All the containers have been backed up.
#{msg}." + redirect_to :action => :backups + end + + def backup_user_containers + get_users + end + + def create_user_backup + vpses = Vps.find(:all, :conditions => "user_id = '#{params[:dump][:user_id]}'") + user = User.find(params[:dump][:user_id]) + unless vpses.size.zero? + msg = "" + ids = [] + for vps in vpses + msg += `vzdump --suspend #{vps.cnt_id}` + ids << vps.cnt_id + end + flash[:notice] = "#{ids.join(", ")} have been backed up for #{user.name}." + redirect_to :action => :backups + else + flash[:notice] = "#{user.name} has no containers on this server. Nothing was backed up." + redirect_to :action => :backup_user_containers + end + end + +#Private methods +private + + def redirect_msg (vps_id, msg) + flash[:notice] = "Container #{vps_id} #{msg}" + redirect_to :action => :list_vps + end + + def get_users + @users = User.find(:all).map {|user| [user.name, user.id]} + end +end diff --git a/app/controllers/inside_vps_controller.rb b/app/controllers/inside_vps_controller.rb new file mode 100644 index 0000000..90a7e77 --- /dev/null +++ b/app/controllers/inside_vps_controller.rb @@ -0,0 +1,192 @@ +class InsideVpsController < ApplicationController + before_filter :authorize + before_filter :authorize_admin + before_filter :is_vz_running + before_filter :get_vps_id + before_filter :get_conf, :except => [:run_command, :execute_cmd, :view_vps, :booting, :basic_net, :adv_net, :disk_mgt, :cpu_mgt, :memory_mgt, :misc, :monitor_resources, :running_processes, :services, :start_service, :stop_service, :restart_service] + before_filter :read_conf, :except => [:run_command, :execute_cmd, :view_vps] + + def start_service + name = params[:ser_name] + out = `vzctl exec #{@vps_id} /etc/init.d/#{name} start` + flash[:notice] = out + redirect_to :action => :services, :vps_id => @vps_id + end + + def stop_service + name = params[:ser_name] + out = `vzctl exec #{@vps_id} /etc/init.d/#{name} stop` + flash[:notice] = out + redirect_to :action => :services, :vps_id => @vps_id + end + + def restart_service + name = params[:ser_name] + out = `vzctl exec #{@vps_id} /etc/init.d/#{name} restart` + flash[:notice] = out + redirect_to :action => :services, :vps_id => @vps_id + end + + def services + output = `vzctl exec #{@vps_id} ls /etc/init.d/` + @all_services = output.split("\n") + end + + def running_processes + out='' + File.popen("vzctl exec #{@vps_id} ps aux"){|f|f.gets;out=f.read} + @procs = out.split("\n") + end + def run_command + end + + def execute_cmd + @cmd = params[:command][:line] + @output = `vzctl exec #{@vps_id} #{@cmd}` + end + + def view_vps + i = `vzlist -aH #{@vps_id}` + k = `vzlist -aHn #{@vps_id}` + thename = k.split(" ") + @name = thename[4] + @line = i.split(" ") + #extract name of distro + @line << distro_name(@line[0]) + @uptime = `vzctl exec #{@vps_id} uptime` + end + def root_pass + end + def booting + end + def basic_net + end + def adv_net + end + def disk_mgt + end + def cpu_mgt + end + def memory_mgt + end + def misc + end + + def change_name + new_name = params[:vps][:new_name] + output = "" + output += `vzctl set #{@vps_id} --name #{new_name} --save` + write_conf(output, 'view_vps') + end + + def edit_booting + output = "" + output += `vzctl set #{@vps_id} --onboot #{@conf[:booting]} --save` + write_conf(output, 'view_vps') + end + + def add_ip + output = "" + output += `vzctl set #{@vps_id} --ipadd #{@conf[:IP_ADDRESS]} --save` + write_conf(output, 'basic_net') + end + + def delete_ip + ip = params[:ip] + output = "" + output += `vzctl set #{@vps_id} --ipdel #{ip} --save` + write_conf(output, 'basic_net') + end + + def edit_hostname + output = "" + output += `vzctl set #{@vps_id} --hostname #{@conf[:HOSTNAME]} --save` + write_conf(output, 'basic_net') + end + + def edit_nameserver + output = "" + output += `vzctl set #{@vps_id} --nameserver #{@conf[:NAMESERVER]} --save` + write_conf(output, 'basic_net') + end + + def edit_adv_net + output = "" + output += `vzctl set #{@vps_id} --numtcpsock #{@conf[:NUMTCPSOCK1]}:#{@conf[:NUMTCPSOCK2]} --save` + output += `vzctl set #{@vps_id} --tcpsndbuf #{@conf[:TCPSNDBUF1]}:#{@conf[:TCPSNDBUF2]} --save` + output += `vzctl set #{@vps_id} --othersockbuf #{@conf[:OTHERSOCKBUF1]}:#{@conf[:OTHERSOCKBUF2]} --save` + output += `vzctl set #{@vps_id} --dgramrcvbuf #{@conf[:DGRAMRCVBUF1]}:#{@conf[:DGRAMRCVBUF2]} --save` + output += `vzctl set #{@vps_id} --tcprcvbuf #{@conf[:TCPRCVBUF1]}:#{@conf[:TCPRCVBUF2]} --save` + output += `vzctl set #{@vps_id} --numothersock #{@conf[:NUMOTHERSOCK1]}:#{@conf[:NUMOTHERSOCK2]} --save` + write_conf(output, 'view_vps') + end + + def edit_disk_mgt + output = "" + output += `vzctl set #{@vps_id} --diskinodes #{@conf[:DISKINODES1]}:#{@conf[:DISKINODES2]} --save` + output += `vzctl set #{@vps_id} --diskspace #{@conf[:DISKSPACE1]}:#{@conf[:DISKSPACE2]} --save` + output += `vzctl set #{@vps_id} --quotatime #{@conf[:QUOTATIME]} --save` + write_conf(output, 'view_vps') + end + + def edit_cpu_mgt + output = "" + output += `vzctl set #{@vps_id} --numproc #{@conf[:NUMPROC1]}:#{@conf[:NUMPROC2]} --save` + output += `vzctl set #{@vps_id} --cpuunits #{@conf[:CPUUNITS]} --save` + write_conf(output, 'view_vps' ) + end + + def edit_memory + output = "" + output += `vzctl set #{@vps_id} --kmemsize #{@conf[:KMEMSIZE1]}:#{@conf[:KMEMSIZE2]} --save` + output += `vzctl set #{@vps_id} --physpages #{@conf[:PHYSPAGES1]}:#{@conf[:PHYSPAGES2]} --save` + output += `vzctl set #{@vps_id} --dcachesize #{@conf[:DCACHESIZE1]}:#{@conf[:DCACHESIZE2]} --save` + output += `vzctl set #{@vps_id} --privvmpages #{@conf[:PRIVVMPAGES1]}:#{@conf[:PRIVVMPAGES2]} --save` + output += `vzctl set #{@vps_id} --lockedpages #{@conf[:LOCKEDPAGES1]}:#{@conf[:LOCKEDPAGES2]} --save` + output += `vzctl set #{@vps_id} --vmguarpages #{@conf[:VMGUARPAGES1]}:#{@conf[:VMGUARPAGES2]} --save` + output += `vzctl set #{@vps_id} --oomguarpages #{@conf[:OOMGUARPAGES1]}:#{@conf[:OOMGUARPAGES2]} --save` + output += `vzctl set #{@vps_id} --shmpages #{@conf[:SHMPAGES1]}:#{@conf[:SHMPAGES2]} --save` + write_conf(output, 'view_vps') + end + + def edit_misc + output = "" + output += `vzctl set #{@vps_id} --numflock #{@conf[:NUMFLOCK1]}:#{@conf[:NUMFLOCK2]} --save` + output += `vzctl set #{@vps_id} --numsiginfo #{@conf[:NUMSIGINFO1]}:#{@conf[:NUMSIGINFO2]} --save` + output += `vzctl set #{@vps_id} --numiptent #{@conf[:NUMIPTENT1]}:#{@conf[:NUMIPTENT2]} --save` + output += `vzctl set #{@vps_id} --numfile #{@conf[:NUMFILE1]}:#{@conf[:NUMFILE2]} --save` + output += `vzctl set #{@vps_id} --numpty #{@conf[:NUMPTY1]}:#{@conf[:NUMPTY2]} --save` + write_conf(output, 'view_vps') + end + + def monitor_resources + out = `cat /proc/bc/#{@vps_id}/resources` + @ubc = out.split("\n") + end + +#Private Methods start here +private + def distro_name(vps_id) + first_line = "" + file = File.open("/vz/private/#{vps_id}/etc/issue") + file.each_line {|line| first_line = line; break } + file.close + x = first_line.split(" ") + dist_name = x[0] + end + + def get_conf + @conf = params[:conf] + end + + def read_conf + cnf = Vps.new + @conf_file = cnf.read_conf_file(@vps_id) + end + + def write_conf(output, to_action) + flash[:notice] = output + to_action ||= 'view_vps' + redirect_to :action => to_action, :vps_id => @vps_id + end +end diff --git a/app/controllers/login_controller.rb b/app/controllers/login_controller.rb new file mode 100644 index 0000000..ac07b31 --- /dev/null +++ b/app/controllers/login_controller.rb @@ -0,0 +1,30 @@ +class LoginController < ApplicationController + before_filter :authorize, :except => :sign_in + def logout + session[:user_name] = nil + session[:user_id] = nil + session[:name] = nil + session[:permission] = nil + flash[:notice] = "You are logged out" + redirect_to :action => :sign_in + end + + def sign_in + session[:user_id] = nil + session[:permission] = nil + if request.post? + user = User.authunticate(params[:username], params[:password]) + if user && user.activated == 'yes' + session[:user_id] = user.id + session[:user_name] = user.username + session[:name] = user.name + session[:permission] = user.authority + flash[:notice] = "You are logged in" + redirect_to :controller => :menu, :action => :index + else + flash[:notice] = "Invalid username/password" + end + end + end + +end diff --git a/app/controllers/menu_controller.rb b/app/controllers/menu_controller.rb new file mode 100644 index 0000000..a552908 --- /dev/null +++ b/app/controllers/menu_controller.rb @@ -0,0 +1,6 @@ +class MenuController < ApplicationController + def index + end + def about + end +end diff --git a/app/controllers/openvz_controller.rb b/app/controllers/openvz_controller.rb new file mode 100644 index 0000000..5243fef --- /dev/null +++ b/app/controllers/openvz_controller.rb @@ -0,0 +1,23 @@ +class OpenvzController < ApplicationController + before_filter :authorize + before_filter :authorize_admin + + + def start_vz + flash[:notice] = `/etc/init.d/vz start` + redirect_to :controller => :openvz, :action => :vz_status + end + def restart_vz + flash[:notice] = `/etc/init.d/vz restart` + redirect_to :controller => :openvz, :action => :vz_status + end + def stop_vz + flash[:notice] = `/etc/init.d/vz stop` + redirect_to :controller => :openvz, :action => :vz_status + end + + def vz_status + @status = `/etc/init.d/vz status` + end + +end diff --git a/app/controllers/ostemplate_controller.rb b/app/controllers/ostemplate_controller.rb new file mode 100644 index 0000000..2aab5c0 --- /dev/null +++ b/app/controllers/ostemplate_controller.rb @@ -0,0 +1,75 @@ +class OstemplateController < ApplicationController + before_filter :authorize + before_filter :authorize_admin + before_filter :get_template_name + before_filter :get_vps_id, :only => [:recreate_template, :make_new_template] + def list_templates + list = `ls /vz/template/cache/*.tar.gz` + all = list.split("\n") + @templates = [] + for t in all + n = t.split("/vz/template/cache/") + @templates << n[1] + end + @templates + end + + def recreate + i = `vzlist -aH` + k = `vzlist -aHn` + @rows = extract_vps_values(i, k) + end + + def recreate_template + end + + def make_new_template + name = params[:temp][:name] + new_name = name + ".tar.gz" + templates = list_templates + if templates.include?(new_name) + flash[:notice] = " #{name} is used, choose different name." + redirect_to :action => :recreate_template, :vps_id => @vps_id + else + #this is to stop the container and delete the IP addresses + `vzctl stop #{@vps_id}` + `vzctl set #{@vps_id} --ipdel all --save` + #this accesses the private directory of the container and make a tar file in the cache directory + `cd /vz/private/#{@vps_id}; tar czf /vz/template/cache/#{new_name} .` + flash[:notice] = " #{name} template has been created successfully." + redirect_to :action => :list_templates + end + end + + def delete_template + if system("rm -rf /vz/template/cache/#{@name}") + flash[:notice] = "#{@name} template was deleted successfully." + redirect_to :action => :list_templates + else + flash[:notice] = "Failed to delete #{@name} template." + redirect_to :action => :list_templates + end + end + + def copy_template + end + + def transfer_file + username = params[:dest][:username] + address = params[:dest][:add] + #check for the existence of the IP address in the known_hosts files first + if system("scp /vz/template/cache/#{@name} #{username}@#{address}:/vz/template/cache/") + flash[:notice] = "#{@name} template was copied successfully to #{username}@#{address}." + redirect_to :action => :list_templates + else + flash[:notice] = "Failed to copy #{@name} template to #{username}@#{address} .
Make sure that you have keyed in the right username and IP address of the destination.
Or Read the note in the page below." + redirect_to :action => :copy_template, :name => @name + end + end +#private methods + private + + def get_template_name + @name = params[:name] + end +end diff --git a/app/controllers/user_containers_controller.rb b/app/controllers/user_containers_controller.rb new file mode 100644 index 0000000..99b4b4d --- /dev/null +++ b/app/controllers/user_containers_controller.rb @@ -0,0 +1,56 @@ +class UserContainersController < ApplicationController + + before_filter :authorize + before_filter :get_vps_id + before_filter :authorized_logged_in + before_filter :authorize_client + + def list_vps + @status = params[:status] + @user = User.find_by_id(params[:id]) + ids = @user.vpss.map {|vps| [vps.cnt_id]} + #@status ||= "all" + if @status == "running" +#this is a bug, filter the running one only + i = `vzlist -H #{ids.join(" ")}` + k = `vzlist -Hn #{ids.join(" ")}` + elsif @status == "stopped" + i = `vzlist -SH #{ids.join(" ")}` + k = `vzlist -SHn #{ids.join(" ")}` + else + i = `vzlist -aH #{ids.join(" ")}` + k = `vzlist -aHn #{ids.join(" ")}` + @status = "" + end + + @rows = extract_vps_values(i, k) + end + + def start_vps + msg = `vzctl start #{@vps_id}` + redirect_msg(@vps_id, "started\n" + msg) + end + + def restart_vps + msg = `vzctl restart #{@vps_id}` + redirect_msg(@vps_id, "restarted\n" + msg) + end + + def stop_vps + msg = `vzctl stop #{@vps_id}` + redirect_msg(@vps_id, "stopped\n"+ msg) + end + + private + def redirect_msg (vps_id, msg) + flash[:notice] = "Container #{vps_id} #{msg}" + redirect_to :action => :list_vps, :id => session[:user_id] + end + + def authorized_logged_in + if session[:user_id].to_i != params[:id].to_i + flash[:notice] = "You have no privileges to access other's accounts" + redirect_to :controller => :menu + end + end +end diff --git a/app/controllers/user_controller.rb b/app/controllers/user_controller.rb new file mode 100644 index 0000000..577e240 --- /dev/null +++ b/app/controllers/user_controller.rb @@ -0,0 +1,80 @@ +class UserController < ApplicationController + before_filter :authorize + before_filter :authorize_admin, :expect => [:view_user_by_user] + + def index + list + render :action => 'list' + end + + # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html) + verify :method => :post, :only => [ :destroy, :create, :update ], + :redirect_to => { :action => :list } + + def list + #@user_pages, @users = paginate :users, :per_page => 10 + @users = User.find(:all) + end + + def activated + @users = User.find(:all, :conditions => 'activated = "yes"') + render :action => :list + end + + def deactivated + @users = User.find(:all, :conditions => 'activated = "no"') + render :action => :list + end + + def show + @user = User.find(params[:id]) + end + + def new + @user = User.new + end + + def create + @user = User.new(params[:user]) + @user.password = User.encrypt_password(params[:user][:password]) + if @user.save + flash[:notice] = 'User was successfully created.' + redirect_to :action => 'list' + else + render :action => 'new' + end + end + + def edit + @user = User.find(params[:id]) + end + + def update + @user = User.find(params[:id]) + if @user.update_attributes(params[:user]) + flash[:notice] = 'User was successfully updated.' + redirect_to :action => 'show', :id => @user + else + render :action => 'edit' + end + end + + def change_password + @user = User.find(params[:id]) + end + + def update_password + if params[:password][:p1] == params[:password][:p2] + new_pass = User.encrypt_password(params[:password][:p2]) + @user = User.find(params[:id]) + @user.password = new_pass + if @user.update_attributes(params[:user]) + flash[:notice] = "Password has changed successfully." + redirect_to :action => :show, :id => params[:id] + end + else + flash[:notice] = "Passwords did not match. Make sure you do not mistype them." + redirect_to :action => :change_password, :id => params[:id] + end + end +end diff --git a/app/controllers/user_information_controller.rb b/app/controllers/user_information_controller.rb new file mode 100644 index 0000000..5d1af4c --- /dev/null +++ b/app/controllers/user_information_controller.rb @@ -0,0 +1,58 @@ +class UserInformationController < ApplicationController + + before_filter :authorize_client + before_filter :authorized_logged_in + + def view_user_by_user + @user = User.find(params[:id]) + end + + def edit + @user = User.find(params[:id]) + end + + def update + @user = User.find(params[:id]) + if @user.update_attributes(params[:user]) + flash[:notice] = 'User was successfully updated.' + redirect_to :controller => :user_information, :action => 'view_user_by_user', :id => @user + else + render :action => 'edit' + end + end + + def change_password + @user = User.find(params[:id]) + end + + def update_password + if params[:password][:p1] == params[:password][:p2] + @user = User.find(params[:id]) + current_pass = User.encrypt_password(params[:password][:current]) + if current_pass == @user.password + new_pass = User.encrypt_password(params[:password][:p2]) + @user.password = new_pass + if @user.update_attributes(params[:user]) + flash[:notice] = "Password has changed successfully." + redirect_to :action => :view_user_by_user, :id => params[:id] + end + else + flash[:notice] = "Current Password is wrong. Make sure you do not mistype it." + redirect_to :action => :change_password, :id => params[:id] + end + else + flash[:notice] = "Passwords did not match. Make sure you do not mistype them." + redirect_to :action => :change_password, :id => params[:id] + end + end + + private + + def authorized_logged_in + if session[:user_id].to_i != params[:id].to_i + flash[:notice] = "You have no privileges to access this area" + redirect_to :controller => :menu + end + end + +end diff --git a/app/controllers/user_inside_vps_controller.rb b/app/controllers/user_inside_vps_controller.rb new file mode 100644 index 0000000..ec88a84 --- /dev/null +++ b/app/controllers/user_inside_vps_controller.rb @@ -0,0 +1,89 @@ +class UserInsideVpsController < ApplicationController + + before_filter :authorize + before_filter :authorize_client + before_filter :authorized_logged_in + before_filter :get_vps_id + + def services + output = `vzctl exec #{@vps_id} ls /etc/init.d/` + @all_services = output.split("\n") + end + + def monitor_resources + out = `cat /proc/bc/#{@vps_id}/resources` + @ubc = out.split("\n") + end + + def edit_nameserver + output = "" + output += `vzctl set #{@vps_id} --nameserver #{@conf[:NAMESERVER]} --save` + write_conf(output, 'basic_net') + end + + def edit_hostname + output = "" + output += `vzctl set #{@vps_id} --hostname #{@conf[:HOSTNAME]} --save` + write_conf(output, 'basic_net') + end + + def view_vps + i = `vzlist -aH #{@vps_id}` + k = `vzlist -aHn #{@vps_id}` + thename = k.split(" ") + @name = thename[4] + @line = i.split(" ") + #extract name of distro + @line << distro_name(@line[0]) + @uptime = `vzctl exec #{@vps_id} uptime` + end + + def run_command + end + + def execute_cmd + @cmd = params[:command][:line] + @output = `vzctl exec #{@vps_id} #{@cmd}` + end + + def running_processes + out='' + File.popen("vzctl exec #{@vps_id} ps aux"){|f|f.gets;out=f.read} + @procs = out.split("\n") + end + + def start_service + name = params[:ser_name] + out = `vzctl exec #{@vps_id} /etc/init.d/#{name} start` + flash[:notice] = out + redirect_to :action => :services, :vps_id => @vps_id, :id => session[:user_id] + end + + def stop_service + name = params[:ser_name] + out = `vzctl exec #{@vps_id} /etc/init.d/#{name} stop` + flash[:notice] = out + redirect_to :action => :services, :vps_id => @vps_id, :id => session[:user_id] + end + + def restart_service + name = params[:ser_name] + out = `vzctl exec #{@vps_id} /etc/init.d/#{name} restart` + flash[:notice] = out + redirect_to :action => :services, :vps_id => @vps_id, :id => session[:user_id] + end + + private + def authorized_logged_in + #check that the user is not typing on the browser ids of containers and ids of users that are not his/her + vps = Vps.find_by_cnt_id(params[:vps_id]) + if session[:user_id].to_i != vps.user_id.to_i || session[:user_id].to_i != params[:id].to_i + flash[:notice] = "You have no privileges to access this area" + redirect_to :controller => :menu + end + end + + def get_conf + @conf = params[:conf] + end +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb new file mode 100644 index 0000000..35e78c3 --- /dev/null +++ b/app/helpers/application_helper.rb @@ -0,0 +1,5 @@ +# Methods added to this helper will be available to all templates in the application. +module ApplicationHelper + + +end diff --git a/app/helpers/configfile_helper.rb b/app/helpers/configfile_helper.rb new file mode 100644 index 0000000..7428111 --- /dev/null +++ b/app/helpers/configfile_helper.rb @@ -0,0 +1,2 @@ +module ConfigfileHelper +end diff --git a/app/helpers/container_helper.rb b/app/helpers/container_helper.rb new file mode 100644 index 0000000..6665d64 --- /dev/null +++ b/app/helpers/container_helper.rb @@ -0,0 +1,2 @@ +module ContainerHelper +end diff --git a/app/helpers/inside_vps_helper.rb b/app/helpers/inside_vps_helper.rb new file mode 100644 index 0000000..e5ce00a --- /dev/null +++ b/app/helpers/inside_vps_helper.rb @@ -0,0 +1,3 @@ +module InsideVpsHelper + +end diff --git a/app/helpers/login_helper.rb b/app/helpers/login_helper.rb new file mode 100644 index 0000000..a0418e3 --- /dev/null +++ b/app/helpers/login_helper.rb @@ -0,0 +1,2 @@ +module LoginHelper +end diff --git a/app/helpers/menu_helper.rb b/app/helpers/menu_helper.rb new file mode 100644 index 0000000..0052cb8 --- /dev/null +++ b/app/helpers/menu_helper.rb @@ -0,0 +1,3 @@ +module MenuHelper + +end diff --git a/app/helpers/openvz_helper.rb b/app/helpers/openvz_helper.rb new file mode 100644 index 0000000..d38cdb8 --- /dev/null +++ b/app/helpers/openvz_helper.rb @@ -0,0 +1,2 @@ +module OpenvzHelper +end diff --git a/app/helpers/ostemplate_helper.rb b/app/helpers/ostemplate_helper.rb new file mode 100644 index 0000000..96518e5 --- /dev/null +++ b/app/helpers/ostemplate_helper.rb @@ -0,0 +1,2 @@ +module OstemplateHelper +end diff --git a/app/helpers/user_containers_helper.rb b/app/helpers/user_containers_helper.rb new file mode 100644 index 0000000..69e7c43 --- /dev/null +++ b/app/helpers/user_containers_helper.rb @@ -0,0 +1,2 @@ +module UserContainersHelper +end diff --git a/app/helpers/user_helper.rb b/app/helpers/user_helper.rb new file mode 100644 index 0000000..0147c3f --- /dev/null +++ b/app/helpers/user_helper.rb @@ -0,0 +1,2 @@ +module UserHelper +end diff --git a/app/helpers/user_information_helper.rb b/app/helpers/user_information_helper.rb new file mode 100644 index 0000000..22a455f --- /dev/null +++ b/app/helpers/user_information_helper.rb @@ -0,0 +1,2 @@ +module UserInformationHelper +end diff --git a/app/helpers/user_inside_vps_helper.rb b/app/helpers/user_inside_vps_helper.rb new file mode 100644 index 0000000..9de0fb0 --- /dev/null +++ b/app/helpers/user_inside_vps_helper.rb @@ -0,0 +1,2 @@ +module UserInsideVpsHelper +end diff --git a/app/models/user.rb b/app/models/user.rb new file mode 100644 index 0000000..733b91d --- /dev/null +++ b/app/models/user.rb @@ -0,0 +1,22 @@ +class User < ActiveRecord::Base + require "digest/sha1" + has_many :vpss + validates_uniqueness_of :username + validates_presence_of :username, :password, :activated, :country, :email, :name, :authority + + def self.encrypt_password(password) + return Digest::SHA1.hexdigest(password) + end + + def self.authunticate(username, password) + passwd = encrypt_password(password) + user = self.find_by_username(username) + if user + if user.password != passwd + user = nil + end + end + user + end + +end diff --git a/app/models/vps.rb b/app/models/vps.rb new file mode 100644 index 0000000..6fbdebe --- /dev/null +++ b/app/models/vps.rb @@ -0,0 +1,39 @@ +class Vps < ActiveRecord::Base + attr_accessor :vps_id, :name + validates_presence_of :cnt_id, :user_id + belongs_to :user + + def extract_values(file) + all_lines = file.readlines + conf_det = {} + for line in all_lines + unless line.include?("#") + val = line.split("=") + conf_det[val[0]] = val[1] + end + + end + return conf_det + end + + def read_conf_file(vps_id) + file = File.open("/etc/sysconfig/vz-scripts/#{vps_id}.conf", "r") + extract_values(file) + end + + def read_sample_conf_file(name) + file = File.open("#{name}", "r") + extract_values(file) + end + + def write_to_conf_file(name, conf_file) + new_file = File.open("/etc/sysconfig/vz-scripts/ve-#{name}.conf-sample", "w") + for k, v in conf_file + unless k["\n"] + @text = k.to_s+"="+v.to_s + new_file.write(@text) + end + end + new_file.close + end +end diff --git a/app/views/configfile/_conf_details.rhtml b/app/views/configfile/_conf_details.rhtml new file mode 100644 index 0000000..824e7e8 --- /dev/null +++ b/app/views/configfile/_conf_details.rhtml @@ -0,0 +1,178 @@ + + + + + + + <% v = @conf_file["NUMTCPSOCK"].split(":") %> + + + + + + + <% v = @conf_file["TCPSNDBUF"].split(":") %> + + + + + + + <% v = @conf_file["TCPRCVBUF"].split(":") %> + + + + + + + <% v = @conf_file["OTHERSOCKBUF"].split(":") %> + + + + + + + <% v = @conf_file["DGRAMRCVBUF"].split(":") %> + + + + + + + <% v = @conf_file["NUMOTHERSOCK"].split(":") %> + + + + + + + + <% v = @conf_file["DISKSPACE"].split(":") %> + + + + + + + + <% v = @conf_file["DISKINODES"].split(":") %> + + + + + + + + + + + + + + + + + + + <% v = @conf_file["NUMPROC"].split(":") %> + + + + + + + + <% v = @conf_file["KMEMSIZE"].split(":") %> + + + + + + + + <% v = @conf_file["LOCKEDPAGES"].split(":") %> + + + + + + + <% v = @conf_file["PRIVVMPAGES"].split(":") %> + + + + + + + <% v = @conf_file["SHMPAGES"].split(":") %> + + + + + + + <% v = @conf_file["PHYSPAGES"].split(":") %> + + + + + + + <% v = @conf_file["VMGUARPAGES"].split(":") %> + + + + + + + <% v = @conf_file["OOMGUARPAGES"].split(":") %> + + + + + + + + <% v = @conf_file["DCACHESIZE"].split(":") %> + + + + + + + + <% v = @conf_file["NUMFLOCK"].split(":") %> + + + + + + + + <% v = @conf_file["NUMPTY"].split(":") %> + + + + + + + <% v = @conf_file["NUMFILE"].split(":") %> + + + + + + + <% v = @conf_file["NUMSIGINFO"].split(":") %> + + + + + + + <% v = @conf_file["NUMIPTENT"].split(":") %> + + + + + diff --git a/app/views/configfile/_conf_links.rhtml b/app/views/configfile/_conf_links.rhtml new file mode 100644 index 0000000..f413723 --- /dev/null +++ b/app/views/configfile/_conf_links.rhtml @@ -0,0 +1,10 @@ + + diff --git a/app/views/configfile/edit_conf_file.rhtml b/app/views/configfile/edit_conf_file.rhtml new file mode 100644 index 0000000..1203080 --- /dev/null +++ b/app/views/configfile/edit_conf_file.rhtml @@ -0,0 +1,14 @@ +<%= render :partial => 'conf_links' %> +

Edit Configuration File

+ +<% form_tag :action => "create_new_conf_file" do %> + +<%= hidden_field "file", "name", :value => @file_name %> + <%= render :partial => 'conf_details' %> + + + + + +<% end %> + diff --git a/app/views/configfile/list_conf_file.rhtml b/app/views/configfile/list_conf_file.rhtml new file mode 100644 index 0000000..015e866 --- /dev/null +++ b/app/views/configfile/list_conf_file.rhtml @@ -0,0 +1,16 @@ +<%= render :partial => 'conf_links' %> +
+

All Configuration Files

+
Parameter Barrier limit
Number of TCP Sockets : <%= text_field "conf", "NUMTCPSOCK1", :value => v[0].gsub("\"", ""), :size => "20"%> <%= text_field "conf", "NUMTCPSOCK2", :value => v[1].gsub("\"", ""), :size => "20"%>
TCP Send Buffer Size : <%= text_field "conf", "TCPSNDBUF1", :value => v[0].gsub("\"", ""), :size => "20"%> <%= text_field "conf", "TCPSNDBUF2", :value => v[1].gsub("\"", ""), :size => "20"%>
TCP Recieve Buffer Size : <%= text_field "conf", "TCPRCVBUF1", :value => v[0].gsub("\"", ""), :size => "20"%> <%= text_field "conf", "TCPRCVBUF2", :value => v[1].gsub("\"", ""), :size => "20"%>
Other Socket Buffer Size : <%= text_field "conf", "OTHERSOCKBUF1", :value => v[0].gsub("\"", ""), :size => "20"%> <%= text_field "conf", "OTHERSOCKBUF2", :value => v[1].gsub("\"", ""), :size => "20"%>
UDP Recieve Buffer Size : <%= text_field "conf", "DGRAMRCVBUF1", :value => v[0].gsub("\"", ""), :size => "20"%> <%= text_field "conf", "DGRAMRCVBUF2", :value => v[1].gsub("\"", ""), :size => "20"%>
Number of Other Sockets : <%= text_field "conf", "NUMOTHERSOCK1", :value => v[0].gsub("\"", ""), :size => "20"%> <%= text_field "conf", "NUMOTHERSOCK2", :value => v[1].gsub("\"", ""), :size => "20"%>
Disk Space (KByte) : <%= text_field "conf", "DISKSPACE1", :value => v[0].gsub("\"", ""), :size => "20"%> <%= text_field "conf", "DISKSPACE2", :value => v[1].gsub("\"", ""), :size => "20"%>
Disk Inodes : <%= text_field "conf", "DISKINODES1", :value => v[0].gsub("\"", ""), :size => "20"%> <%= text_field "conf", "DISKINODES2", :value => v[1].gsub("\"", ""), :size => "20"%>
Quota Time : <%= text_field "conf", "QUOTATIME", :value => @conf_file["QUOTATIME"].gsub("\"", ""), :size => "20" unless @conf_file["QUOTATIME"].blank?%>
Guaranteed Minimum CPU : <%= text_field "conf", "CPUUNITS", :value => @conf_file["CPUUNITS"].gsub("\"", ""), :size => "20"%>
Number of processes : <%= text_field "conf", "NUMPROC1", :value => v[0].gsub("\"", ""), :size => "20"%> <%= text_field "conf", "NUMPROC2", :value => v[1].gsub("\"", ""), :size => "20"%>
Kernel Memory Size : <%= text_field "conf", "KMEMSIZE1", :value => v[0].gsub("\"", ""), :size => "20"%> <%= text_field "conf", "KMEMSIZE2", :value => v[1].gsub("\"", ""), :size => "20"%>
Number of Locked Pages : <%= text_field "conf", "LOCKEDPAGES1", :value => v[0].gsub("\"", ""), :size => "20"%> <%= text_field "conf", "LOCKEDPAGES2", :value => v[1].gsub("\"", ""), :size => "20"%>
Private VM Pages : <%= text_field "conf", "PRIVVMPAGES1", :value => v[0].gsub("\"", ""), :size => "20"%> <%= text_field "conf", "PRIVVMPAGES2", :value => v[1].gsub("\"", ""), :size => "20"%>
Number of shm Pages : <%= text_field "conf", "SHMPAGES1", :value => v[0].gsub("\"", ""), :size => "20"%> <%= text_field "conf", "SHMPAGES2", :value => v[1].gsub("\"", ""), :size => "20"%>
Physical Pages : <%= text_field "conf", "PHYSPAGES1", :value => v[0].gsub("\"", ""), :size => "20"%> <%= text_field "conf", "PHYSPAGES2", :value => v[1].gsub("\"", ""), :size => "20"%>
VM Guar Pages : <%= text_field "conf", "VMGUARPAGES1", :value => v[0].gsub("\"", ""), :size => "20"%> <%= text_field "conf", "VMGUARPAGES2", :value => v[1].gsub("\"", ""), :size => "20"%>
OOM Guar Pages : <%= text_field "conf", "OOMGUARPAGES1", :value => v[0].gsub("\"", ""), :size => "20"%> <%= text_field "conf", "OOMGUARPAGES2", :value => v[1].gsub("\"", ""), :size => "20"%>
Disk Cache Size : <%= text_field "conf", "DCACHESIZE1", :value => v[0].gsub("\"", ""), :size => "20"%> <%= text_field "conf", "DCACHESIZE2", :value => v[1].gsub("\"", ""), :size => "20"%>
Number of files lock : <%= text_field "conf", "NUMFLOCK1", :value => v[0].gsub("\"", ""), :size => "20"%> <%= text_field "conf", "NUMFLOCK2", :value => v[1].gsub("\"", ""), :size => "20"%>
Number of PTYs : <%= text_field "conf", "NUMPTY1", :value => v[0].gsub("\"", ""), :size => "20"%> <%= text_field "conf", "NUMPTY2", :value => v[1].gsub("\"", ""), :size => "20"%>
Number of Open Files : <%= text_field "conf", "NUMFILE1", :value => v[0].gsub("\"", ""), :size => "20"%> <%= text_field "conf", "NUMFILE2", :value => v[1].gsub("\"", ""), :size => "20"%>
Number of SIGINFO : <%= text_field "conf", "NUMSIGINFO1", :value => v[0].gsub("\"", ""), :size => "20"%> <%= text_field "conf", "NUMSIGINFO2", :value => v[1].gsub("\"", ""), :size => "20"%>
Number of IPTENT : <%= text_field "conf", "NUMIPTENT1", :value => v[0].gsub("\"", ""), :size => "20"%> <%= text_field "conf", "NUMIPTENT2", :value => v[1].gsub("\"", ""), :size => "20"%>
<%= submit_tag "Create Configuration File"%>
+ +<% for conf in @conf_names -%> + + + + + + +<% end -%> +
<%= conf %> | <%= link_to "Edit", :action => :edit_conf_file , :conf_name => conf %> | <%= link_to "delete", {:action => :delete_conf_file , :conf_name => conf}, :confirm => "Are you sure?", :method => :post %>
+ + diff --git a/app/views/configfile/new_conf_file.rhtml b/app/views/configfile/new_conf_file.rhtml new file mode 100644 index 0000000..bc5b2bf --- /dev/null +++ b/app/views/configfile/new_conf_file.rhtml @@ -0,0 +1,21 @@ +<%= render :partial => 'conf_links' %> +

Create New Configuration File

+ +<% form_tag :action => "create_new_conf_file" do %> + + + + + + +
Name of the configuration file : <%= text_field "file", "name", :size => "20"%> note: space is not allowed
+ +<%= render :partial => 'conf_details' %> + + + <%= submit_tag "Create Configuration File"%> + + + +<% end %> + diff --git a/app/views/configfile/split_resources.rhtml b/app/views/configfile/split_resources.rhtml new file mode 100644 index 0000000..2476748 --- /dev/null +++ b/app/views/configfile/split_resources.rhtml @@ -0,0 +1,27 @@ +<%= render :partial => 'conf_links' %> +

Split the Hardware Node resources into equal parts. This page generates a full set of system resource control parameters for the given number of Containers. +

+ +<% form_tag :action => "generate_conf" do %> + + + + + + + + + + + + + + + + + +
Name of configuration file : <%= text_field "split", "name", :size => 25 %> note: space is not allowed.
Number of containers : <%= text_field "split", "ve", :size => 25 %>
Swap Size (Kbytes) : <%= text_field "split", "swap", :size => 25 %>
+ <%= submit_tag "Generate" %> +
+<% end %> + diff --git a/app/views/container/_form.rhtml b/app/views/container/_form.rhtml new file mode 100644 index 0000000..58d8d82 --- /dev/null +++ b/app/views/container/_form.rhtml @@ -0,0 +1,43 @@ + + + Owner of the container : <%= select("vps","user_id", @users, :prompt => "Choose Owner") %> + + + Operating system template : <%= select("vps","os_name", @new_names, :prompt => "Choose Distribution") %> + + + Configuration File Name : <%= select("vps", "conf_file", @conf_names, :prompt => "Choose config file")%> + + + Container ID : <%= text_field "vps", "vps_id" %> + + + Name : <%= text_field "vps", "vps_name" %> + + + IP Address : <%= text_field "vps", "ipadd" %> + + + Hostname : <%= text_field "vps", "hostname" %> + + + Nameserver (DNS) : <%= text_field "vps", "nameserver" %> + + + Enable booting on startup : + + Yes <%= radio_button "vps", "booting", "yes" %> + No <%= radio_button "vps", "booting", "no" %> + + + + Start up the container after creating it : + + Yes <%= radio_button "vps", "starting", "yes" %> + No <%= radio_button "vps", "starting", "no" %> + + + + Root Password (optional) : <%= text_field "vps", "root_pass" %> + + diff --git a/app/views/container/_links.rhtml b/app/views/container/_links.rhtml new file mode 100644 index 0000000..4169367 --- /dev/null +++ b/app/views/container/_links.rhtml @@ -0,0 +1,14 @@ + diff --git a/app/views/container/_top.html.erb b/app/views/container/_top.html.erb new file mode 100644 index 0000000..5be3a9a --- /dev/null +++ b/app/views/container/_top.html.erb @@ -0,0 +1,4 @@ +<%= link_to "Backup All Containers", {:action => :backup_all_containers}, :confirm => "Are you sure you want to backup all the containers. \nCaution: This operation may take SEVERAL MINUTES." %> +| +<%= link_to "Backup User's Containers", :action => :backup_user_containers %> + diff --git a/app/views/container/assign_to_owner.html.erb b/app/views/container/assign_to_owner.html.erb new file mode 100644 index 0000000..428d6bb --- /dev/null +++ b/app/views/container/assign_to_owner.html.erb @@ -0,0 +1,14 @@ +<%= render :partial => 'links' %> + +

Assign or Change Ownership of Container <%= @vps_id %>

+ +<% if @owner %> +

Current Owner is <%= @owner.user.name %>

+<% else %> +

No owner

+<% end %> + +<% form_tag :action => :change_owner, :vps_id => @vps_id do %> +

Owner of the container : <%= select("vps","user_id", @users, :prompt => "Choose Owner") %>

+ <%= submit_tag "Change Owner" %> +<% end %> diff --git a/app/views/container/backup_user_containers.html.erb b/app/views/container/backup_user_containers.html.erb new file mode 100644 index 0000000..e8ba1c5 --- /dev/null +++ b/app/views/container/backup_user_containers.html.erb @@ -0,0 +1,11 @@ +<%= render :partial => 'links' %> +<%= render :partial => 'top' %> +

Backup User's Containers

+<% form_tag :action => :create_user_backup do %> +

Choose the owner in order to backup all the containers that belong to him/her.

+

<%= select("dump","user_id", @users, :prompt => "Choose Owner") %>

+

<%= submit_tag "Backup" %>

+
+

Caution: This process may take several minutes.

+<% end %> + diff --git a/app/views/container/backups.html.erb b/app/views/container/backups.html.erb new file mode 100644 index 0000000..b2d29f7 --- /dev/null +++ b/app/views/container/backups.html.erb @@ -0,0 +1,21 @@ +<%= render :partial => 'links' %> +<%= render :partial => 'top' %> + +
+

All the dumped files

+ + +<% i = 1 %> +<% for dump in @dumps %> + + <% i = 1 - i %> + + <% id = dump[1].gsub("vzdump-",'').gsub('.tar','').gsub("\n",'') %> + <% vps = Vps.find_by_cnt_id(id) %> + + + + +<% end %> + +
File Name Owner
<%= dump[1] %> <%= vps.user.name if vps%> <%= link_to "delete", {:action => :delete_dump, :dump_id => id}, :confirm => "Are you sure?", :method => :post %> <%= link_to "restore", :action => :restore, :dump_id => id %>
diff --git a/app/views/container/list_vps.rhtml b/app/views/container/list_vps.rhtml new file mode 100644 index 0000000..1579365 --- /dev/null +++ b/app/views/container/list_vps.rhtml @@ -0,0 +1,51 @@ +<%= render :partial => 'links' %> +

You have <%= pluralize(@rows.size, "#{@status} container") %>

+ + + +<% i = 1 %> +<% z = 0 %> +<% for row in @rows -%> + <% i = 1 -i%> + "> + + + + + + + + + + + + + "> + + + + <% if row[2].include?("running") %> + + + <% else %> + + <% if session[:permission] == "admin" %> + + <% end %> + <% end %> + <% if session[:permission] == "admin" %> + + + <% end %> + + +<% end -%> + +
Distro Cnt ID Name No. Ps Status IP Address Hostname Owner
<%= link_to image_tag("icons/"+row[5].downcase+".png", :border => 0) , :controller => :inside_vps, :action => :view_vps, :vps_id => row[0] unless row[5].blank? %> <%=link_to row[0],:controller => :inside_vps, :action => :view_vps, :vps_id => row[0]%> <%= @names[z] %> <% z = z + 1 %> <%=link_to row[1],:controller => :inside_vps, :action => :running_processes, :vps_id =>row[0]%> <%=row[2]%> <%=row[3]%> <%=row[4]%> + <% vps = Vps.find_by_cnt_id(row[0]) %> + <% if vps %> + <%= link_to vps.user.name, :action => :vpses_of_user, :user_id => vps.user.id %> + <% else %> + <%= link_to "Assign to Owner", :action => :assign_to_owner, :vps_id =>row[0] %> + <% end %> +
<%= link_to "stop", :action => :stop_vps, :vps_id => row[0] %> <%= link_to "restart" , :action => :restart_vps, :vps_id => row[0] %> <%= link_to "start", :action => :start_vps, :vps_id => row[0] %> <%= link_to "destroy", {:action => :destroy_vps, :vps_id => row[0]}, :confirm => "Are you sure?", :method => :post %> <%= link_to "migrate", :action => :migrate_vps, :vps_id => row[0] %> <%= link_to "create backup", :action => :create_backup, :vps_id => row[0] %> <%= link_to "Change Owner", :action => :assign_to_owner, :vps_id =>row[0] %>
diff --git a/app/views/container/migrate_vps.rhtml b/app/views/container/migrate_vps.rhtml new file mode 100644 index 0000000..39ccea1 --- /dev/null +++ b/app/views/container/migrate_vps.rhtml @@ -0,0 +1,50 @@ +<%= render :partial => 'links' %> +

Virtual Environment Migration

+<% form_tag :action => "vzmigrate", :vps_id => @vps_id do %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Destination IP Address. : <%= text_field "vps", "ip", :size => 25 %>
Remove VE area on source Host Node for the successfully migrated VE. : + Yes <%= radio_button "vps", "r", "yes" %> + No <%= radio_button "vps", "r", "no", :checked => true %> +
Perform online (zero-downtime) migration. : + Yes <%= radio_button "vps", "online", "yes" %> + No <%= radio_button "vps", "online", "no" , :checked => true %> +
Do not clean synced destination VE private area in case of some error. : + Yes <%= radio_button "vps", "keepdst", "yes" %> + No <%= radio_button "vps", "keepdst", "no", :checked => true %> +
Show output details (verbose). : + Yes <%= radio_button "vps", "v", "yes" %> + No <%= radio_button "vps", "v", "no", :checked => true %> +
+ <%= submit_tag "migrate" %> +
+<% end %> + +

Warning: This operation may take several minutes, please wait.

+
diff --git a/app/views/container/new_vps.rhtml b/app/views/container/new_vps.rhtml new file mode 100644 index 0000000..a3acf1b --- /dev/null +++ b/app/views/container/new_vps.rhtml @@ -0,0 +1,14 @@ +<%= render :partial => 'links' %> +

Create New Container

+<%= error_messages_for :vps %> + + +<% form_tag :action => "create_vps" do %> + + <%= render :partial => 'form' %> + + + +
<%= submit_tag "Create"%>
+<% end %> + diff --git a/app/views/container/restore.html.erb b/app/views/container/restore.html.erb new file mode 100644 index 0000000..e53f0c4 --- /dev/null +++ b/app/views/container/restore.html.erb @@ -0,0 +1,17 @@ +<%= render :partial => 'links' %> +<%= render :partial => 'top' %> +
+ +

Restore Container <%= @dump_id %>

+ +<% form_tag :action => :restore_dump, :dump_id => @dump_id do %> +

+ Owner of the container: <%= select("dump","user_id", @users, :prompt => "Choose Owner") %> +

+

+ New Container ID: <%= text_field("dump","id") %> NOTE: The ID Must be different from <%= @dump_id %> or any other used container ids +

+

+ <%= submit_tag "Restore" %> +

+<% end %> diff --git a/app/views/inside_vps/_links.rhtml b/app/views/inside_vps/_links.rhtml new file mode 100644 index 0000000..4c3b1c0 --- /dev/null +++ b/app/views/inside_vps/_links.rhtml @@ -0,0 +1,22 @@ + diff --git a/app/views/inside_vps/adv_net.rhtml b/app/views/inside_vps/adv_net.rhtml new file mode 100644 index 0000000..4df975a --- /dev/null +++ b/app/views/inside_vps/adv_net.rhtml @@ -0,0 +1,58 @@ +<%= render :partial => 'links' %> +
+

Advanced Network Configuration

+ +<% form_tag :action => "edit_adv_net", :vps_id => @vps_id do %> + + + + + + + + <% v = @conf_file["NUMTCPSOCK"].split(":") %> + + + + + + + <% v = @conf_file["TCPSNDBUF"].split(":") %> + + + + + + + <% v = @conf_file["TCPRCVBUF"].split(":") %> + + + + + + + <% v = @conf_file["OTHERSOCKBUF"].split(":") %> + + + + + + + <% v = @conf_file["DGRAMRCVBUF"].split(":") %> + + + + + + + <% v = @conf_file["NUMOTHERSOCK"].split(":") %> + + + + + + +
Parameter Barrier limit
Number of TCP Sockets : <%= text_field "conf", "NUMTCPSOCK1", :value => v[0].gsub("\"", ""), :size => "20"%> <%= text_field "conf", "NUMTCPSOCK2", :value => v[1].gsub("\"", ""), :size => "20"%>
TCP Send Buffer Size : <%= text_field "conf", "TCPSNDBUF1", :value => v[0].gsub("\"", ""), :size => "20"%> <%= text_field "conf", "TCPSNDBUF2", :value => v[1].gsub("\"", ""), :size => "20"%>
TCP Recieve Buffer Size : <%= text_field "conf", "TCPRCVBUF1", :value => v[0].gsub("\"", ""), :size => "20"%> <%= text_field "conf", "TCPRCVBUF2", :value => v[1].gsub("\"", ""), :size => "20"%>
Other Socket Buffer Size : <%= text_field "conf", "OTHERSOCKBUF1", :value => v[0].gsub("\"", ""), :size => "20"%> <%= text_field "conf", "OTHERSOCKBUF2", :value => v[1].gsub("\"", ""), :size => "20"%>
UDP Recieve Buffer Size : <%= text_field "conf", "DGRAMRCVBUF1", :value => v[0].gsub("\"", ""), :size => "20"%> <%= text_field "conf", "DGRAMRCVBUF2", :value => v[1].gsub("\"", ""), :size => "20"%>
Number of Other Sockets : <%= text_field "conf", "NUMOTHERSOCK1", :value => v[0].gsub("\"", ""), :size => "20"%> <%= text_field "conf", "NUMOTHERSOCK2", :value => v[1].gsub("\"", ""), :size => "20"%>
<%= submit_tag "Edit"%>
+ +<% end %> +
diff --git a/app/views/inside_vps/basic_net.rhtml b/app/views/inside_vps/basic_net.rhtml new file mode 100644 index 0000000..f3030a8 --- /dev/null +++ b/app/views/inside_vps/basic_net.rhtml @@ -0,0 +1,77 @@ +<%= render :partial => 'links' %> +
+

Basic Network Configuration

+ + +<% form_tag :action => "edit_hostname", :vps_id => @vps_id do %> + + + + +<% end %> + + +
Hostname : + <% unless @conf_file["HOSTNAME"].blank? %> + <%= text_field "conf", "HOSTNAME", :value => @conf_file["HOSTNAME"].gsub("\"", "") %> + <% else %> + <%= text_field "conf", "HOSTNAME" %> + <% end %> + <%= submit_tag "Edit"%>
+
+ + + + + + <% unless @conf_file["IP_ADDRESS"].blank? %> + <% addresses = @conf_file["IP_ADDRESS"].split(" ") %> + <% for address in addresses %> + + + + + <% end %> + + + <% form_tag :action => "add_ip", :vps_id => @vps_id do %> + + + + <% end %> + <% else %> + + + + <% form_tag :action => "add_ip", :vps_id => @vps_id do %> + + + + <% end %> + <% end %> + +
IP Address(es)
<%= address.gsub("\"", "") %> <%= link_to "delete", {:action => :delete_ip, :ip => address.gsub("\"", ""), :vps_id => @vps_id}, :confirm => "Are you sure?", :method => :post %>
New IP Address : <%= text_field "conf", "IP_ADDRESS" %> <%= submit_tag "Add IP" %>
No IP addresses have been assigned.
New IP Address : <%= text_field "conf", "IP_ADDRESS" %> <%= submit_tag "Add IP" %>
+ +
+ + + + + + <% end %> + <% else %> + <% form_tag :action => "edit_nameserver", :vps_id => @vps_id do %> + + <% end %> + <% end %> + + +
Nameserver : + <% unless @conf_file["NAMESERVER"].blank? %> + <% form_tag :action => "edit_nameserver", :vps_id => @vps_id do %> + <%= text_field "conf", "NAMESERVER", :value => @conf_file["NAMESERVER"].gsub("\"", "") %> + <%= submit_tag "edit nameserver" %> + <%= text_field "conf", "NAMESERVER" %> + <%= submit_tag "edit nameserver" %> +
+
diff --git a/app/views/inside_vps/booting.rhtml b/app/views/inside_vps/booting.rhtml new file mode 100644 index 0000000..30dd374 --- /dev/null +++ b/app/views/inside_vps/booting.rhtml @@ -0,0 +1,28 @@ +<%= render :partial => 'links' %> + +

Enable/Disable Booting of Container <%= @vps_id %>

+
+ + +<% form_tag :action => :edit_booting, :vps_id => @vps_id do %> + + + + + + + + +<% end -%> + +
Enable booting on startup : + <% if @conf_file["ONBOOT"].include?("yes") %> + Yes <%= radio_button "conf", "booting", "yes", :checked => true %> + No <%= radio_button "conf", "booting", "no" %> + <% else %> + Yes <%= radio_button "conf", "booting", "yes" %> + No <%= radio_button "conf", "booting", "no", :checked => true %> + <% end %> +
<%= submit_tag "Change" %>
+ +
diff --git a/app/views/inside_vps/cpu_mgt.rhtml b/app/views/inside_vps/cpu_mgt.rhtml new file mode 100644 index 0000000..4a0cff7 --- /dev/null +++ b/app/views/inside_vps/cpu_mgt.rhtml @@ -0,0 +1,30 @@ +<%= render :partial => 'links' %> +
+ +

CPU Management Panel

+ +<% form_tag :action => "edit_cpu_mgt", :vps_id => @vps_id do %> + + + + + + + + + + + + + + <% v = @conf_file["NUMPROC"].split(":") %> + + + + + + +
Parameter Value
Guaranteed Minimum CPU : <%= text_field "conf", "CPUUNITS", :value => @conf_file["CPUUNITS"].gsub("\"", ""), :size => "20"%>
Number of processes : <%= text_field "conf", "NUMPROC1", :value => v[0].gsub("\"", ""), :size => "20"%> <%= text_field "conf", "NUMPROC2", :value => v[1].gsub("\"", ""), :size => "20"%>
<%= submit_tag "Edit"%>
+ +<% end %> +
diff --git a/app/views/inside_vps/disk_mgt.rhtml b/app/views/inside_vps/disk_mgt.rhtml new file mode 100644 index 0000000..bef237f --- /dev/null +++ b/app/views/inside_vps/disk_mgt.rhtml @@ -0,0 +1,34 @@ +<%= render :partial => 'links' %> +
+ +

Disk Management Panel

+<% form_tag :action => "edit_disk_mgt", :vps_id => @vps_id do %> + + + + + + + <% v = @conf_file["DISKSPACE"].split(":") %> + + + + + + + + <% v = @conf_file["DISKINODES"].split(":") %> + + + + + + + + + + + +
Parameter Barrier limit
Disk Space (KByte) : <%= text_field "conf", "DISKSPACE1", :value => v[0].gsub("\"", ""), :size => "20"%> <%= text_field "conf", "DISKSPACE2", :value => v[1].gsub("\"", ""), :size => "20"%>
Disk Inodes : <%= text_field "conf", "DISKINODES1", :value => v[0].gsub("\"", ""), :size => "20"%> <%= text_field "conf", "DISKINODES2", :value => v[1].gsub("\"", ""), :size => "20"%>
Quota Time : <%= text_field "conf", "QUOTATIME", :value => @conf_file["QUOTATIME"].gsub("\"", ""), :size => "20" unless @conf_file["QUOTATIME"].blank?%>
<%= submit_tag "Edit"%>
+<% end -%> +
diff --git a/app/views/inside_vps/edit_adv_net.rhtml b/app/views/inside_vps/edit_adv_net.rhtml new file mode 100644 index 0000000..22aaa8e --- /dev/null +++ b/app/views/inside_vps/edit_adv_net.rhtml @@ -0,0 +1,10 @@ +<%= @conf.inspect %> +
+
+<%= @conf_file.inspect %> + +

+<%= @conf_file.size%> +

+ +<%= @vps_id.inspect %> diff --git a/app/views/inside_vps/execute_cmd.rhtml b/app/views/inside_vps/execute_cmd.rhtml new file mode 100644 index 0000000..fafba01 --- /dev/null +++ b/app/views/inside_vps/execute_cmd.rhtml @@ -0,0 +1,19 @@ +<%= render :partial => 'links' %> + +

Execute Linux Commands in <%= @vps_id %>

+
+ +<% form_tag :action => :execute_cmd, :vps_id => @vps_id do %> +<%= text_field "command", "line" , :size => "40"%> +<%= submit_tag "RUN" %> +<% end -%> + + +

You executed: <%= @cmd %>

+ +

The output:

+ + + +
<%= @output.gsub(/\n/, '
') %>
+
diff --git a/app/views/inside_vps/memory_mgt.rhtml b/app/views/inside_vps/memory_mgt.rhtml new file mode 100644 index 0000000..550dc7c --- /dev/null +++ b/app/views/inside_vps/memory_mgt.rhtml @@ -0,0 +1,74 @@ +<%= render :partial => 'links' %> +
+ +

Memeory Management Panel

+ +<% form_tag :action => "edit_memory", :vps_id => @vps_id do %> + + + + + + + + <% v = @conf_file["KMEMSIZE"].split(":") %> + + + + + + + + <% v = @conf_file["LOCKEDPAGES"].split(":") %> + + + + + + + <% v = @conf_file["PRIVVMPAGES"].split(":") %> + + + + + + + <% v = @conf_file["SHMPAGES"].split(":") %> + + + + + + + <% v = @conf_file["PHYSPAGES"].split(":") %> + + + + + + + <% v = @conf_file["VMGUARPAGES"].split(":") %> + + + + + + + <% v = @conf_file["OOMGUARPAGES"].split(":") %> + + + + + + + <% v = @conf_file["DCACHESIZE"].split(":") %> + + + + + + +
Parameter Barrier limit
Kernel Memory Size : <%= text_field "conf", "KMEMSIZE1", :value => v[0].gsub("\"", ""), :size => "20"%> <%= text_field "conf", "KMEMSIZE2", :value => v[1].gsub("\"", ""), :size => "20"%>
Number of Locked Pages : <%= text_field "conf", "LOCKEDPAGES1", :value => v[0].gsub("\"", ""), :size => "20"%> <%= text_field "conf", "LOCKEDPAGES2", :value => v[1].gsub("\"", ""), :size => "20"%>
Private VM Pages : <%= text_field "conf", "PRIVVMPAGES1", :value => v[0].gsub("\"", ""), :size => "20"%> <%= text_field "conf", "PRIVVMPAGES2", :value => v[1].gsub("\"", ""), :size => "20"%>
Number of shm Pages : <%= text_field "conf", "SHMPAGES1", :value => v[0].gsub("\"", ""), :size => "20"%> <%= text_field "conf", "SHMPAGES2", :value => v[1].gsub("\"", ""), :size => "20"%>
Physical Pages : <%= text_field "conf", "PHYSPAGES1", :value => v[0].gsub("\"", ""), :size => "20"%> <%= text_field "conf", "PHYSPAGES2", :value => v[1].gsub("\"", ""), :size => "20"%>
VM Guar Pages : <%= text_field "conf", "VMGUARPAGES1", :value => v[0].gsub("\"", ""), :size => "20"%> <%= text_field "conf", "VMGUARPAGES2", :value => v[1].gsub("\"", ""), :size => "20"%>
OOM Guar Pages : <%= text_field "conf", "OOMGUARPAGES1", :value => v[0].gsub("\"", ""), :size => "20"%> <%= text_field "conf", "OOMGUARPAGES2", :value => v[1].gsub("\"", ""), :size => "20"%>
Disk Cache Size : <%= text_field "conf", "DCACHESIZE1", :value => v[0].gsub("\"", ""), :size => "20"%> <%= text_field "conf", "DCACHESIZE2", :value => v[1].gsub("\"", ""), :size => "20"%>
<%= submit_tag "Edit"%>
+ +<% end %> +
diff --git a/app/views/inside_vps/misc.rhtml b/app/views/inside_vps/misc.rhtml new file mode 100644 index 0000000..41640d3 --- /dev/null +++ b/app/views/inside_vps/misc.rhtml @@ -0,0 +1,52 @@ +<%= render :partial => 'links' %> +
+

Miscellaneous

+ +<% form_tag :action => "edit_misc", :vps_id => @vps_id do %> + + + + + + + + <% v = @conf_file["NUMFLOCK"].split(":") %> + + + + + + + + <% v = @conf_file["NUMPTY"].split(":") %> + + + + + + + <% v = @conf_file["NUMFILE"].split(":") %> + + + + + + + <% v = @conf_file["NUMSIGINFO"].split(":") %> + + + + + + + <% v = @conf_file["NUMIPTENT"].split(":") %> + + + + + + +
Parameter Barrier limit
Number of files lock : <%= text_field "conf", "NUMFLOCK1", :value => v[0].gsub("\"", ""), :size => "20"%> <%= text_field "conf", "NUMFLOCK2", :value => v[1].gsub("\"", ""), :size => "20"%>
Number of PTYs : <%= text_field "conf", "NUMPTY1", :value => v[0].gsub("\"", ""), :size => "20"%> <%= text_field "conf", "NUMPTY2", :value => v[1].gsub("\"", ""), :size => "20"%>
Number of Open Files : <%= text_field "conf", "NUMFILE1", :value => v[0].gsub("\"", ""), :size => "20"%> <%= text_field "conf", "NUMFILE2", :value => v[1].gsub("\"", ""), :size => "20"%>
Number of SIGINFO : <%= text_field "conf", "NUMSIGINFO1", :value => v[0].gsub("\"", ""), :size => "20"%> <%= text_field "conf", "NUMSIGINFO2", :value => v[1].gsub("\"", ""), :size => "20"%>
Number of IPTENT : <%= text_field "conf", "NUMIPTENT1", :value => v[0].gsub("\"", ""), :size => "20"%> <%= text_field "conf", "NUMIPTENT2", :value => v[1].gsub("\"", ""), :size => "20"%>
<%= submit_tag "Edit"%>
+ +<% end %> +
diff --git a/app/views/inside_vps/monitor_resources.rhtml b/app/views/inside_vps/monitor_resources.rhtml new file mode 100644 index 0000000..db53545 --- /dev/null +++ b/app/views/inside_vps/monitor_resources.rhtml @@ -0,0 +1,23 @@ +<%= render :partial => 'links' %> +
+ +

User Bean Counter for <%= @vps_id %>

+ + +<% for entry in @ubc -%> + <% values = entry.split(" ") %> + <% if values[5].to_i.zero? %> + + <% else %> + + <% end %> + + + + + + + +<% end -%> +
Resource Held Max held Barrier Limit Failcnt
<%= values[0] %> <%= values[1] %> <%= values[2] %> <%= values[3] %> <%= values[4] %> <%= values[5] %>
+ diff --git a/app/views/inside_vps/run_command.rhtml b/app/views/inside_vps/run_command.rhtml new file mode 100644 index 0000000..09d4c53 --- /dev/null +++ b/app/views/inside_vps/run_command.rhtml @@ -0,0 +1,9 @@ +<%= render :partial => 'links' %> +
+ +

Execute Linux Commands in <%= @vps_id %>

+<% form_tag :action => :execute_cmd, :vps_id => @vps_id do %> +<%= text_field "command", "line" , :size => "40"%> +<%= submit_tag "RUN" %> +<% end -%> +
diff --git a/app/views/inside_vps/running_processes.rhtml b/app/views/inside_vps/running_processes.rhtml new file mode 100644 index 0000000..e320d63 --- /dev/null +++ b/app/views/inside_vps/running_processes.rhtml @@ -0,0 +1,23 @@ +<%= render :partial => 'links' %> +
+ +

Running Processes for <%= @vps_id %>

+
+ + <% for entry in @procs -%> + <% values = entry.split(" ") %> + + + + + + + + + + + + + <% end -%> +
User PID %CPU %MEM VSZ RSS Stat Start Time Command
<%= values[0] %> <%= values[1] %> <%= values[2] %> <%= values[3] %> <%= values[4] %> <%= values[5] %> <%= values[7] %> <%= values[8] %> <%= values[9] %> <%= values[10] %>
+ diff --git a/app/views/inside_vps/services.html.erb b/app/views/inside_vps/services.html.erb new file mode 100644 index 0000000..5f95218 --- /dev/null +++ b/app/views/inside_vps/services.html.erb @@ -0,0 +1,28 @@ +<%= render :partial => 'links' %> +
+

All services in container <%= @vps_id %>

+ +
+ + <% for service in @all_services -%> + + + <% out = `vzctl exec #{@vps_id} /etc/init.d/#{service} status` %> + + + <% elsif out.include?('is stopped') %> + <%= link_to "start", :action => :start_service, :vps_id => @vps_id, :ser_name => service %> + + <% else %> + + <%= link_to "stop", :action => :stop_service, :vps_id => @vps_id, :ser_name => service %> + | <%= link_to "restart", :action => :start_service, :vps_id => @vps_id, :ser_name => service %> + | <%= link_to "start", :action => :start_service, :vps_id => @vps_id, :ser_name => service %> + <% end -%> + + <% end -%> +
Name Status Operations
<%= service %> <%= out %> + <% if out.include?('is running') %> + <%= link_to "stop", :action => :stop_service, :vps_id => @vps_id, :ser_name => service %> + | <%= link_to "restart", :action => :start_service, :vps_id => @vps_id, :ser_name => service %>
+
diff --git a/app/views/inside_vps/view_vps.rhtml b/app/views/inside_vps/view_vps.rhtml new file mode 100644 index 0000000..7103e61 --- /dev/null +++ b/app/views/inside_vps/view_vps.rhtml @@ -0,0 +1,34 @@ +<%= render :partial => 'links' %> +
+ +

General Information About Container <%= @vps_id %>

+ + + + + + + + + + <% form_tag :action => :change_name, :vps_id => @vps_id do %> + + <% end -%> + + + + + + + + + + + + + + +
Container : <%= image_tag("icons/"+@line[5].downcase+".png", :border => 0) unless @line[5].blank? %>
Container ID : <%= @vps_id %>
Name : <%= text_field "vps", "new_name" , :size => "15", :value => @name %> + <%= submit_tag "Change" %>
Container Status : <%= @line[2] %>
IP Address : <%= @line[3] %>
Hostname : <%= @line[4] %>
Uptime : <%= @uptime %>
+ +
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb new file mode 100644 index 0000000..34dfb9e --- /dev/null +++ b/app/views/layouts/application.html.erb @@ -0,0 +1,58 @@ + +WebVZ: OpenVZ Management Tool +<%= stylesheet_link_tag 'webvz' %> + + + + + + <% if flash[:notice] %> +
+

Notice

+

<%= flash[:notice].gsub("\n", "
") %>

+

+ <% end -%> + +
+
    + <%if session[:permission] == "admin" %> +
  • <%= link_to " Containers " , :controller => :container, :action => :list_vps, :status=> "all" %>
  • +
  • <%= link_to " OS-Templates ", :controller => :ostemplate, :action => :list_templates%>
  • +
  • <%= link_to "Configuration files ", :controller => :configfile, :action => :list_conf_file %>
  • +
  • <%= link_to " OpenVZ" , :controller => :openvz, :action => :vz_status%>
  • +
  • <%= link_to " Users ", :controller => :user, :action => :list %>
  • + <% end -%> + + <% if session[:permission] == "client" %> +
  • <%= link_to " Containers " , :controller => :user_containers, :action => :list_vps, :status=> "all", :id => session[:user_id] %>
  • +
  • <%= link_to " Personalize ", :controller => :user_information, :action => :view_user_by_user, :id => session[:user_id] %>
  • + <% end -%> + +
  • <%= link_to " About ", :controller => :menu, :action => :about %>
  • + + <%if session[:permission] == nil -%> +
  • <%= link_to " Login ", :controller => :login, :action => :sign_in %>
  • + <% end -%> + + <% if session[:user_name] %> +
  • <%= link_to " Logout (#{session[:user_name]})", :controller => :login, :action => :logout%>
  • + <% end %> +
+
+ +
+ <%= yield %> +
+ +

All Rights Reserved © Shuaib Zahda 2008

+ + + diff --git a/app/views/login/sign_in.rhtml b/app/views/login/sign_in.rhtml new file mode 100644 index 0000000..a713673 --- /dev/null +++ b/app/views/login/sign_in.rhtml @@ -0,0 +1,13 @@ +

Sign in

+<% form_tag do %> +

Username: +<%= text_field_tag :username, params[:username] %> +

+

+Password: +<%= password_field_tag :password, params[:password] %> +

+ +<%= submit_tag "Sign in" %> + +<% end %> \ No newline at end of file diff --git a/app/views/menu/about.rhtml b/app/views/menu/about.rhtml new file mode 100644 index 0000000..b94da79 --- /dev/null +++ b/app/views/menu/about.rhtml @@ -0,0 +1,13 @@ +
+<%= image_tag "webvz-logo.png", :alt => "WebVZ logo"%> + +

WebVZ version 2.0

+

Developer: Shuaib Zahda from Palestine

+

<%=mail_to "shuaib.zahda@gmail.com", "Email: shuaib.zahda@gmail.com" %>

+

<%=mail_to "webvz.info@gmail.com", "Email: webvz.info@gmail.com" %>

+ +

<%= link_to "WebVZ Official website", "http://webvz.sourceforge.net", :target => "_blank" %>

+

<%= link_to "WebVZ Google Group", "http://groups.google.com/group/webvz", :target => "_blank" %>

+ +

The containers' logos were borrowed from EasyVZ.

+

Special Thanks to Leila Daniel from Quantum Beez Sdn Bhd for the logo.

diff --git a/app/views/menu/index.rhtml b/app/views/menu/index.rhtml new file mode 100644 index 0000000..a65d336 --- /dev/null +++ b/app/views/menu/index.rhtml @@ -0,0 +1,35 @@ + +<%= image_tag "webvz-logo.png", :alt => "WebVZ logo"%> + +

WebVZ: OpenVZ Web Management Tool

+ +

WebVZ 2.0 Features

+ +

1. Create containers.

+

2. Start, Stop, Restart, and Destroy containers.

+

3. Migrate containers from Host Node to another Host Node.

+

4. Create and Manage configuration files.

+

5. Change the configuration of the containers on the fly.

+

6. Start, Stop, and Restart OpenVZ service.

+

7. Execute Commands inside the running containers and display the output on the same page.

+

8. list the stopped containers, the running containers and/or all of them.

+

9. Assign a name for each container.

+

10. User Accesss Module.

+

11. Add/delete multiple IP Addresses

+

12. Monitor container's resources (user_beancounter) and highlight the over used resource.

+

13. Assign root password.

+

13. Enable/Disable booting of the container.

+

14. Manage Operating System Templates (delete, download and copy).

+

15. Recreate a customized OS-Template from an existing container.

+

16. Copy OS-Template from a machine to another

+

17. Backup all containers

+

18. Backup all client's containers

+

19. Backup a specific container

+

20. restore containers

+

21. Access Control Level

+

22. Client can stop, start and restart his/her container

+

23. Client can run commands, observe, and monitor the containers

+

24. Ownership of containers

+

25. admin and client users

+

26. Security enforcement on who access what

+ diff --git a/app/views/openvz/vz_status.rhtml b/app/views/openvz/vz_status.rhtml new file mode 100644 index 0000000..25fdd61 --- /dev/null +++ b/app/views/openvz/vz_status.rhtml @@ -0,0 +1,11 @@ +

OpenVZ Service

+

<%= @status %>

+ +<% if @status.include?("running") -%> +

<%= link_to "Stop OpenVZ", :controller => :openvz, :action => :stop_vz %>

+

<%= link_to "Restart OpenVZ", :controller => :openvz, :action => :restart_vz %>

+<% else -%> +

<%= link_to "Start OpenVZ", :controller => :openvz, :action => :start_vz %>

+<% end -%> + +

kernel version: <%= `uname -r` %>

diff --git a/app/views/ostemplate/_links.rhtml b/app/views/ostemplate/_links.rhtml new file mode 100644 index 0000000..38ebbcb --- /dev/null +++ b/app/views/ostemplate/_links.rhtml @@ -0,0 +1,10 @@ + diff --git a/app/views/ostemplate/copy_template.rhtml b/app/views/ostemplate/copy_template.rhtml new file mode 100644 index 0000000..e9963fe --- /dev/null +++ b/app/views/ostemplate/copy_template.rhtml @@ -0,0 +1,28 @@ +<%= render :partial => 'links' %> + + + <% form_tag :action => :transfer_file, :name => @name do %> + + + + + + + + + + + + + + + + <% end %> +

Copy OS template from this computer to another computer

Template Name: <%= @name %>
Example: shuaib@10.0.1.15 OR shuaib@myserver
Destination Server Username: <%= text_field 'dest', 'username' %>
Destination Server IP address: <%= text_field 'dest', 'add' %>
<%= submit_tag "Transfer" %>
+ + +

Warning: This operation may take several minutes, please wait.

+

+Warning: This operation will be done without supplying the password to the destination server. This can be done by passing over the password of the destination server using ssh. Do the first part of this tutorial before preceeding in this operation. +

+
diff --git a/app/views/ostemplate/list_templates.rhtml b/app/views/ostemplate/list_templates.rhtml new file mode 100644 index 0000000..0ce1ed4 --- /dev/null +++ b/app/views/ostemplate/list_templates.rhtml @@ -0,0 +1,12 @@ +<%= render :partial => 'links' %> + + + <% for template in @templates %> + + + + + +<% end %> + +

All OS Templates

<%= template %> <%= link_to "delete",{:action => :delete_template, :name => template}, {:confirm => "Are you sure?", :post => true} %> <%= link_to "copy", :action => :copy_template, :name => template %>
diff --git a/app/views/ostemplate/recreate.rhtml b/app/views/ostemplate/recreate.rhtml new file mode 100644 index 0000000..b80269a --- /dev/null +++ b/app/views/ostemplate/recreate.rhtml @@ -0,0 +1,30 @@ +<%= render :partial => 'links' %> + +

Re-Create OS-Template from customized one.

+ + + + + +<% i = 1 %> +<% z = 0 %> +<% for row in @rows -%> + <% i = 1 -i%> + "> + + + + + + + + + +<% end -%> + +

You have <%= pluralize(@rows.size, "container") %>

Distro Container ID Name No. Processes Status IP Address Hostname
<%= image_tag("icons/"+row[5].downcase+".png", :border => 0) unless row[5].blank? %> <%= row[0] %> <%= @names[z] %> <% z = z + 1 %> <%=row[1]%> <%=row[2]%> <%=row[3]%> <%=row[4]%> <%= link_to "re-create OS-template", :action => :recreate_template, :vps_id => row[0] %>
+ + +

Warning: It is advised that you implement this operation on non production container since, this operation stops the container from functioning and deletes its all IP addresses. After the operation finishes you can restart the container and configure the IP addresses.

+
+ diff --git a/app/views/ostemplate/recreate_template.rhtml b/app/views/ostemplate/recreate_template.rhtml new file mode 100644 index 0000000..e343a08 --- /dev/null +++ b/app/views/ostemplate/recreate_template.rhtml @@ -0,0 +1,17 @@ +<%= render :partial => 'links' %> + + + <% form_tag :action => :make_new_template, :vps_id => @vps_id do %> + + + + + + + <% end %> +

Create New Customized Template

New Template Name: <%= text_field 'temp', 'name' %> e.g. centos-5-i386-[rails] replace rails with a meaningful name for you.
<%= submit_tag "Create" %>
+ + +

Warning: This operation may take several minutes, please wait.

+

Warning: It is advised that you implement this operation on non production container since, this operation stops the container from functioning and deletes its all IP addresses. After the operation finishes you can restart the container and configure the IP addresses.

+
diff --git a/app/views/user/_editForm.html.erb b/app/views/user/_editForm.html.erb new file mode 100644 index 0000000..ac43131 --- /dev/null +++ b/app/views/user/_editForm.html.erb @@ -0,0 +1,43 @@ + + <%= error_messages_for 'user' %> + + + + + + : + <%= text_field 'user', 'username' %> + + + + : + <%= text_field 'user', 'name' %> + + + + : + <%= country_select 'user', 'country', "choose country" %> + + + + : + <%= text_field 'user', 'email' %> + + + + : + <%= text_field 'user', 'phone' %> + + + + : + Administartion <%= radio_button 'user', 'authority', 'admin' %> Client <%= radio_button 'user', 'authority', 'client' %> + + + + : + Yes <%= radio_button 'user', 'activated', 'yes' %> No <%= radio_button 'user', 'activated', 'no' %> + + + + diff --git a/app/views/user/_form.rhtml b/app/views/user/_form.rhtml new file mode 100644 index 0000000..dccf3a5 --- /dev/null +++ b/app/views/user/_form.rhtml @@ -0,0 +1,48 @@ + + <%= error_messages_for 'user' %> + + + + + + : + <%= text_field 'user', 'username' %> + + + + : + <%= password_field 'user', 'password' %> + + + + : + <%= text_field 'user', 'name' %> + + + + : + <%= country_select 'user', 'country', "choose country" %> + + + + : + <%= text_field 'user', 'email' %> + + + + : + <%= text_field 'user', 'phone' %> + + + + : + Administartion <%= radio_button 'user', 'authority', 'admin' %> Client <%= radio_button 'user', 'authority', 'client' %> + + + + : + Yes <%= radio_button 'user', 'activated', 'yes' %> No <%= radio_button 'user', 'activated', 'no' %> + + + + diff --git a/app/views/user/_links.rhtml b/app/views/user/_links.rhtml new file mode 100644 index 0000000..ef96ece --- /dev/null +++ b/app/views/user/_links.rhtml @@ -0,0 +1,11 @@ + diff --git a/app/views/user/change_password.html.erb b/app/views/user/change_password.html.erb new file mode 100644 index 0000000..1b69f8d --- /dev/null +++ b/app/views/user/change_password.html.erb @@ -0,0 +1,21 @@ +<%= render :partial => 'links' %> +

Change the password of <%= @user.username %>

+ + + +<% form_tag :action => :update_password, :id => @user.id do %> + + + + + + + + + + + + + +<% end %> +
Username : <%= @user.username %>
New Password : <%= password_field 'password', 'p1', :size => "20" %>
Re-type New Password : <%= password_field 'password', 'p2', :size => "20" %>
<%= submit_tag "Change Password" %>
diff --git a/app/views/user/edit.rhtml b/app/views/user/edit.rhtml new file mode 100644 index 0000000..6a4dd05 --- /dev/null +++ b/app/views/user/edit.rhtml @@ -0,0 +1,18 @@ +<%= render :partial => 'links' %> +

Editing user

+ +<% form_tag :action => 'update', :id => @user do %> + + + <%= render :partial => 'editForm' %> + + + + + + +
<%= link_to "Change Password", :action => :change_password, :id => @user.id %>
<%= submit_tag 'Edit' %>
+<% end %> + +<%= link_to 'Show', :action => 'show', :id => @user %> | +<%= link_to 'Back', :action => 'list' %> diff --git a/app/views/user/list.rhtml b/app/views/user/list.rhtml new file mode 100644 index 0000000..994c9b4 --- /dev/null +++ b/app/views/user/list.rhtml @@ -0,0 +1,26 @@ +<%= render :partial => 'links' %> +

Listing users

+ + + + <%# for column in User.content_columns %> + + + + <%# end %> + + +<% for user in @users %> + + + + + + + + +<% end %> +
NameUsernameAuthority
<%= link_to user.name, :action => 'show', :id => user %> <%= user.username %> <%= user.authority %> <%= link_to 'Edit', :action => 'edit', :id => user %><%= link_to 'Change Password', :action => 'change_password', :id => user %>
+ +<%#= link_to 'Previous page', { :page => @user_pages.current.previous } if @user_pages.current.previous %> +<%#= link_to 'Next page', { :page => @user_pages.current.next } if @user_pages.current.next %> diff --git a/app/views/user/new.rhtml b/app/views/user/new.rhtml new file mode 100644 index 0000000..3a45d8f --- /dev/null +++ b/app/views/user/new.rhtml @@ -0,0 +1,11 @@ +<%= render :partial => 'links' %> +

New user

+ +<% form_tag :action => 'create' do %> + + <%= render :partial => 'form' %> + + + +
<%= submit_tag "Create" %>
+<% end %> diff --git a/app/views/user/show.rhtml b/app/views/user/show.rhtml new file mode 100644 index 0000000..af99008 --- /dev/null +++ b/app/views/user/show.rhtml @@ -0,0 +1,13 @@ +<%= render :partial => 'links' %> + +<% for column in User.content_columns %> + + + + +<% end %> +
<%= column.human_name %> + : <%=h @user.send(column.name) %>
+ +<%= link_to 'Edit', :action => 'edit', :id => @user %> | +<%= link_to 'Back', :action => 'list' %> diff --git a/app/views/user_containers/_links.html.erb b/app/views/user_containers/_links.html.erb new file mode 100644 index 0000000..7c34966 --- /dev/null +++ b/app/views/user_containers/_links.html.erb @@ -0,0 +1,9 @@ + diff --git a/app/views/user_containers/list_vps.rhtml b/app/views/user_containers/list_vps.rhtml new file mode 100644 index 0000000..495f735 --- /dev/null +++ b/app/views/user_containers/list_vps.rhtml @@ -0,0 +1,29 @@ +<%= render :partial => 'links' %> +

<%= session[:name] %>, You have <%= pluralize(@rows.size, "container") %>

+ + + +<% i = 1 %> +<% z = 0 %> +<% for row in @rows -%> + <% i = 1 -i%> + "> + + + + + + + + + + <% if row[2].include?("running") %> + + + <% else %> + + <% end %> + +<% end -%> + +
Distribution Container ID Name No. Processes Status IP Address Hostname
<%= link_to image_tag("icons/"+row[5].downcase+".png", :border => 0) , :controller => :user_inside_vps, :action => :view_vps, :id => session[:user_id], :vps_id => row[0] unless row[5].blank? %> <%=link_to row[0], :controller => :user_inside_vps, :action => :view_vps, :id => session[:user_id], :vps_id => row[0]%> <%= @names[z] %> <% z = z + 1 %> <%=link_to row[1], :controller => :user_inside_vps, :action => :running_processes, :id =>session[:user_id], :vps_id =>row[0]%> <%=row[2]%> <%=row[3]%> <%=row[4]%> <%= link_to "stop", :action => :stop_vps, :vps_id => row[0], :id => session[:user_id] %> <%= link_to "restart", :action => :restart_vps, :vps_id => row[0], :id => session[:user_id] %> <%= link_to "start", :action => :start_vps, :vps_id => row[0], :id => session[:user_id] %>
diff --git a/app/views/user_information/_form.html.erb b/app/views/user_information/_form.html.erb new file mode 100644 index 0000000..dc3c53d --- /dev/null +++ b/app/views/user_information/_form.html.erb @@ -0,0 +1,27 @@ +<%= error_messages_for 'user' %> + + + + + + : + <%= text_field 'user', 'name' %> + + + + : + <%= country_select 'user', 'country', "choose country" %> + + + + : + <%= text_field 'user', 'email' %> + + + + : + <%= text_field 'user', 'phone' %> + + + + diff --git a/app/views/user_information/change_password.html.erb b/app/views/user_information/change_password.html.erb new file mode 100644 index 0000000..7e5f1b8 --- /dev/null +++ b/app/views/user_information/change_password.html.erb @@ -0,0 +1,23 @@ +

Change the password of <%= @user.username %>

+ + + +<% form_tag :action => :update_password, :id => @user.id do %> + + + + + + + + + + + + + + + + +<% end %> +
Username : <%= @user.username %>
Current Password : <%= password_field 'password', 'current', :size => "20" %>
New Password : <%= password_field 'password', 'p1', :size => "20" %>
Re-type New Password : <%= password_field 'password', 'p2', :size => "20" %>
<%= submit_tag "Change Password" %>
diff --git a/app/views/user_information/edit.html.erb b/app/views/user_information/edit.html.erb new file mode 100644 index 0000000..08f3486 --- /dev/null +++ b/app/views/user_information/edit.html.erb @@ -0,0 +1,14 @@ +

Editing Your Personal Information

+ +<% form_tag :action => 'update', :id => @user do %> + + <%= render :partial => 'form' %> + + + + + + +
<%= link_to "Change Password", :action => :change_password, :id => @user.id %>
<%= submit_tag 'Edit' %>
+<% end %> + diff --git a/app/views/user_information/view_user_by_user.html.erb b/app/views/user_information/view_user_by_user.html.erb new file mode 100644 index 0000000..096a5a2 --- /dev/null +++ b/app/views/user_information/view_user_by_user.html.erb @@ -0,0 +1,25 @@ +

User Information

+

+ <%= link_to "Edit profile", :action => :edit , :id => @user.id %> | + <%= link_to "Change Password", :action => :change_password, :id => @user.id %> +

+ + + + + + + + + + + + + + + + + + + +
Username : <%= @user.username %>
Name : <%= @user.name %>
Country : <%= @user.country %>
E-mail : <%= @user.email %>
Phone Number : <%= @user.phone %>
You are a member since : <%= @user.created_at %>
diff --git a/app/views/user_inside_vps/_links.html.erb b/app/views/user_inside_vps/_links.html.erb new file mode 100644 index 0000000..e66c059 --- /dev/null +++ b/app/views/user_inside_vps/_links.html.erb @@ -0,0 +1,11 @@ + diff --git a/app/views/user_inside_vps/execute_cmd.html.erb b/app/views/user_inside_vps/execute_cmd.html.erb new file mode 100644 index 0000000..fafba01 --- /dev/null +++ b/app/views/user_inside_vps/execute_cmd.html.erb @@ -0,0 +1,19 @@ +<%= render :partial => 'links' %> + +

Execute Linux Commands in <%= @vps_id %>

+
+ +<% form_tag :action => :execute_cmd, :vps_id => @vps_id do %> +<%= text_field "command", "line" , :size => "40"%> +<%= submit_tag "RUN" %> +<% end -%> + + +

You executed: <%= @cmd %>

+ +

The output:

+ + + +
<%= @output.gsub(/\n/, '
') %>
+
diff --git a/app/views/user_inside_vps/monitor_resources.html.erb b/app/views/user_inside_vps/monitor_resources.html.erb new file mode 100644 index 0000000..db53545 --- /dev/null +++ b/app/views/user_inside_vps/monitor_resources.html.erb @@ -0,0 +1,23 @@ +<%= render :partial => 'links' %> +
+ +

User Bean Counter for <%= @vps_id %>

+ + +<% for entry in @ubc -%> + <% values = entry.split(" ") %> + <% if values[5].to_i.zero? %> + + <% else %> + + <% end %> + + + + + + + +<% end -%> +
Resource Held Max held Barrier Limit Failcnt
<%= values[0] %> <%= values[1] %> <%= values[2] %> <%= values[3] %> <%= values[4] %> <%= values[5] %>
+ diff --git a/app/views/user_inside_vps/run_command.html.erb b/app/views/user_inside_vps/run_command.html.erb new file mode 100644 index 0000000..28626a1 --- /dev/null +++ b/app/views/user_inside_vps/run_command.html.erb @@ -0,0 +1,9 @@ +<%= render :partial => 'links' %> +
+ +

Execute Linux Commands in <%= @vps_id %>

+<% form_tag :action => :execute_cmd, :vps_id => @vps_id, :id => session[:user_id] do %> +<%= text_field "command", "line" , :size => "40"%> +<%= submit_tag "RUN" %> +<% end -%> +
diff --git a/app/views/user_inside_vps/running_processes.html.erb b/app/views/user_inside_vps/running_processes.html.erb new file mode 100644 index 0000000..e320d63 --- /dev/null +++ b/app/views/user_inside_vps/running_processes.html.erb @@ -0,0 +1,23 @@ +<%= render :partial => 'links' %> +
+ +

Running Processes for <%= @vps_id %>

+
+ + <% for entry in @procs -%> + <% values = entry.split(" ") %> + + + + + + + + + + + + + <% end -%> +
User PID %CPU %MEM VSZ RSS Stat Start Time Command
<%= values[0] %> <%= values[1] %> <%= values[2] %> <%= values[3] %> <%= values[4] %> <%= values[5] %> <%= values[7] %> <%= values[8] %> <%= values[9] %> <%= values[10] %>
+ diff --git a/app/views/user_inside_vps/services.html.erb b/app/views/user_inside_vps/services.html.erb new file mode 100644 index 0000000..f28b59a --- /dev/null +++ b/app/views/user_inside_vps/services.html.erb @@ -0,0 +1,26 @@ +<%= render :partial => 'links' %> +
+

All services in container <%= @vps_id %>

+ +
+ + <% for service in @all_services -%> + + + <% out = `vzctl exec #{@vps_id} /etc/init.d/#{service} status` %> + + + <% elsif out.include?('is stopped') %> + <%= link_to "start", :action => :start_service, :vps_id => @vps_id, :ser_name => service, :id => session[:user_id] %> + <% else %> + <%= link_to "stop", :action => :stop_service, :vps_id => @vps_id, :ser_name => service, :id => session[:user_id] %> + | <%= link_to "restart", :action => :start_service, :vps_id => @vps_id, :ser_name => service, :id => session[:user_id] %> + | <%= link_to "start", :action => :start_service, :vps_id => @vps_id, :ser_name => service, :id => session[:user_id] %> + <% end -%> + + <% end -%> +
Name Status Operations
<%= service %> <%= out %> + <% if out.include?('is running') %> + <%= link_to "stop", :action => :stop_service, :vps_id => @vps_id, :ser_name => service, :id => session[:user_id] %> + | <%= link_to "restart", :action => :start_service, :vps_id => @vps_id, :ser_name => service, :id => session[:user_id] %>
+
diff --git a/app/views/user_inside_vps/view_vps.html.erb b/app/views/user_inside_vps/view_vps.html.erb new file mode 100644 index 0000000..538da5b --- /dev/null +++ b/app/views/user_inside_vps/view_vps.html.erb @@ -0,0 +1,31 @@ +<%= render :partial => 'links' %> +
+ +

General Information About Container <%= @vps_id %>

+ + + + + + + + + + + + + + + + + + + + + + + + +
Container : <%= image_tag("icons/"+@line[5].downcase+".png", :border => 0) unless @line[5].blank? %>
Container ID : <%= @vps_id %>
Name : <%= @name %>
Container Status : <%= @line[2] %>
IP Address : <%= @line[3] %>
Hostname : <%= @line[4] %>
Uptime : <%= @uptime %>
+ +
diff --git a/config/boot.rb b/config/boot.rb new file mode 100644 index 0000000..5697cc1 --- /dev/null +++ b/config/boot.rb @@ -0,0 +1,109 @@ +# Don't change this file! +# Configure your app in config/environment.rb and config/environments/*.rb + +RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT) + +module Rails + class << self + def boot! + unless booted? + preinitialize + pick_boot.run + end + end + + def booted? + defined? Rails::Initializer + end + + def pick_boot + (vendor_rails? ? VendorBoot : GemBoot).new + end + + def vendor_rails? + File.exist?("#{RAILS_ROOT}/vendor/rails") + end + + # FIXME : Ruby 1.9 + def preinitialize + load(preinitializer_path) if File.exists?(preinitializer_path) + end + + def preinitializer_path + "#{RAILS_ROOT}/config/preinitializer.rb" + end + end + + class Boot + def run + load_initializer + Rails::Initializer.run(:set_load_path) + end + end + + class VendorBoot < Boot + def load_initializer + require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer" + end + end + + class GemBoot < Boot + def load_initializer + self.class.load_rubygems + load_rails_gem + require 'initializer' + end + + def load_rails_gem + if version = self.class.gem_version + gem 'rails', version + else + gem 'rails' + end + rescue Gem::LoadError => load_error + $stderr.puts %(Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.) + exit 1 + end + + class << self + def rubygems_version + Gem::RubyGemsVersion if defined? Gem::RubyGemsVersion + end + + def gem_version + if defined? RAILS_GEM_VERSION + RAILS_GEM_VERSION + elsif ENV.include?('RAILS_GEM_VERSION') + ENV['RAILS_GEM_VERSION'] + else + parse_gem_version(read_environment_rb) + end + end + + def load_rubygems + require 'rubygems' + + unless rubygems_version >= '0.9.4' + $stderr.puts %(Rails requires RubyGems >= 0.9.4 (you have #{rubygems_version}). Please `gem update --system` and try again.) + exit 1 + end + + rescue LoadError + $stderr.puts %(Rails requires RubyGems >= 0.9.4. Please install RubyGems and try again: http://rubygems.rubyforge.org) + exit 1 + end + + def parse_gem_version(text) + $1 if text =~ /^[^#]*RAILS_GEM_VERSION\s*=\s*["']([!~<>=]*\s*[\d.]+)["']/ + end + + private + def read_environment_rb + File.read("#{RAILS_ROOT}/config/environment.rb") + end + end + end +end + +# All that for this: +Rails.boot! diff --git a/config/database.yml b/config/database.yml new file mode 100644 index 0000000..2db01c0 --- /dev/null +++ b/config/database.yml @@ -0,0 +1,19 @@ +# SQLite version 3.x +# gem install sqlite3-ruby (not necessary on OS X Leopard) +development: + adapter: sqlite3 + database: db/development.sqlite3 + timeout: 5000 + +# Warning: The database defined as 'test' will be erased and +# re-generated from your development database when you run 'rake'. +# Do not set this db to the same as development or production. +test: + adapter: sqlite3 + database: db/test.sqlite3 + timeout: 5000 + +production: + adapter: sqlite3 + database: db/production.sqlite3 + timeout: 5000 diff --git a/config/environment.rb b/config/environment.rb new file mode 100644 index 0000000..624decc --- /dev/null +++ b/config/environment.rb @@ -0,0 +1,59 @@ +# Be sure to restart your server when you modify this file + +# Uncomment below to force Rails into production mode when +# you don't control web/app server and can't set it the proper way +# ENV['RAILS_ENV'] ||= 'production' + +# Specifies gem version of Rails to use when vendor/rails is not present +RAILS_GEM_VERSION = '2.1.0' unless defined? RAILS_GEM_VERSION + +# Bootstrap the Rails environment, frameworks, and default configuration +require File.join(File.dirname(__FILE__), 'boot') + +Rails::Initializer.run do |config| + # Settings in config/environments/* take precedence over those specified here. + # Application configuration should go into files in config/initializers + # -- all .rb files in that directory are automatically loaded. + # See Rails::Configuration for more options. + + # Skip frameworks you're not going to use (only works if using vendor/rails). + # To use Rails without a database, you must remove the Active Record framework + # config.frameworks -= [ :active_record, :active_resource, :action_mailer ] + + # Only load the plugins named here, in the order given. By default, all plugins + # in vendor/plugins are loaded in alphabetical order. + # :all can be used as a placeholder for all plugins not explicitly named + # config.plugins = [ :exception_notification, :ssl_requirement, :all ] + + # Add additional load paths for your own custom dirs + # config.load_paths += %W( #{RAILS_ROOT}/extras ) + + # Force all environments to use the same logger level + # (by default production uses :info, the others :debug) + # config.log_level = :debug + + # Your secret key for verifying cookie session data integrity. + # If you change this key, all old sessions will become invalid! + # Make sure the secret is at least 30 characters and all random, + # no regular words or you'll be exposed to dictionary attacks. + config.action_controller.session = { + :session_key => '_webvz_session', + :secret => '0aa7edb1472172ca32b291965bc707074d2009f1c2650e47c74c7394ffc1f6b566d63eb658f5a0258798e6b9c9e647b81691869ff7f083f9d5c0c82eabffe518' + } + + # Use the database for sessions instead of the cookie-based default, + # which shouldn't be used to store highly confidential information + # (create the session table with 'rake db:sessions:create') + # config.action_controller.session_store = :active_record_store + + # Use SQL instead of Active Record's schema dumper when creating the test database. + # This is necessary if your schema can't be completely dumped by the schema dumper, + # like if you have constraints or database-specific column types + # config.active_record.schema_format = :sql + + # Activate observers that should always be running + # config.active_record.observers = :cacher, :garbage_collector + + # Make Active Record use UTC-base instead of local time + # config.active_record.default_timezone = :utc +end diff --git a/config/environments/development.rb b/config/environments/development.rb new file mode 100644 index 0000000..09a451f --- /dev/null +++ b/config/environments/development.rb @@ -0,0 +1,18 @@ +# Settings specified here will take precedence over those in config/environment.rb + +# In the development environment your application's code is reloaded on +# every request. This slows down response time but is perfect for development +# since you don't have to restart the webserver when you make code changes. +config.cache_classes = false + +# Log error messages when you accidentally call methods on nil. +config.whiny_nils = true + +# Show full error reports and disable caching +config.action_controller.consider_all_requests_local = true +config.action_view.debug_rjs = true +config.action_controller.perform_caching = false +config.action_view.cache_template_extensions = false + +# Don't care if the mailer can't send +config.action_mailer.raise_delivery_errors = false \ No newline at end of file diff --git a/config/environments/production.rb b/config/environments/production.rb new file mode 100644 index 0000000..91f541c --- /dev/null +++ b/config/environments/production.rb @@ -0,0 +1,19 @@ +# Settings specified here will take precedence over those in config/environment.rb + +# The production environment is meant for finished, "live" apps. +# Code is not reloaded between requests +config.cache_classes = true + +# Use a different logger for distributed setups +# config.logger = SyslogLogger.new + +# Full error reports are disabled and caching is turned on +config.action_controller.consider_all_requests_local = false +config.action_controller.perform_caching = true +config.action_view.cache_template_loading = true + +# Enable serving of images, stylesheets, and javascripts from an asset server +# config.action_controller.asset_host = "http://assets.example.com" + +# Disable delivery errors, bad email addresses will be ignored +# config.action_mailer.raise_delivery_errors = false diff --git a/config/environments/test.rb b/config/environments/test.rb new file mode 100644 index 0000000..58850a7 --- /dev/null +++ b/config/environments/test.rb @@ -0,0 +1,22 @@ +# Settings specified here will take precedence over those in config/environment.rb + +# The test environment is used exclusively to run your application's +# test suite. You never need to work with it otherwise. Remember that +# your test database is "scratch space" for the test suite and is wiped +# and recreated between test runs. Don't rely on the data there! +config.cache_classes = true + +# Log error messages when you accidentally call methods on nil. +config.whiny_nils = true + +# Show full error reports and disable caching +config.action_controller.consider_all_requests_local = true +config.action_controller.perform_caching = false + +# Disable request forgery protection in test environment +config.action_controller.allow_forgery_protection = false + +# Tell ActionMailer not to deliver emails to the real world. +# The :test delivery method accumulates sent emails in the +# ActionMailer::Base.deliveries array. +config.action_mailer.delivery_method = :test diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb new file mode 100644 index 0000000..09158b8 --- /dev/null +++ b/config/initializers/inflections.rb @@ -0,0 +1,10 @@ +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format +# (all these examples are active by default): +# Inflector.inflections do |inflect| +# inflect.plural /^(ox)$/i, '\1en' +# inflect.singular /^(ox)en/i, '\1' +# inflect.irregular 'person', 'people' +# inflect.uncountable %w( fish sheep ) +# end diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb new file mode 100644 index 0000000..72aca7e --- /dev/null +++ b/config/initializers/mime_types.rb @@ -0,0 +1,5 @@ +# Be sure to restart your server when you modify this file. + +# Add new mime types for use in respond_to blocks: +# Mime::Type.register "text/richtext", :rtf +# Mime::Type.register_alias "text/html", :iphone diff --git a/config/routes.rb b/config/routes.rb new file mode 100644 index 0000000..ec72e49 --- /dev/null +++ b/config/routes.rb @@ -0,0 +1,35 @@ +ActionController::Routing::Routes.draw do |map| + # The priority is based upon order of creation: first created -> highest priority. + + # Sample of regular route: + # map.connect 'products/:id', :controller => 'catalog', :action => 'view' + # Keep in mind you can assign values other than :controller and :action + + # Sample of named route: + # map.purchase 'products/:id/purchase', :controller => 'catalog', :action => 'purchase' + # This route can be invoked with purchase_url(:id => product.id) + + # Sample resource route (maps HTTP verbs to controller actions automatically): + # map.resources :products + + # Sample resource route with options: + # map.resources :products, :member => { :short => :get, :toggle => :post }, :collection => { :sold => :get } + + # Sample resource route with sub-resources: + # map.resources :products, :has_many => [ :comments, :sales ], :has_one => :seller + + # Sample resource route within a namespace: + # map.namespace :admin do |admin| + # # Directs /admin/products/* to Admin::ProductsController (app/controllers/admin/products_controller.rb) + # admin.resources :products + # end + + # You can have the root of your site routed with map.root -- just remember to delete public/index.html. + map.root :controller => "menu" + + # See how all your routes lay out with "rake routes" + + # Install the default routes as the lowest priority. + map.connect ':controller/:action/:id' + map.connect ':controller/:action/:id.:format' +end diff --git a/db/development.sqlite3 b/db/development.sqlite3 new file mode 100644 index 0000000..18c03ff Binary files /dev/null and b/db/development.sqlite3 differ diff --git a/db/migrate/001_create_vps.rb b/db/migrate/001_create_vps.rb new file mode 100644 index 0000000..715ae69 --- /dev/null +++ b/db/migrate/001_create_vps.rb @@ -0,0 +1,13 @@ +class CreateVps < ActiveRecord::Migration + def self.up + create_table :vps do |t| + t.column :cnt_id, :integer + t.column :user_id, :integer + t.timestamps + end + end + + def self.down + drop_table :vps + end +end diff --git a/db/migrate/002_create_users.rb b/db/migrate/002_create_users.rb new file mode 100644 index 0000000..b4ef518 --- /dev/null +++ b/db/migrate/002_create_users.rb @@ -0,0 +1,20 @@ +class CreateUsers < ActiveRecord::Migration + def self.up + create_table :users do |t| + t.column :username, :string + t.column :password, :string + t.column :created_at, :date + t.column :name, :string + t.column :email, :string + t.column :language, :string + t.column :template, :string + t.column :authority, :string + t.column :phone, :string + t.timestamps + end + end + + def self.down + drop_table :users + end +end diff --git a/db/migrate/20080906232241_add_country_to_user.rb b/db/migrate/20080906232241_add_country_to_user.rb new file mode 100644 index 0000000..1283419 --- /dev/null +++ b/db/migrate/20080906232241_add_country_to_user.rb @@ -0,0 +1,9 @@ +class AddCountryToUser < ActiveRecord::Migration + def self.up + add_column :users, :country, :string + end + + def self.down + remove_column :users, :country + end +end diff --git a/db/migrate/20080906232725_add_activation_to_user.rb b/db/migrate/20080906232725_add_activation_to_user.rb new file mode 100644 index 0000000..ec6eeb1 --- /dev/null +++ b/db/migrate/20080906232725_add_activation_to_user.rb @@ -0,0 +1,9 @@ +class AddActivationToUser < ActiveRecord::Migration + def self.up + add_column :users, :activated, :string + end + + def self.down + remove_column :users, :activated + end +end diff --git a/db/schema.rb b/db/schema.rb new file mode 100644 index 0000000..c0283b8 --- /dev/null +++ b/db/schema.rb @@ -0,0 +1,36 @@ +# This file is auto-generated from the current state of the database. Instead of editing this file, +# please use the migrations feature of Active Record to incrementally modify your database, and +# then regenerate this schema definition. +# +# Note that this schema.rb definition is the authoritative source for your database schema. If you need +# to create the application database on another system, you should be using db:schema:load, not running +# all the migrations from scratch. The latter is a flawed and unsustainable approach (the more migrations +# you'll amass, the slower it'll run and the greater likelihood for issues). +# +# It's strongly recommended to check this file into your version control system. + +ActiveRecord::Schema.define(:version => 20080906232725) do + + create_table "users", :force => true do |t| + t.string "username" + t.string "password" + t.date "created_at" + t.string "name" + t.string "email" + t.string "language" + t.string "template" + t.string "authority" + t.string "phone" + t.datetime "updated_at" + t.string "country" + t.string "activated" + end + + create_table "vps", :force => true do |t| + t.integer "cnt_id" + t.integer "user_id" + t.datetime "created_at" + t.datetime "updated_at" + end + +end diff --git a/doc/README_FOR_APP b/doc/README_FOR_APP new file mode 100644 index 0000000..fe41f5c --- /dev/null +++ b/doc/README_FOR_APP @@ -0,0 +1,2 @@ +Use this README file to introduce your application and point to useful places in the API for learning more. +Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries. diff --git a/log/development.log b/log/development.log new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/log/development.log @@ -0,0 +1 @@ + diff --git a/log/production.log b/log/production.log new file mode 100644 index 0000000..e69de29 diff --git a/log/server.log b/log/server.log new file mode 100644 index 0000000..e69de29 diff --git a/log/test.log b/log/test.log new file mode 100644 index 0000000..e69de29 diff --git a/public/.htaccess b/public/.htaccess new file mode 100644 index 0000000..d9d211c --- /dev/null +++ b/public/.htaccess @@ -0,0 +1,40 @@ +# General Apache options +AddHandler fastcgi-script .fcgi +AddHandler cgi-script .cgi +Options +FollowSymLinks +ExecCGI + +# If you don't want Rails to look in certain directories, +# use the following rewrite rules so that Apache won't rewrite certain requests +# +# Example: +# RewriteCond %{REQUEST_URI} ^/notrails.* +# RewriteRule .* - [L] + +# Redirect all requests not available on the filesystem to Rails +# By default the cgi dispatcher is used which is very slow +# +# For better performance replace the dispatcher with the fastcgi one +# +# Example: +# RewriteRule ^(.*)$ dispatch.fcgi [QSA,L] +RewriteEngine On + +# If your Rails application is accessed via an Alias directive, +# then you MUST also set the RewriteBase in this htaccess file. +# +# Example: +# Alias /myrailsapp /path/to/myrailsapp/public +# RewriteBase /myrailsapp + +RewriteRule ^$ index.html [QSA] +RewriteRule ^([^.]+)$ $1.html [QSA] +RewriteCond %{REQUEST_FILENAME} !-f +RewriteRule ^(.*)$ dispatch.cgi [QSA,L] + +# In case Rails experiences terminal errors +# Instead of displaying this message you can supply a file here which will be rendered instead +# +# Example: +# ErrorDocument 500 /500.html + +ErrorDocument 500 "

Application error

Rails application failed to start properly" diff --git a/public/404.html b/public/404.html new file mode 100644 index 0000000..eff660b --- /dev/null +++ b/public/404.html @@ -0,0 +1,30 @@ + + + + + + + The page you were looking for doesn't exist (404) + + + + + +
+

The page you were looking for doesn't exist.

+

You may have mistyped the address or the page may have moved.

+
+ + \ No newline at end of file diff --git a/public/422.html b/public/422.html new file mode 100644 index 0000000..b54e4a3 --- /dev/null +++ b/public/422.html @@ -0,0 +1,30 @@ + + + + + + + The change you wanted was rejected (422) + + + + + +
+

The change you wanted was rejected.

+

Maybe you tried to change something you didn't have access to.

+
+ + \ No newline at end of file diff --git a/public/500.html b/public/500.html new file mode 100644 index 0000000..0e9c14f --- /dev/null +++ b/public/500.html @@ -0,0 +1,30 @@ + + + + + + + We're sorry, but something went wrong (500) + + + + + +
+

We're sorry, but something went wrong.

+

We've been notified about this issue and we'll take a look at it shortly.

+
+ + \ No newline at end of file diff --git a/public/dispatch.cgi b/public/dispatch.cgi new file mode 100755 index 0000000..9b5ae76 --- /dev/null +++ b/public/dispatch.cgi @@ -0,0 +1,10 @@ +#!/usr/local/bin/ruby + +require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT) + +# If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like: +# "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher" -- otherwise performance is severely impaired +require "dispatcher" + +ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun) +Dispatcher.dispatch \ No newline at end of file diff --git a/public/dispatch.fcgi b/public/dispatch.fcgi new file mode 100755 index 0000000..65188f3 --- /dev/null +++ b/public/dispatch.fcgi @@ -0,0 +1,24 @@ +#!/usr/local/bin/ruby +# +# You may specify the path to the FastCGI crash log (a log of unhandled +# exceptions which forced the FastCGI instance to exit, great for debugging) +# and the number of requests to process before running garbage collection. +# +# By default, the FastCGI crash log is RAILS_ROOT/log/fastcgi.crash.log +# and the GC period is nil (turned off). A reasonable number of requests +# could range from 10-100 depending on the memory footprint of your app. +# +# Example: +# # Default log path, normal GC behavior. +# RailsFCGIHandler.process! +# +# # Default log path, 50 requests between GC. +# RailsFCGIHandler.process! nil, 50 +# +# # Custom log path, normal GC behavior. +# RailsFCGIHandler.process! '/var/log/myapp_fcgi_crash.log' +# +require File.dirname(__FILE__) + "/../config/environment" +require 'fcgi_handler' + +RailsFCGIHandler.process! diff --git a/public/dispatch.rb b/public/dispatch.rb new file mode 100755 index 0000000..9b5ae76 --- /dev/null +++ b/public/dispatch.rb @@ -0,0 +1,10 @@ +#!/usr/local/bin/ruby + +require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT) + +# If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like: +# "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher" -- otherwise performance is severely impaired +require "dispatcher" + +ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun) +Dispatcher.dispatch \ No newline at end of file diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..40b5daa Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/images/icons/.svn/README.txt b/public/images/icons/.svn/README.txt new file mode 100644 index 0000000..271a8ce --- /dev/null +++ b/public/images/icons/.svn/README.txt @@ -0,0 +1,2 @@ +This is a Subversion working copy administrative directory. +Visit http://subversion.tigris.org/ for more information. diff --git a/public/images/icons/.svn/dir-wcprops b/public/images/icons/.svn/dir-wcprops new file mode 100644 index 0000000..85484fb --- /dev/null +++ b/public/images/icons/.svn/dir-wcprops @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 47 +/svnroot/easyvz/!svn/ver/5/trunk/ezvz/gui/icons +END diff --git a/public/images/icons/.svn/empty-file b/public/images/icons/.svn/empty-file new file mode 100644 index 0000000..e69de29 diff --git a/public/images/icons/.svn/entries b/public/images/icons/.svn/entries new file mode 100644 index 0000000..1f0c5bb --- /dev/null +++ b/public/images/icons/.svn/entries @@ -0,0 +1,248 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/images/icons/.svn/format b/public/images/icons/.svn/format new file mode 100644 index 0000000..b8626c4 --- /dev/null +++ b/public/images/icons/.svn/format @@ -0,0 +1 @@ +4 diff --git a/public/images/icons/.svn/prop-base/arch.png.svn-base b/public/images/icons/.svn/prop-base/arch.png.svn-base new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/prop-base/arch.png.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/prop-base/centos.png.svn-base b/public/images/icons/.svn/prop-base/centos.png.svn-base new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/prop-base/centos.png.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/prop-base/damnsmall.png.svn-base b/public/images/icons/.svn/prop-base/damnsmall.png.svn-base new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/prop-base/damnsmall.png.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/prop-base/debian.png.svn-base b/public/images/icons/.svn/prop-base/debian.png.svn-base new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/prop-base/debian.png.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/prop-base/edubuntu.png.svn-base b/public/images/icons/.svn/prop-base/edubuntu.png.svn-base new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/prop-base/edubuntu.png.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/prop-base/fedora.png.svn-base b/public/images/icons/.svn/prop-base/fedora.png.svn-base new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/prop-base/fedora.png.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/prop-base/gentoo.png.svn-base b/public/images/icons/.svn/prop-base/gentoo.png.svn-base new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/prop-base/gentoo.png.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/prop-base/knoppix.png.svn-base b/public/images/icons/.svn/prop-base/knoppix.png.svn-base new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/prop-base/knoppix.png.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/prop-base/kubuntu.png.svn-base b/public/images/icons/.svn/prop-base/kubuntu.png.svn-base new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/prop-base/kubuntu.png.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/prop-base/lfs.png.svn-base b/public/images/icons/.svn/prop-base/lfs.png.svn-base new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/prop-base/lfs.png.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/prop-base/linspire.png.svn-base b/public/images/icons/.svn/prop-base/linspire.png.svn-base new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/prop-base/linspire.png.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/prop-base/linux_logo.png.svn-base b/public/images/icons/.svn/prop-base/linux_logo.png.svn-base new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/prop-base/linux_logo.png.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/prop-base/mandriva.png.svn-base b/public/images/icons/.svn/prop-base/mandriva.png.svn-base new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/prop-base/mandriva.png.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/prop-base/mepis.png.svn-base b/public/images/icons/.svn/prop-base/mepis.png.svn-base new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/prop-base/mepis.png.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/prop-base/morphix.png.svn-base b/public/images/icons/.svn/prop-base/morphix.png.svn-base new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/prop-base/morphix.png.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/prop-base/pclinuxos.png.svn-base b/public/images/icons/.svn/prop-base/pclinuxos.png.svn-base new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/prop-base/pclinuxos.png.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/prop-base/redhat.png.svn-base b/public/images/icons/.svn/prop-base/redhat.png.svn-base new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/prop-base/redhat.png.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/prop-base/slackware.png.svn-base b/public/images/icons/.svn/prop-base/slackware.png.svn-base new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/prop-base/slackware.png.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/prop-base/slax.png.svn-base b/public/images/icons/.svn/prop-base/slax.png.svn-base new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/prop-base/slax.png.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/prop-base/suse.png.svn-base b/public/images/icons/.svn/prop-base/suse.png.svn-base new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/prop-base/suse.png.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/prop-base/turbolinux.png.svn-base b/public/images/icons/.svn/prop-base/turbolinux.png.svn-base new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/prop-base/turbolinux.png.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/prop-base/ubuntu.png.svn-base b/public/images/icons/.svn/prop-base/ubuntu.png.svn-base new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/prop-base/ubuntu.png.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/prop-base/vector.png.svn-base b/public/images/icons/.svn/prop-base/vector.png.svn-base new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/prop-base/vector.png.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/prop-base/xandros.png.svn-base b/public/images/icons/.svn/prop-base/xandros.png.svn-base new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/prop-base/xandros.png.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/prop-base/xubuntu.png.svn-base b/public/images/icons/.svn/prop-base/xubuntu.png.svn-base new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/prop-base/xubuntu.png.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/prop-base/yoper.png.svn-base b/public/images/icons/.svn/prop-base/yoper.png.svn-base new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/prop-base/yoper.png.svn-base @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/props/arch.png.svn-work b/public/images/icons/.svn/props/arch.png.svn-work new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/props/arch.png.svn-work @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/props/centos.png.svn-work b/public/images/icons/.svn/props/centos.png.svn-work new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/props/centos.png.svn-work @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/props/damnsmall.png.svn-work b/public/images/icons/.svn/props/damnsmall.png.svn-work new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/props/damnsmall.png.svn-work @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/props/debian.png.svn-work b/public/images/icons/.svn/props/debian.png.svn-work new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/props/debian.png.svn-work @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/props/edubuntu.png.svn-work b/public/images/icons/.svn/props/edubuntu.png.svn-work new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/props/edubuntu.png.svn-work @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/props/fedora.png.svn-work b/public/images/icons/.svn/props/fedora.png.svn-work new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/props/fedora.png.svn-work @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/props/gentoo.png.svn-work b/public/images/icons/.svn/props/gentoo.png.svn-work new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/props/gentoo.png.svn-work @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/props/knoppix.png.svn-work b/public/images/icons/.svn/props/knoppix.png.svn-work new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/props/knoppix.png.svn-work @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/props/kubuntu.png.svn-work b/public/images/icons/.svn/props/kubuntu.png.svn-work new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/props/kubuntu.png.svn-work @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/props/lfs.png.svn-work b/public/images/icons/.svn/props/lfs.png.svn-work new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/props/lfs.png.svn-work @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/props/linspire.png.svn-work b/public/images/icons/.svn/props/linspire.png.svn-work new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/props/linspire.png.svn-work @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/props/linux_logo.png.svn-work b/public/images/icons/.svn/props/linux_logo.png.svn-work new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/props/linux_logo.png.svn-work @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/props/mandriva.png.svn-work b/public/images/icons/.svn/props/mandriva.png.svn-work new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/props/mandriva.png.svn-work @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/props/mepis.png.svn-work b/public/images/icons/.svn/props/mepis.png.svn-work new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/props/mepis.png.svn-work @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/props/morphix.png.svn-work b/public/images/icons/.svn/props/morphix.png.svn-work new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/props/morphix.png.svn-work @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/props/pclinuxos.png.svn-work b/public/images/icons/.svn/props/pclinuxos.png.svn-work new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/props/pclinuxos.png.svn-work @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/props/redhat.png.svn-work b/public/images/icons/.svn/props/redhat.png.svn-work new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/props/redhat.png.svn-work @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/props/slackware.png.svn-work b/public/images/icons/.svn/props/slackware.png.svn-work new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/props/slackware.png.svn-work @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/props/slax.png.svn-work b/public/images/icons/.svn/props/slax.png.svn-work new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/props/slax.png.svn-work @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/props/suse.png.svn-work b/public/images/icons/.svn/props/suse.png.svn-work new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/props/suse.png.svn-work @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/props/turbolinux.png.svn-work b/public/images/icons/.svn/props/turbolinux.png.svn-work new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/props/turbolinux.png.svn-work @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/props/ubuntu.png.svn-work b/public/images/icons/.svn/props/ubuntu.png.svn-work new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/props/ubuntu.png.svn-work @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/props/vector.png.svn-work b/public/images/icons/.svn/props/vector.png.svn-work new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/props/vector.png.svn-work @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/props/xandros.png.svn-work b/public/images/icons/.svn/props/xandros.png.svn-work new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/props/xandros.png.svn-work @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/props/xubuntu.png.svn-work b/public/images/icons/.svn/props/xubuntu.png.svn-work new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/props/xubuntu.png.svn-work @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/props/yoper.png.svn-work b/public/images/icons/.svn/props/yoper.png.svn-work new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/public/images/icons/.svn/props/yoper.png.svn-work @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/public/images/icons/.svn/text-base/arch.png.svn-base b/public/images/icons/.svn/text-base/arch.png.svn-base new file mode 100644 index 0000000..d4a3513 Binary files /dev/null and b/public/images/icons/.svn/text-base/arch.png.svn-base differ diff --git a/public/images/icons/.svn/text-base/centos.png.svn-base b/public/images/icons/.svn/text-base/centos.png.svn-base new file mode 100644 index 0000000..64a69b5 Binary files /dev/null and b/public/images/icons/.svn/text-base/centos.png.svn-base differ diff --git a/public/images/icons/.svn/text-base/damnsmall.png.svn-base b/public/images/icons/.svn/text-base/damnsmall.png.svn-base new file mode 100644 index 0000000..8bfe006 Binary files /dev/null and b/public/images/icons/.svn/text-base/damnsmall.png.svn-base differ diff --git a/public/images/icons/.svn/text-base/debian.png.svn-base b/public/images/icons/.svn/text-base/debian.png.svn-base new file mode 100644 index 0000000..800263a Binary files /dev/null and b/public/images/icons/.svn/text-base/debian.png.svn-base differ diff --git a/public/images/icons/.svn/text-base/edubuntu.png.svn-base b/public/images/icons/.svn/text-base/edubuntu.png.svn-base new file mode 100644 index 0000000..6866292 Binary files /dev/null and b/public/images/icons/.svn/text-base/edubuntu.png.svn-base differ diff --git a/public/images/icons/.svn/text-base/fedora.png.svn-base b/public/images/icons/.svn/text-base/fedora.png.svn-base new file mode 100644 index 0000000..00781fc Binary files /dev/null and b/public/images/icons/.svn/text-base/fedora.png.svn-base differ diff --git a/public/images/icons/.svn/text-base/gentoo.png.svn-base b/public/images/icons/.svn/text-base/gentoo.png.svn-base new file mode 100644 index 0000000..04139e6 Binary files /dev/null and b/public/images/icons/.svn/text-base/gentoo.png.svn-base differ diff --git a/public/images/icons/.svn/text-base/knoppix.png.svn-base b/public/images/icons/.svn/text-base/knoppix.png.svn-base new file mode 100644 index 0000000..c86786b Binary files /dev/null and b/public/images/icons/.svn/text-base/knoppix.png.svn-base differ diff --git a/public/images/icons/.svn/text-base/kubuntu.png.svn-base b/public/images/icons/.svn/text-base/kubuntu.png.svn-base new file mode 100644 index 0000000..20eebcd Binary files /dev/null and b/public/images/icons/.svn/text-base/kubuntu.png.svn-base differ diff --git a/public/images/icons/.svn/text-base/lfs.png.svn-base b/public/images/icons/.svn/text-base/lfs.png.svn-base new file mode 100644 index 0000000..96fb7ad Binary files /dev/null and b/public/images/icons/.svn/text-base/lfs.png.svn-base differ diff --git a/public/images/icons/.svn/text-base/linspire.png.svn-base b/public/images/icons/.svn/text-base/linspire.png.svn-base new file mode 100644 index 0000000..5f38778 Binary files /dev/null and b/public/images/icons/.svn/text-base/linspire.png.svn-base differ diff --git a/public/images/icons/.svn/text-base/linux_logo.png.svn-base b/public/images/icons/.svn/text-base/linux_logo.png.svn-base new file mode 100644 index 0000000..4ae72f1 Binary files /dev/null and b/public/images/icons/.svn/text-base/linux_logo.png.svn-base differ diff --git a/public/images/icons/.svn/text-base/mandriva.png.svn-base b/public/images/icons/.svn/text-base/mandriva.png.svn-base new file mode 100644 index 0000000..e1f1253 Binary files /dev/null and b/public/images/icons/.svn/text-base/mandriva.png.svn-base differ diff --git a/public/images/icons/.svn/text-base/mepis.png.svn-base b/public/images/icons/.svn/text-base/mepis.png.svn-base new file mode 100644 index 0000000..1b396fd Binary files /dev/null and b/public/images/icons/.svn/text-base/mepis.png.svn-base differ diff --git a/public/images/icons/.svn/text-base/morphix.png.svn-base b/public/images/icons/.svn/text-base/morphix.png.svn-base new file mode 100644 index 0000000..a1d13ab Binary files /dev/null and b/public/images/icons/.svn/text-base/morphix.png.svn-base differ diff --git a/public/images/icons/.svn/text-base/pclinuxos.png.svn-base b/public/images/icons/.svn/text-base/pclinuxos.png.svn-base new file mode 100644 index 0000000..d13a288 Binary files /dev/null and b/public/images/icons/.svn/text-base/pclinuxos.png.svn-base differ diff --git a/public/images/icons/.svn/text-base/redhat.png.svn-base b/public/images/icons/.svn/text-base/redhat.png.svn-base new file mode 100644 index 0000000..75c4d19 Binary files /dev/null and b/public/images/icons/.svn/text-base/redhat.png.svn-base differ diff --git a/public/images/icons/.svn/text-base/slackware.png.svn-base b/public/images/icons/.svn/text-base/slackware.png.svn-base new file mode 100644 index 0000000..47ca0ed Binary files /dev/null and b/public/images/icons/.svn/text-base/slackware.png.svn-base differ diff --git a/public/images/icons/.svn/text-base/slax.png.svn-base b/public/images/icons/.svn/text-base/slax.png.svn-base new file mode 100644 index 0000000..4822806 Binary files /dev/null and b/public/images/icons/.svn/text-base/slax.png.svn-base differ diff --git a/public/images/icons/.svn/text-base/suse.png.svn-base b/public/images/icons/.svn/text-base/suse.png.svn-base new file mode 100644 index 0000000..97c6eb9 Binary files /dev/null and b/public/images/icons/.svn/text-base/suse.png.svn-base differ diff --git a/public/images/icons/.svn/text-base/turbolinux.png.svn-base b/public/images/icons/.svn/text-base/turbolinux.png.svn-base new file mode 100644 index 0000000..b9b45b8 Binary files /dev/null and b/public/images/icons/.svn/text-base/turbolinux.png.svn-base differ diff --git a/public/images/icons/.svn/text-base/ubuntu.png.svn-base b/public/images/icons/.svn/text-base/ubuntu.png.svn-base new file mode 100644 index 0000000..c5e30a7 Binary files /dev/null and b/public/images/icons/.svn/text-base/ubuntu.png.svn-base differ diff --git a/public/images/icons/.svn/text-base/vector.png.svn-base b/public/images/icons/.svn/text-base/vector.png.svn-base new file mode 100644 index 0000000..bd17316 Binary files /dev/null and b/public/images/icons/.svn/text-base/vector.png.svn-base differ diff --git a/public/images/icons/.svn/text-base/xandros.png.svn-base b/public/images/icons/.svn/text-base/xandros.png.svn-base new file mode 100644 index 0000000..3c12f93 Binary files /dev/null and b/public/images/icons/.svn/text-base/xandros.png.svn-base differ diff --git a/public/images/icons/.svn/text-base/xubuntu.png.svn-base b/public/images/icons/.svn/text-base/xubuntu.png.svn-base new file mode 100644 index 0000000..cdfb7fc Binary files /dev/null and b/public/images/icons/.svn/text-base/xubuntu.png.svn-base differ diff --git a/public/images/icons/.svn/text-base/yoper.png.svn-base b/public/images/icons/.svn/text-base/yoper.png.svn-base new file mode 100644 index 0000000..5f0e161 Binary files /dev/null and b/public/images/icons/.svn/text-base/yoper.png.svn-base differ diff --git a/public/images/icons/.svn/wcprops/arch.png.svn-work b/public/images/icons/.svn/wcprops/arch.png.svn-work new file mode 100644 index 0000000..e2695dd --- /dev/null +++ b/public/images/icons/.svn/wcprops/arch.png.svn-work @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 56 +/svnroot/easyvz/!svn/ver/5/trunk/ezvz/gui/icons/arch.png +END diff --git a/public/images/icons/.svn/wcprops/centos.png.svn-work b/public/images/icons/.svn/wcprops/centos.png.svn-work new file mode 100644 index 0000000..3304d07 --- /dev/null +++ b/public/images/icons/.svn/wcprops/centos.png.svn-work @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 58 +/svnroot/easyvz/!svn/ver/5/trunk/ezvz/gui/icons/centos.png +END diff --git a/public/images/icons/.svn/wcprops/damnsmall.png.svn-work b/public/images/icons/.svn/wcprops/damnsmall.png.svn-work new file mode 100644 index 0000000..47d289d --- /dev/null +++ b/public/images/icons/.svn/wcprops/damnsmall.png.svn-work @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 61 +/svnroot/easyvz/!svn/ver/5/trunk/ezvz/gui/icons/damnsmall.png +END diff --git a/public/images/icons/.svn/wcprops/debian.png.svn-work b/public/images/icons/.svn/wcprops/debian.png.svn-work new file mode 100644 index 0000000..b8c6794 --- /dev/null +++ b/public/images/icons/.svn/wcprops/debian.png.svn-work @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 58 +/svnroot/easyvz/!svn/ver/5/trunk/ezvz/gui/icons/debian.png +END diff --git a/public/images/icons/.svn/wcprops/edubuntu.png.svn-work b/public/images/icons/.svn/wcprops/edubuntu.png.svn-work new file mode 100644 index 0000000..2101a9e --- /dev/null +++ b/public/images/icons/.svn/wcprops/edubuntu.png.svn-work @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 60 +/svnroot/easyvz/!svn/ver/5/trunk/ezvz/gui/icons/edubuntu.png +END diff --git a/public/images/icons/.svn/wcprops/fedora.png.svn-work b/public/images/icons/.svn/wcprops/fedora.png.svn-work new file mode 100644 index 0000000..8cacd54 --- /dev/null +++ b/public/images/icons/.svn/wcprops/fedora.png.svn-work @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 58 +/svnroot/easyvz/!svn/ver/5/trunk/ezvz/gui/icons/fedora.png +END diff --git a/public/images/icons/.svn/wcprops/gentoo.png.svn-work b/public/images/icons/.svn/wcprops/gentoo.png.svn-work new file mode 100644 index 0000000..286bc53 --- /dev/null +++ b/public/images/icons/.svn/wcprops/gentoo.png.svn-work @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 58 +/svnroot/easyvz/!svn/ver/5/trunk/ezvz/gui/icons/gentoo.png +END diff --git a/public/images/icons/.svn/wcprops/knoppix.png.svn-work b/public/images/icons/.svn/wcprops/knoppix.png.svn-work new file mode 100644 index 0000000..51e15d4 --- /dev/null +++ b/public/images/icons/.svn/wcprops/knoppix.png.svn-work @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 59 +/svnroot/easyvz/!svn/ver/5/trunk/ezvz/gui/icons/knoppix.png +END diff --git a/public/images/icons/.svn/wcprops/kubuntu.png.svn-work b/public/images/icons/.svn/wcprops/kubuntu.png.svn-work new file mode 100644 index 0000000..e802fdd --- /dev/null +++ b/public/images/icons/.svn/wcprops/kubuntu.png.svn-work @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 59 +/svnroot/easyvz/!svn/ver/5/trunk/ezvz/gui/icons/kubuntu.png +END diff --git a/public/images/icons/.svn/wcprops/lfs.png.svn-work b/public/images/icons/.svn/wcprops/lfs.png.svn-work new file mode 100644 index 0000000..fe7a950 --- /dev/null +++ b/public/images/icons/.svn/wcprops/lfs.png.svn-work @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 55 +/svnroot/easyvz/!svn/ver/5/trunk/ezvz/gui/icons/lfs.png +END diff --git a/public/images/icons/.svn/wcprops/linspire.png.svn-work b/public/images/icons/.svn/wcprops/linspire.png.svn-work new file mode 100644 index 0000000..2943413 --- /dev/null +++ b/public/images/icons/.svn/wcprops/linspire.png.svn-work @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 60 +/svnroot/easyvz/!svn/ver/5/trunk/ezvz/gui/icons/linspire.png +END diff --git a/public/images/icons/.svn/wcprops/linux_logo.png.svn-work b/public/images/icons/.svn/wcprops/linux_logo.png.svn-work new file mode 100644 index 0000000..3797982 --- /dev/null +++ b/public/images/icons/.svn/wcprops/linux_logo.png.svn-work @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 62 +/svnroot/easyvz/!svn/ver/5/trunk/ezvz/gui/icons/linux_logo.png +END diff --git a/public/images/icons/.svn/wcprops/mandriva.png.svn-work b/public/images/icons/.svn/wcprops/mandriva.png.svn-work new file mode 100644 index 0000000..4eb3e68 --- /dev/null +++ b/public/images/icons/.svn/wcprops/mandriva.png.svn-work @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 60 +/svnroot/easyvz/!svn/ver/5/trunk/ezvz/gui/icons/mandriva.png +END diff --git a/public/images/icons/.svn/wcprops/mepis.png.svn-work b/public/images/icons/.svn/wcprops/mepis.png.svn-work new file mode 100644 index 0000000..fff9bbe --- /dev/null +++ b/public/images/icons/.svn/wcprops/mepis.png.svn-work @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 57 +/svnroot/easyvz/!svn/ver/5/trunk/ezvz/gui/icons/mepis.png +END diff --git a/public/images/icons/.svn/wcprops/morphix.png.svn-work b/public/images/icons/.svn/wcprops/morphix.png.svn-work new file mode 100644 index 0000000..e44ef77 --- /dev/null +++ b/public/images/icons/.svn/wcprops/morphix.png.svn-work @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 59 +/svnroot/easyvz/!svn/ver/5/trunk/ezvz/gui/icons/morphix.png +END diff --git a/public/images/icons/.svn/wcprops/pclinuxos.png.svn-work b/public/images/icons/.svn/wcprops/pclinuxos.png.svn-work new file mode 100644 index 0000000..f4ad0c5 --- /dev/null +++ b/public/images/icons/.svn/wcprops/pclinuxos.png.svn-work @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 61 +/svnroot/easyvz/!svn/ver/5/trunk/ezvz/gui/icons/pclinuxos.png +END diff --git a/public/images/icons/.svn/wcprops/redhat.png.svn-work b/public/images/icons/.svn/wcprops/redhat.png.svn-work new file mode 100644 index 0000000..82ed524 --- /dev/null +++ b/public/images/icons/.svn/wcprops/redhat.png.svn-work @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 58 +/svnroot/easyvz/!svn/ver/5/trunk/ezvz/gui/icons/redhat.png +END diff --git a/public/images/icons/.svn/wcprops/slackware.png.svn-work b/public/images/icons/.svn/wcprops/slackware.png.svn-work new file mode 100644 index 0000000..a3d6293 --- /dev/null +++ b/public/images/icons/.svn/wcprops/slackware.png.svn-work @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 61 +/svnroot/easyvz/!svn/ver/5/trunk/ezvz/gui/icons/slackware.png +END diff --git a/public/images/icons/.svn/wcprops/slax.png.svn-work b/public/images/icons/.svn/wcprops/slax.png.svn-work new file mode 100644 index 0000000..d40c6df --- /dev/null +++ b/public/images/icons/.svn/wcprops/slax.png.svn-work @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 56 +/svnroot/easyvz/!svn/ver/5/trunk/ezvz/gui/icons/slax.png +END diff --git a/public/images/icons/.svn/wcprops/suse.png.svn-work b/public/images/icons/.svn/wcprops/suse.png.svn-work new file mode 100644 index 0000000..b124b76 --- /dev/null +++ b/public/images/icons/.svn/wcprops/suse.png.svn-work @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 56 +/svnroot/easyvz/!svn/ver/5/trunk/ezvz/gui/icons/suse.png +END diff --git a/public/images/icons/.svn/wcprops/turbolinux.png.svn-work b/public/images/icons/.svn/wcprops/turbolinux.png.svn-work new file mode 100644 index 0000000..cb82774 --- /dev/null +++ b/public/images/icons/.svn/wcprops/turbolinux.png.svn-work @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 62 +/svnroot/easyvz/!svn/ver/5/trunk/ezvz/gui/icons/turbolinux.png +END diff --git a/public/images/icons/.svn/wcprops/ubuntu.png.svn-work b/public/images/icons/.svn/wcprops/ubuntu.png.svn-work new file mode 100644 index 0000000..1d92d2b --- /dev/null +++ b/public/images/icons/.svn/wcprops/ubuntu.png.svn-work @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 58 +/svnroot/easyvz/!svn/ver/5/trunk/ezvz/gui/icons/ubuntu.png +END diff --git a/public/images/icons/.svn/wcprops/vector.png.svn-work b/public/images/icons/.svn/wcprops/vector.png.svn-work new file mode 100644 index 0000000..899eb7c --- /dev/null +++ b/public/images/icons/.svn/wcprops/vector.png.svn-work @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 58 +/svnroot/easyvz/!svn/ver/5/trunk/ezvz/gui/icons/vector.png +END diff --git a/public/images/icons/.svn/wcprops/xandros.png.svn-work b/public/images/icons/.svn/wcprops/xandros.png.svn-work new file mode 100644 index 0000000..b6563f6 --- /dev/null +++ b/public/images/icons/.svn/wcprops/xandros.png.svn-work @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 59 +/svnroot/easyvz/!svn/ver/5/trunk/ezvz/gui/icons/xandros.png +END diff --git a/public/images/icons/.svn/wcprops/xubuntu.png.svn-work b/public/images/icons/.svn/wcprops/xubuntu.png.svn-work new file mode 100644 index 0000000..5d8724d --- /dev/null +++ b/public/images/icons/.svn/wcprops/xubuntu.png.svn-work @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 59 +/svnroot/easyvz/!svn/ver/5/trunk/ezvz/gui/icons/xubuntu.png +END diff --git a/public/images/icons/.svn/wcprops/yoper.png.svn-work b/public/images/icons/.svn/wcprops/yoper.png.svn-work new file mode 100644 index 0000000..8e892fa --- /dev/null +++ b/public/images/icons/.svn/wcprops/yoper.png.svn-work @@ -0,0 +1,5 @@ +K 25 +svn:wc:ra_dav:version-url +V 57 +/svnroot/easyvz/!svn/ver/5/trunk/ezvz/gui/icons/yoper.png +END diff --git a/public/images/icons/arch.png b/public/images/icons/arch.png new file mode 100644 index 0000000..d4a3513 Binary files /dev/null and b/public/images/icons/arch.png differ diff --git a/public/images/icons/centos.png b/public/images/icons/centos.png new file mode 100644 index 0000000..64a69b5 Binary files /dev/null and b/public/images/icons/centos.png differ diff --git a/public/images/icons/damnsmall.png b/public/images/icons/damnsmall.png new file mode 100644 index 0000000..8bfe006 Binary files /dev/null and b/public/images/icons/damnsmall.png differ diff --git a/public/images/icons/debian.png b/public/images/icons/debian.png new file mode 100644 index 0000000..800263a Binary files /dev/null and b/public/images/icons/debian.png differ diff --git a/public/images/icons/edubuntu.png b/public/images/icons/edubuntu.png new file mode 100644 index 0000000..6866292 Binary files /dev/null and b/public/images/icons/edubuntu.png differ diff --git a/public/images/icons/fedora.png b/public/images/icons/fedora.png new file mode 100644 index 0000000..00781fc Binary files /dev/null and b/public/images/icons/fedora.png differ diff --git a/public/images/icons/gentoo.png b/public/images/icons/gentoo.png new file mode 100644 index 0000000..04139e6 Binary files /dev/null and b/public/images/icons/gentoo.png differ diff --git a/public/images/icons/knoppix.png b/public/images/icons/knoppix.png new file mode 100644 index 0000000..c86786b Binary files /dev/null and b/public/images/icons/knoppix.png differ diff --git a/public/images/icons/kubuntu.png b/public/images/icons/kubuntu.png new file mode 100644 index 0000000..20eebcd Binary files /dev/null and b/public/images/icons/kubuntu.png differ diff --git a/public/images/icons/lfs.png b/public/images/icons/lfs.png new file mode 100644 index 0000000..96fb7ad Binary files /dev/null and b/public/images/icons/lfs.png differ diff --git a/public/images/icons/linspire.png b/public/images/icons/linspire.png new file mode 100644 index 0000000..5f38778 Binary files /dev/null and b/public/images/icons/linspire.png differ diff --git a/public/images/icons/linux_logo.png b/public/images/icons/linux_logo.png new file mode 100644 index 0000000..4ae72f1 Binary files /dev/null and b/public/images/icons/linux_logo.png differ diff --git a/public/images/icons/mandriva.png b/public/images/icons/mandriva.png new file mode 100644 index 0000000..e1f1253 Binary files /dev/null and b/public/images/icons/mandriva.png differ diff --git a/public/images/icons/mepis.png b/public/images/icons/mepis.png new file mode 100644 index 0000000..1b396fd Binary files /dev/null and b/public/images/icons/mepis.png differ diff --git a/public/images/icons/morphix.png b/public/images/icons/morphix.png new file mode 100644 index 0000000..a1d13ab Binary files /dev/null and b/public/images/icons/morphix.png differ diff --git a/public/images/icons/pclinuxos.png b/public/images/icons/pclinuxos.png new file mode 100644 index 0000000..d13a288 Binary files /dev/null and b/public/images/icons/pclinuxos.png differ diff --git a/public/images/icons/redhat.png b/public/images/icons/redhat.png new file mode 100644 index 0000000..75c4d19 Binary files /dev/null and b/public/images/icons/redhat.png differ diff --git a/public/images/icons/slackware.png b/public/images/icons/slackware.png new file mode 100644 index 0000000..47ca0ed Binary files /dev/null and b/public/images/icons/slackware.png differ diff --git a/public/images/icons/slax.png b/public/images/icons/slax.png new file mode 100644 index 0000000..4822806 Binary files /dev/null and b/public/images/icons/slax.png differ diff --git a/public/images/icons/suse.png b/public/images/icons/suse.png new file mode 100644 index 0000000..97c6eb9 Binary files /dev/null and b/public/images/icons/suse.png differ diff --git a/public/images/icons/turbolinux.png b/public/images/icons/turbolinux.png new file mode 100644 index 0000000..b9b45b8 Binary files /dev/null and b/public/images/icons/turbolinux.png differ diff --git a/public/images/icons/ubuntu.png b/public/images/icons/ubuntu.png new file mode 100644 index 0000000..c5e30a7 Binary files /dev/null and b/public/images/icons/ubuntu.png differ diff --git a/public/images/icons/vector.png b/public/images/icons/vector.png new file mode 100644 index 0000000..bd17316 Binary files /dev/null and b/public/images/icons/vector.png differ diff --git a/public/images/icons/xandros.png b/public/images/icons/xandros.png new file mode 100644 index 0000000..3c12f93 Binary files /dev/null and b/public/images/icons/xandros.png differ diff --git a/public/images/icons/xubuntu.png b/public/images/icons/xubuntu.png new file mode 100644 index 0000000..cdfb7fc Binary files /dev/null and b/public/images/icons/xubuntu.png differ diff --git a/public/images/icons/yoper.png b/public/images/icons/yoper.png new file mode 100644 index 0000000..5f0e161 Binary files /dev/null and b/public/images/icons/yoper.png differ diff --git a/public/images/webvz-logo.png b/public/images/webvz-logo.png new file mode 100644 index 0000000..12f2f6a Binary files /dev/null and b/public/images/webvz-logo.png differ diff --git a/public/images/webvz_small.jpg b/public/images/webvz_small.jpg new file mode 100644 index 0000000..d7c716e Binary files /dev/null and b/public/images/webvz_small.jpg differ diff --git a/public/javascripts/application.js b/public/javascripts/application.js new file mode 100644 index 0000000..fe45776 --- /dev/null +++ b/public/javascripts/application.js @@ -0,0 +1,2 @@ +// Place your application-specific JavaScript functions and classes here +// This file is automatically included by javascript_include_tag :defaults diff --git a/public/javascripts/controls.js b/public/javascripts/controls.js new file mode 100644 index 0000000..fbc4418 --- /dev/null +++ b/public/javascripts/controls.js @@ -0,0 +1,963 @@ +// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan) +// (c) 2005-2007 Jon Tirsen (http://www.tirsen.com) +// Contributors: +// Richard Livsey +// Rahul Bhargava +// Rob Wills +// +// script.aculo.us is freely distributable under the terms of an MIT-style license. +// For details, see the script.aculo.us web site: http://script.aculo.us/ + +// Autocompleter.Base handles all the autocompletion functionality +// that's independent of the data source for autocompletion. This +// includes drawing the autocompletion menu, observing keyboard +// and mouse events, and similar. +// +// Specific autocompleters need to provide, at the very least, +// a getUpdatedChoices function that will be invoked every time +// the text inside the monitored textbox changes. This method +// should get the text for which to provide autocompletion by +// invoking this.getToken(), NOT by directly accessing +// this.element.value. This is to allow incremental tokenized +// autocompletion. Specific auto-completion logic (AJAX, etc) +// belongs in getUpdatedChoices. +// +// Tokenized incremental autocompletion is enabled automatically +// when an autocompleter is instantiated with the 'tokens' option +// in the options parameter, e.g.: +// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' }); +// will incrementally autocomplete with a comma as the token. +// Additionally, ',' in the above example can be replaced with +// a token array, e.g. { tokens: [',', '\n'] } which +// enables autocompletion on multiple tokens. This is most +// useful when one of the tokens is \n (a newline), as it +// allows smart autocompletion after linebreaks. + +if(typeof Effect == 'undefined') + throw("controls.js requires including script.aculo.us' effects.js library"); + +var Autocompleter = { } +Autocompleter.Base = Class.create({ + baseInitialize: function(element, update, options) { + element = $(element) + this.element = element; + this.update = $(update); + this.hasFocus = false; + this.changed = false; + this.active = false; + this.index = 0; + this.entryCount = 0; + this.oldElementValue = this.element.value; + + if(this.setOptions) + this.setOptions(options); + else + this.options = options || { }; + + this.options.paramName = this.options.paramName || this.element.name; + this.options.tokens = this.options.tokens || []; + this.options.frequency = this.options.frequency || 0.4; + this.options.minChars = this.options.minChars || 1; + this.options.onShow = this.options.onShow || + function(element, update){ + if(!update.style.position || update.style.position=='absolute') { + update.style.position = 'absolute'; + Position.clone(element, update, { + setHeight: false, + offsetTop: element.offsetHeight + }); + } + Effect.Appear(update,{duration:0.15}); + }; + this.options.onHide = this.options.onHide || + function(element, update){ new Effect.Fade(update,{duration:0.15}) }; + + if(typeof(this.options.tokens) == 'string') + this.options.tokens = new Array(this.options.tokens); + // Force carriage returns as token delimiters anyway + if (!this.options.tokens.include('\n')) + this.options.tokens.push('\n'); + + this.observer = null; + + this.element.setAttribute('autocomplete','off'); + + Element.hide(this.update); + + Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this)); + Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this)); + }, + + show: function() { + if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); + if(!this.iefix && + (Prototype.Browser.IE) && + (Element.getStyle(this.update, 'position')=='absolute')) { + new Insertion.After(this.update, + ''); + this.iefix = $(this.update.id+'_iefix'); + } + if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50); + }, + + fixIEOverlapping: function() { + Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)}); + this.iefix.style.zIndex = 1; + this.update.style.zIndex = 2; + Element.show(this.iefix); + }, + + hide: function() { + this.stopIndicator(); + if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update); + if(this.iefix) Element.hide(this.iefix); + }, + + startIndicator: function() { + if(this.options.indicator) Element.show(this.options.indicator); + }, + + stopIndicator: function() { + if(this.options.indicator) Element.hide(this.options.indicator); + }, + + onKeyPress: function(event) { + if(this.active) + switch(event.keyCode) { + case Event.KEY_TAB: + case Event.KEY_RETURN: + this.selectEntry(); + Event.stop(event); + case Event.KEY_ESC: + this.hide(); + this.active = false; + Event.stop(event); + return; + case Event.KEY_LEFT: + case Event.KEY_RIGHT: + return; + case Event.KEY_UP: + this.markPrevious(); + this.render(); + Event.stop(event); + return; + case Event.KEY_DOWN: + this.markNext(); + this.render(); + Event.stop(event); + return; + } + else + if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || + (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return; + + this.changed = true; + this.hasFocus = true; + + if(this.observer) clearTimeout(this.observer); + this.observer = + setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); + }, + + activate: function() { + this.changed = false; + this.hasFocus = true; + this.getUpdatedChoices(); + }, + + onHover: function(event) { + var element = Event.findElement(event, 'LI'); + if(this.index != element.autocompleteIndex) + { + this.index = element.autocompleteIndex; + this.render(); + } + Event.stop(event); + }, + + onClick: function(event) { + var element = Event.findElement(event, 'LI'); + this.index = element.autocompleteIndex; + this.selectEntry(); + this.hide(); + }, + + onBlur: function(event) { + // needed to make click events working + setTimeout(this.hide.bind(this), 250); + this.hasFocus = false; + this.active = false; + }, + + render: function() { + if(this.entryCount > 0) { + for (var i = 0; i < this.entryCount; i++) + this.index==i ? + Element.addClassName(this.getEntry(i),"selected") : + Element.removeClassName(this.getEntry(i),"selected"); + if(this.hasFocus) { + this.show(); + this.active = true; + } + } else { + this.active = false; + this.hide(); + } + }, + + markPrevious: function() { + if(this.index > 0) this.index-- + else this.index = this.entryCount-1; + this.getEntry(this.index).scrollIntoView(true); + }, + + markNext: function() { + if(this.index < this.entryCount-1) this.index++ + else this.index = 0; + this.getEntry(this.index).scrollIntoView(false); + }, + + getEntry: function(index) { + return this.update.firstChild.childNodes[index]; + }, + + getCurrentEntry: function() { + return this.getEntry(this.index); + }, + + selectEntry: function() { + this.active = false; + this.updateElement(this.getCurrentEntry()); + }, + + updateElement: function(selectedElement) { + if (this.options.updateElement) { + this.options.updateElement(selectedElement); + return; + } + var value = ''; + if (this.options.select) { + var nodes = $(selectedElement).select('.' + this.options.select) || []; + if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select); + } else + value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); + + var bounds = this.getTokenBounds(); + if (bounds[0] != -1) { + var newValue = this.element.value.substr(0, bounds[0]); + var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/); + if (whitespace) + newValue += whitespace[0]; + this.element.value = newValue + value + this.element.value.substr(bounds[1]); + } else { + this.element.value = value; + } + this.oldElementValue = this.element.value; + this.element.focus(); + + if (this.options.afterUpdateElement) + this.options.afterUpdateElement(this.element, selectedElement); + }, + + updateChoices: function(choices) { + if(!this.changed && this.hasFocus) { + this.update.innerHTML = choices; + Element.cleanWhitespace(this.update); + Element.cleanWhitespace(this.update.down()); + + if(this.update.firstChild && this.update.down().childNodes) { + this.entryCount = + this.update.down().childNodes.length; + for (var i = 0; i < this.entryCount; i++) { + var entry = this.getEntry(i); + entry.autocompleteIndex = i; + this.addObservers(entry); + } + } else { + this.entryCount = 0; + } + + this.stopIndicator(); + this.index = 0; + + if(this.entryCount==1 && this.options.autoSelect) { + this.selectEntry(); + this.hide(); + } else { + this.render(); + } + } + }, + + addObservers: function(element) { + Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this)); + Event.observe(element, "click", this.onClick.bindAsEventListener(this)); + }, + + onObserverEvent: function() { + this.changed = false; + this.tokenBounds = null; + if(this.getToken().length>=this.options.minChars) { + this.getUpdatedChoices(); + } else { + this.active = false; + this.hide(); + } + this.oldElementValue = this.element.value; + }, + + getToken: function() { + var bounds = this.getTokenBounds(); + return this.element.value.substring(bounds[0], bounds[1]).strip(); + }, + + getTokenBounds: function() { + if (null != this.tokenBounds) return this.tokenBounds; + var value = this.element.value; + if (value.strip().empty()) return [-1, 0]; + var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue); + var offset = (diff == this.oldElementValue.length ? 1 : 0); + var prevTokenPos = -1, nextTokenPos = value.length; + var tp; + for (var index = 0, l = this.options.tokens.length; index < l; ++index) { + tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1); + if (tp > prevTokenPos) prevTokenPos = tp; + tp = value.indexOf(this.options.tokens[index], diff + offset); + if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp; + } + return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]); + } +}); + +Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) { + var boundary = Math.min(newS.length, oldS.length); + for (var index = 0; index < boundary; ++index) + if (newS[index] != oldS[index]) + return index; + return boundary; +}; + +Ajax.Autocompleter = Class.create(Autocompleter.Base, { + initialize: function(element, update, url, options) { + this.baseInitialize(element, update, options); + this.options.asynchronous = true; + this.options.onComplete = this.onComplete.bind(this); + this.options.defaultParams = this.options.parameters || null; + this.url = url; + }, + + getUpdatedChoices: function() { + this.startIndicator(); + + var entry = encodeURIComponent(this.options.paramName) + '=' + + encodeURIComponent(this.getToken()); + + this.options.parameters = this.options.callback ? + this.options.callback(this.element, entry) : entry; + + if(this.options.defaultParams) + this.options.parameters += '&' + this.options.defaultParams; + + new Ajax.Request(this.url, this.options); + }, + + onComplete: function(request) { + this.updateChoices(request.responseText); + } +}); + +// The local array autocompleter. Used when you'd prefer to +// inject an array of autocompletion options into the page, rather +// than sending out Ajax queries, which can be quite slow sometimes. +// +// The constructor takes four parameters. The first two are, as usual, +// the id of the monitored textbox, and id of the autocompletion menu. +// The third is the array you want to autocomplete from, and the fourth +// is the options block. +// +// Extra local autocompletion options: +// - choices - How many autocompletion choices to offer +// +// - partialSearch - If false, the autocompleter will match entered +// text only at the beginning of strings in the +// autocomplete array. Defaults to true, which will +// match text at the beginning of any *word* in the +// strings in the autocomplete array. If you want to +// search anywhere in the string, additionally set +// the option fullSearch to true (default: off). +// +// - fullSsearch - Search anywhere in autocomplete array strings. +// +// - partialChars - How many characters to enter before triggering +// a partial match (unlike minChars, which defines +// how many characters are required to do any match +// at all). Defaults to 2. +// +// - ignoreCase - Whether to ignore case when autocompleting. +// Defaults to true. +// +// It's possible to pass in a custom function as the 'selector' +// option, if you prefer to write your own autocompletion logic. +// In that case, the other options above will not apply unless +// you support them. + +Autocompleter.Local = Class.create(Autocompleter.Base, { + initialize: function(element, update, array, options) { + this.baseInitialize(element, update, options); + this.options.array = array; + }, + + getUpdatedChoices: function() { + this.updateChoices(this.options.selector(this)); + }, + + setOptions: function(options) { + this.options = Object.extend({ + choices: 10, + partialSearch: true, + partialChars: 2, + ignoreCase: true, + fullSearch: false, + selector: function(instance) { + var ret = []; // Beginning matches + var partial = []; // Inside matches + var entry = instance.getToken(); + var count = 0; + + for (var i = 0; i < instance.options.array.length && + ret.length < instance.options.choices ; i++) { + + var elem = instance.options.array[i]; + var foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase()) : + elem.indexOf(entry); + + while (foundPos != -1) { + if (foundPos == 0 && elem.length != entry.length) { + ret.push("
  • " + elem.substr(0, entry.length) + "" + + elem.substr(entry.length) + "
  • "); + break; + } else if (entry.length >= instance.options.partialChars && + instance.options.partialSearch && foundPos != -1) { + if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { + partial.push("
  • " + elem.substr(0, foundPos) + "" + + elem.substr(foundPos, entry.length) + "" + elem.substr( + foundPos + entry.length) + "
  • "); + break; + } + } + + foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : + elem.indexOf(entry, foundPos + 1); + + } + } + if (partial.length) + ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)) + return "
      " + ret.join('') + "
    "; + } + }, options || { }); + } +}); + +// AJAX in-place editor and collection editor +// Full rewrite by Christophe Porteneuve (April 2007). + +// Use this if you notice weird scrolling problems on some browsers, +// the DOM might be a bit confused when this gets called so do this +// waits 1 ms (with setTimeout) until it does the activation +Field.scrollFreeActivate = function(field) { + setTimeout(function() { + Field.activate(field); + }, 1); +} + +Ajax.InPlaceEditor = Class.create({ + initialize: function(element, url, options) { + this.url = url; + this.element = element = $(element); + this.prepareOptions(); + this._controls = { }; + arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!! + Object.extend(this.options, options || { }); + if (!this.options.formId && this.element.id) { + this.options.formId = this.element.id + '-inplaceeditor'; + if ($(this.options.formId)) + this.options.formId = ''; + } + if (this.options.externalControl) + this.options.externalControl = $(this.options.externalControl); + if (!this.options.externalControl) + this.options.externalControlOnly = false; + this._originalBackground = this.element.getStyle('background-color') || 'transparent'; + this.element.title = this.options.clickToEditText; + this._boundCancelHandler = this.handleFormCancellation.bind(this); + this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this); + this._boundFailureHandler = this.handleAJAXFailure.bind(this); + this._boundSubmitHandler = this.handleFormSubmission.bind(this); + this._boundWrapperHandler = this.wrapUp.bind(this); + this.registerListeners(); + }, + checkForEscapeOrReturn: function(e) { + if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return; + if (Event.KEY_ESC == e.keyCode) + this.handleFormCancellation(e); + else if (Event.KEY_RETURN == e.keyCode) + this.handleFormSubmission(e); + }, + createControl: function(mode, handler, extraClasses) { + var control = this.options[mode + 'Control']; + var text = this.options[mode + 'Text']; + if ('button' == control) { + var btn = document.createElement('input'); + btn.type = 'submit'; + btn.value = text; + btn.className = 'editor_' + mode + '_button'; + if ('cancel' == mode) + btn.onclick = this._boundCancelHandler; + this._form.appendChild(btn); + this._controls[mode] = btn; + } else if ('link' == control) { + var link = document.createElement('a'); + link.href = '#'; + link.appendChild(document.createTextNode(text)); + link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler; + link.className = 'editor_' + mode + '_link'; + if (extraClasses) + link.className += ' ' + extraClasses; + this._form.appendChild(link); + this._controls[mode] = link; + } + }, + createEditField: function() { + var text = (this.options.loadTextURL ? this.options.loadingText : this.getText()); + var fld; + if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) { + fld = document.createElement('input'); + fld.type = 'text'; + var size = this.options.size || this.options.cols || 0; + if (0 < size) fld.size = size; + } else { + fld = document.createElement('textarea'); + fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows); + fld.cols = this.options.cols || 40; + } + fld.name = this.options.paramName; + fld.value = text; // No HTML breaks conversion anymore + fld.className = 'editor_field'; + if (this.options.submitOnBlur) + fld.onblur = this._boundSubmitHandler; + this._controls.editor = fld; + if (this.options.loadTextURL) + this.loadExternalText(); + this._form.appendChild(this._controls.editor); + }, + createForm: function() { + var ipe = this; + function addText(mode, condition) { + var text = ipe.options['text' + mode + 'Controls']; + if (!text || condition === false) return; + ipe._form.appendChild(document.createTextNode(text)); + }; + this._form = $(document.createElement('form')); + this._form.id = this.options.formId; + this._form.addClassName(this.options.formClassName); + this._form.onsubmit = this._boundSubmitHandler; + this.createEditField(); + if ('textarea' == this._controls.editor.tagName.toLowerCase()) + this._form.appendChild(document.createElement('br')); + if (this.options.onFormCustomization) + this.options.onFormCustomization(this, this._form); + addText('Before', this.options.okControl || this.options.cancelControl); + this.createControl('ok', this._boundSubmitHandler); + addText('Between', this.options.okControl && this.options.cancelControl); + this.createControl('cancel', this._boundCancelHandler, 'editor_cancel'); + addText('After', this.options.okControl || this.options.cancelControl); + }, + destroy: function() { + if (this._oldInnerHTML) + this.element.innerHTML = this._oldInnerHTML; + this.leaveEditMode(); + this.unregisterListeners(); + }, + enterEditMode: function(e) { + if (this._saving || this._editing) return; + this._editing = true; + this.triggerCallback('onEnterEditMode'); + if (this.options.externalControl) + this.options.externalControl.hide(); + this.element.hide(); + this.createForm(); + this.element.parentNode.insertBefore(this._form, this.element); + if (!this.options.loadTextURL) + this.postProcessEditField(); + if (e) Event.stop(e); + }, + enterHover: function(e) { + if (this.options.hoverClassName) + this.element.addClassName(this.options.hoverClassName); + if (this._saving) return; + this.triggerCallback('onEnterHover'); + }, + getText: function() { + return this.element.innerHTML; + }, + handleAJAXFailure: function(transport) { + this.triggerCallback('onFailure', transport); + if (this._oldInnerHTML) { + this.element.innerHTML = this._oldInnerHTML; + this._oldInnerHTML = null; + } + }, + handleFormCancellation: function(e) { + this.wrapUp(); + if (e) Event.stop(e); + }, + handleFormSubmission: function(e) { + var form = this._form; + var value = $F(this._controls.editor); + this.prepareSubmission(); + var params = this.options.callback(form, value) || ''; + if (Object.isString(params)) + params = params.toQueryParams(); + params.editorId = this.element.id; + if (this.options.htmlResponse) { + var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions); + Object.extend(options, { + parameters: params, + onComplete: this._boundWrapperHandler, + onFailure: this._boundFailureHandler + }); + new Ajax.Updater({ success: this.element }, this.url, options); + } else { + var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); + Object.extend(options, { + parameters: params, + onComplete: this._boundWrapperHandler, + onFailure: this._boundFailureHandler + }); + new Ajax.Request(this.url, options); + } + if (e) Event.stop(e); + }, + leaveEditMode: function() { + this.element.removeClassName(this.options.savingClassName); + this.removeForm(); + this.leaveHover(); + this.element.style.backgroundColor = this._originalBackground; + this.element.show(); + if (this.options.externalControl) + this.options.externalControl.show(); + this._saving = false; + this._editing = false; + this._oldInnerHTML = null; + this.triggerCallback('onLeaveEditMode'); + }, + leaveHover: function(e) { + if (this.options.hoverClassName) + this.element.removeClassName(this.options.hoverClassName); + if (this._saving) return; + this.triggerCallback('onLeaveHover'); + }, + loadExternalText: function() { + this._form.addClassName(this.options.loadingClassName); + this._controls.editor.disabled = true; + var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); + Object.extend(options, { + parameters: 'editorId=' + encodeURIComponent(this.element.id), + onComplete: Prototype.emptyFunction, + onSuccess: function(transport) { + this._form.removeClassName(this.options.loadingClassName); + var text = transport.responseText; + if (this.options.stripLoadedTextTags) + text = text.stripTags(); + this._controls.editor.value = text; + this._controls.editor.disabled = false; + this.postProcessEditField(); + }.bind(this), + onFailure: this._boundFailureHandler + }); + new Ajax.Request(this.options.loadTextURL, options); + }, + postProcessEditField: function() { + var fpc = this.options.fieldPostCreation; + if (fpc) + $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate'](); + }, + prepareOptions: function() { + this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions); + Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks); + [this._extraDefaultOptions].flatten().compact().each(function(defs) { + Object.extend(this.options, defs); + }.bind(this)); + }, + prepareSubmission: function() { + this._saving = true; + this.removeForm(); + this.leaveHover(); + this.showSaving(); + }, + registerListeners: function() { + this._listeners = { }; + var listener; + $H(Ajax.InPlaceEditor.Listeners).each(function(pair) { + listener = this[pair.value].bind(this); + this._listeners[pair.key] = listener; + if (!this.options.externalControlOnly) + this.element.observe(pair.key, listener); + if (this.options.externalControl) + this.options.externalControl.observe(pair.key, listener); + }.bind(this)); + }, + removeForm: function() { + if (!this._form) return; + this._form.remove(); + this._form = null; + this._controls = { }; + }, + showSaving: function() { + this._oldInnerHTML = this.element.innerHTML; + this.element.innerHTML = this.options.savingText; + this.element.addClassName(this.options.savingClassName); + this.element.style.backgroundColor = this._originalBackground; + this.element.show(); + }, + triggerCallback: function(cbName, arg) { + if ('function' == typeof this.options[cbName]) { + this.options[cbName](this, arg); + } + }, + unregisterListeners: function() { + $H(this._listeners).each(function(pair) { + if (!this.options.externalControlOnly) + this.element.stopObserving(pair.key, pair.value); + if (this.options.externalControl) + this.options.externalControl.stopObserving(pair.key, pair.value); + }.bind(this)); + }, + wrapUp: function(transport) { + this.leaveEditMode(); + // Can't use triggerCallback due to backward compatibility: requires + // binding + direct element + this._boundComplete(transport, this.element); + } +}); + +Object.extend(Ajax.InPlaceEditor.prototype, { + dispose: Ajax.InPlaceEditor.prototype.destroy +}); + +Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, { + initialize: function($super, element, url, options) { + this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions; + $super(element, url, options); + }, + + createEditField: function() { + var list = document.createElement('select'); + list.name = this.options.paramName; + list.size = 1; + this._controls.editor = list; + this._collection = this.options.collection || []; + if (this.options.loadCollectionURL) + this.loadCollection(); + else + this.checkForExternalText(); + this._form.appendChild(this._controls.editor); + }, + + loadCollection: function() { + this._form.addClassName(this.options.loadingClassName); + this.showLoadingText(this.options.loadingCollectionText); + var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); + Object.extend(options, { + parameters: 'editorId=' + encodeURIComponent(this.element.id), + onComplete: Prototype.emptyFunction, + onSuccess: function(transport) { + var js = transport.responseText.strip(); + if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check + throw 'Server returned an invalid collection representation.'; + this._collection = eval(js); + this.checkForExternalText(); + }.bind(this), + onFailure: this.onFailure + }); + new Ajax.Request(this.options.loadCollectionURL, options); + }, + + showLoadingText: function(text) { + this._controls.editor.disabled = true; + var tempOption = this._controls.editor.firstChild; + if (!tempOption) { + tempOption = document.createElement('option'); + tempOption.value = ''; + this._controls.editor.appendChild(tempOption); + tempOption.selected = true; + } + tempOption.update((text || '').stripScripts().stripTags()); + }, + + checkForExternalText: function() { + this._text = this.getText(); + if (this.options.loadTextURL) + this.loadExternalText(); + else + this.buildOptionList(); + }, + + loadExternalText: function() { + this.showLoadingText(this.options.loadingText); + var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); + Object.extend(options, { + parameters: 'editorId=' + encodeURIComponent(this.element.id), + onComplete: Prototype.emptyFunction, + onSuccess: function(transport) { + this._text = transport.responseText.strip(); + this.buildOptionList(); + }.bind(this), + onFailure: this.onFailure + }); + new Ajax.Request(this.options.loadTextURL, options); + }, + + buildOptionList: function() { + this._form.removeClassName(this.options.loadingClassName); + this._collection = this._collection.map(function(entry) { + return 2 === entry.length ? entry : [entry, entry].flatten(); + }); + var marker = ('value' in this.options) ? this.options.value : this._text; + var textFound = this._collection.any(function(entry) { + return entry[0] == marker; + }.bind(this)); + this._controls.editor.update(''); + var option; + this._collection.each(function(entry, index) { + option = document.createElement('option'); + option.value = entry[0]; + option.selected = textFound ? entry[0] == marker : 0 == index; + option.appendChild(document.createTextNode(entry[1])); + this._controls.editor.appendChild(option); + }.bind(this)); + this._controls.editor.disabled = false; + Field.scrollFreeActivate(this._controls.editor); + } +}); + +//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! **** +//**** This only exists for a while, in order to let **** +//**** users adapt to the new API. Read up on the new **** +//**** API and convert your code to it ASAP! **** + +Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) { + if (!options) return; + function fallback(name, expr) { + if (name in options || expr === undefined) return; + options[name] = expr; + }; + fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' : + options.cancelLink == options.cancelButton == false ? false : undefined))); + fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' : + options.okLink == options.okButton == false ? false : undefined))); + fallback('highlightColor', options.highlightcolor); + fallback('highlightEndColor', options.highlightendcolor); +}; + +Object.extend(Ajax.InPlaceEditor, { + DefaultOptions: { + ajaxOptions: { }, + autoRows: 3, // Use when multi-line w/ rows == 1 + cancelControl: 'link', // 'link'|'button'|false + cancelText: 'cancel', + clickToEditText: 'Click to edit', + externalControl: null, // id|elt + externalControlOnly: false, + fieldPostCreation: 'activate', // 'activate'|'focus'|false + formClassName: 'inplaceeditor-form', + formId: null, // id|elt + highlightColor: '#ffff99', + highlightEndColor: '#ffffff', + hoverClassName: '', + htmlResponse: true, + loadingClassName: 'inplaceeditor-loading', + loadingText: 'Loading...', + okControl: 'button', // 'link'|'button'|false + okText: 'ok', + paramName: 'value', + rows: 1, // If 1 and multi-line, uses autoRows + savingClassName: 'inplaceeditor-saving', + savingText: 'Saving...', + size: 0, + stripLoadedTextTags: false, + submitOnBlur: false, + textAfterControls: '', + textBeforeControls: '', + textBetweenControls: '' + }, + DefaultCallbacks: { + callback: function(form) { + return Form.serialize(form); + }, + onComplete: function(transport, element) { + // For backward compatibility, this one is bound to the IPE, and passes + // the element directly. It was too often customized, so we don't break it. + new Effect.Highlight(element, { + startcolor: this.options.highlightColor, keepBackgroundImage: true }); + }, + onEnterEditMode: null, + onEnterHover: function(ipe) { + ipe.element.style.backgroundColor = ipe.options.highlightColor; + if (ipe._effect) + ipe._effect.cancel(); + }, + onFailure: function(transport, ipe) { + alert('Error communication with the server: ' + transport.responseText.stripTags()); + }, + onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls. + onLeaveEditMode: null, + onLeaveHover: function(ipe) { + ipe._effect = new Effect.Highlight(ipe.element, { + startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor, + restorecolor: ipe._originalBackground, keepBackgroundImage: true + }); + } + }, + Listeners: { + click: 'enterEditMode', + keydown: 'checkForEscapeOrReturn', + mouseover: 'enterHover', + mouseout: 'leaveHover' + } +}); + +Ajax.InPlaceCollectionEditor.DefaultOptions = { + loadingCollectionText: 'Loading options...' +}; + +// Delayed observer, like Form.Element.Observer, +// but waits for delay after last key input +// Ideal for live-search fields + +Form.Element.DelayedObserver = Class.create({ + initialize: function(element, delay, callback) { + this.delay = delay || 0.5; + this.element = $(element); + this.callback = callback; + this.timer = null; + this.lastValue = $F(this.element); + Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this)); + }, + delayedListener: function(event) { + if(this.lastValue == $F(this.element)) return; + if(this.timer) clearTimeout(this.timer); + this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000); + this.lastValue = $F(this.element); + }, + onTimerEvent: function() { + this.timer = null; + this.callback(this.element, $F(this.element)); + } +}); diff --git a/public/javascripts/dragdrop.js b/public/javascripts/dragdrop.js new file mode 100644 index 0000000..ccf4a1e --- /dev/null +++ b/public/javascripts/dragdrop.js @@ -0,0 +1,972 @@ +// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005-2007 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz) +// +// script.aculo.us is freely distributable under the terms of an MIT-style license. +// For details, see the script.aculo.us web site: http://script.aculo.us/ + +if(Object.isUndefined(Effect)) + throw("dragdrop.js requires including script.aculo.us' effects.js library"); + +var Droppables = { + drops: [], + + remove: function(element) { + this.drops = this.drops.reject(function(d) { return d.element==$(element) }); + }, + + add: function(element) { + element = $(element); + var options = Object.extend({ + greedy: true, + hoverclass: null, + tree: false + }, arguments[1] || { }); + + // cache containers + if(options.containment) { + options._containers = []; + var containment = options.containment; + if(Object.isArray(containment)) { + containment.each( function(c) { options._containers.push($(c)) }); + } else { + options._containers.push($(containment)); + } + } + + if(options.accept) options.accept = [options.accept].flatten(); + + Element.makePositioned(element); // fix IE + options.element = element; + + this.drops.push(options); + }, + + findDeepestChild: function(drops) { + deepest = drops[0]; + + for (i = 1; i < drops.length; ++i) + if (Element.isParent(drops[i].element, deepest.element)) + deepest = drops[i]; + + return deepest; + }, + + isContained: function(element, drop) { + var containmentNode; + if(drop.tree) { + containmentNode = element.treeNode; + } else { + containmentNode = element.parentNode; + } + return drop._containers.detect(function(c) { return containmentNode == c }); + }, + + isAffected: function(point, element, drop) { + return ( + (drop.element!=element) && + ((!drop._containers) || + this.isContained(element, drop)) && + ((!drop.accept) || + (Element.classNames(element).detect( + function(v) { return drop.accept.include(v) } ) )) && + Position.within(drop.element, point[0], point[1]) ); + }, + + deactivate: function(drop) { + if(drop.hoverclass) + Element.removeClassName(drop.element, drop.hoverclass); + this.last_active = null; + }, + + activate: function(drop) { + if(drop.hoverclass) + Element.addClassName(drop.element, drop.hoverclass); + this.last_active = drop; + }, + + show: function(point, element) { + if(!this.drops.length) return; + var drop, affected = []; + + this.drops.each( function(drop) { + if(Droppables.isAffected(point, element, drop)) + affected.push(drop); + }); + + if(affected.length>0) + drop = Droppables.findDeepestChild(affected); + + if(this.last_active && this.last_active != drop) this.deactivate(this.last_active); + if (drop) { + Position.within(drop.element, point[0], point[1]); + if(drop.onHover) + drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); + + if (drop != this.last_active) Droppables.activate(drop); + } + }, + + fire: function(event, element) { + if(!this.last_active) return; + Position.prepare(); + + if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active)) + if (this.last_active.onDrop) { + this.last_active.onDrop(element, this.last_active.element, event); + return true; + } + }, + + reset: function() { + if(this.last_active) + this.deactivate(this.last_active); + } +} + +var Draggables = { + drags: [], + observers: [], + + register: function(draggable) { + if(this.drags.length == 0) { + this.eventMouseUp = this.endDrag.bindAsEventListener(this); + this.eventMouseMove = this.updateDrag.bindAsEventListener(this); + this.eventKeypress = this.keyPress.bindAsEventListener(this); + + Event.observe(document, "mouseup", this.eventMouseUp); + Event.observe(document, "mousemove", this.eventMouseMove); + Event.observe(document, "keypress", this.eventKeypress); + } + this.drags.push(draggable); + }, + + unregister: function(draggable) { + this.drags = this.drags.reject(function(d) { return d==draggable }); + if(this.drags.length == 0) { + Event.stopObserving(document, "mouseup", this.eventMouseUp); + Event.stopObserving(document, "mousemove", this.eventMouseMove); + Event.stopObserving(document, "keypress", this.eventKeypress); + } + }, + + activate: function(draggable) { + if(draggable.options.delay) { + this._timeout = setTimeout(function() { + Draggables._timeout = null; + window.focus(); + Draggables.activeDraggable = draggable; + }.bind(this), draggable.options.delay); + } else { + window.focus(); // allows keypress events if window isn't currently focused, fails for Safari + this.activeDraggable = draggable; + } + }, + + deactivate: function() { + this.activeDraggable = null; + }, + + updateDrag: function(event) { + if(!this.activeDraggable) return; + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + // Mozilla-based browsers fire successive mousemove events with + // the same coordinates, prevent needless redrawing (moz bug?) + if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return; + this._lastPointer = pointer; + + this.activeDraggable.updateDrag(event, pointer); + }, + + endDrag: function(event) { + if(this._timeout) { + clearTimeout(this._timeout); + this._timeout = null; + } + if(!this.activeDraggable) return; + this._lastPointer = null; + this.activeDraggable.endDrag(event); + this.activeDraggable = null; + }, + + keyPress: function(event) { + if(this.activeDraggable) + this.activeDraggable.keyPress(event); + }, + + addObserver: function(observer) { + this.observers.push(observer); + this._cacheObserverCallbacks(); + }, + + removeObserver: function(element) { // element instead of observer fixes mem leaks + this.observers = this.observers.reject( function(o) { return o.element==element }); + this._cacheObserverCallbacks(); + }, + + notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag' + if(this[eventName+'Count'] > 0) + this.observers.each( function(o) { + if(o[eventName]) o[eventName](eventName, draggable, event); + }); + if(draggable.options[eventName]) draggable.options[eventName](draggable, event); + }, + + _cacheObserverCallbacks: function() { + ['onStart','onEnd','onDrag'].each( function(eventName) { + Draggables[eventName+'Count'] = Draggables.observers.select( + function(o) { return o[eventName]; } + ).length; + }); + } +} + +/*--------------------------------------------------------------------------*/ + +var Draggable = Class.create({ + initialize: function(element) { + var defaults = { + handle: false, + reverteffect: function(element, top_offset, left_offset) { + var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02; + new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur, + queue: {scope:'_draggable', position:'end'} + }); + }, + endeffect: function(element) { + var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0; + new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, + queue: {scope:'_draggable', position:'end'}, + afterFinish: function(){ + Draggable._dragging[element] = false + } + }); + }, + zindex: 1000, + revert: false, + quiet: false, + scroll: false, + scrollSensitivity: 20, + scrollSpeed: 15, + snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] } + delay: 0 + }; + + if(!arguments[1] || Object.isUndefined(arguments[1].endeffect)) + Object.extend(defaults, { + starteffect: function(element) { + element._opacity = Element.getOpacity(element); + Draggable._dragging[element] = true; + new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); + } + }); + + var options = Object.extend(defaults, arguments[1] || { }); + + this.element = $(element); + + if(options.handle && Object.isString(options.handle)) + this.handle = this.element.down('.'+options.handle, 0); + + if(!this.handle) this.handle = $(options.handle); + if(!this.handle) this.handle = this.element; + + if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) { + options.scroll = $(options.scroll); + this._isScrollChild = Element.childOf(this.element, options.scroll); + } + + Element.makePositioned(this.element); // fix IE + + this.options = options; + this.dragging = false; + + this.eventMouseDown = this.initDrag.bindAsEventListener(this); + Event.observe(this.handle, "mousedown", this.eventMouseDown); + + Draggables.register(this); + }, + + destroy: function() { + Event.stopObserving(this.handle, "mousedown", this.eventMouseDown); + Draggables.unregister(this); + }, + + currentDelta: function() { + return([ + parseInt(Element.getStyle(this.element,'left') || '0'), + parseInt(Element.getStyle(this.element,'top') || '0')]); + }, + + initDrag: function(event) { + if(!Object.isUndefined(Draggable._dragging[this.element]) && + Draggable._dragging[this.element]) return; + if(Event.isLeftClick(event)) { + // abort on form elements, fixes a Firefox issue + var src = Event.element(event); + if((tag_name = src.tagName.toUpperCase()) && ( + tag_name=='INPUT' || + tag_name=='SELECT' || + tag_name=='OPTION' || + tag_name=='BUTTON' || + tag_name=='TEXTAREA')) return; + + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + var pos = Position.cumulativeOffset(this.element); + this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) }); + + Draggables.activate(this); + Event.stop(event); + } + }, + + startDrag: function(event) { + this.dragging = true; + if(!this.delta) + this.delta = this.currentDelta(); + + if(this.options.zindex) { + this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0); + this.element.style.zIndex = this.options.zindex; + } + + if(this.options.ghosting) { + this._clone = this.element.cloneNode(true); + this.element._originallyAbsolute = (this.element.getStyle('position') == 'absolute'); + if (!this.element._originallyAbsolute) + Position.absolutize(this.element); + this.element.parentNode.insertBefore(this._clone, this.element); + } + + if(this.options.scroll) { + if (this.options.scroll == window) { + var where = this._getWindowScroll(this.options.scroll); + this.originalScrollLeft = where.left; + this.originalScrollTop = where.top; + } else { + this.originalScrollLeft = this.options.scroll.scrollLeft; + this.originalScrollTop = this.options.scroll.scrollTop; + } + } + + Draggables.notify('onStart', this, event); + + if(this.options.starteffect) this.options.starteffect(this.element); + }, + + updateDrag: function(event, pointer) { + if(!this.dragging) this.startDrag(event); + + if(!this.options.quiet){ + Position.prepare(); + Droppables.show(pointer, this.element); + } + + Draggables.notify('onDrag', this, event); + + this.draw(pointer); + if(this.options.change) this.options.change(this); + + if(this.options.scroll) { + this.stopScrolling(); + + var p; + if (this.options.scroll == window) { + with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; } + } else { + p = Position.page(this.options.scroll); + p[0] += this.options.scroll.scrollLeft + Position.deltaX; + p[1] += this.options.scroll.scrollTop + Position.deltaY; + p.push(p[0]+this.options.scroll.offsetWidth); + p.push(p[1]+this.options.scroll.offsetHeight); + } + var speed = [0,0]; + if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity); + if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity); + if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity); + if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity); + this.startScrolling(speed); + } + + // fix AppleWebKit rendering + if(Prototype.Browser.WebKit) window.scrollBy(0,0); + + Event.stop(event); + }, + + finishDrag: function(event, success) { + this.dragging = false; + + if(this.options.quiet){ + Position.prepare(); + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + Droppables.show(pointer, this.element); + } + + if(this.options.ghosting) { + if (!this.element._originallyAbsolute) + Position.relativize(this.element); + delete this.element._originallyAbsolute; + Element.remove(this._clone); + this._clone = null; + } + + var dropped = false; + if(success) { + dropped = Droppables.fire(event, this.element); + if (!dropped) dropped = false; + } + if(dropped && this.options.onDropped) this.options.onDropped(this.element); + Draggables.notify('onEnd', this, event); + + var revert = this.options.revert; + if(revert && Object.isFunction(revert)) revert = revert(this.element); + + var d = this.currentDelta(); + if(revert && this.options.reverteffect) { + if (dropped == 0 || revert != 'failure') + this.options.reverteffect(this.element, + d[1]-this.delta[1], d[0]-this.delta[0]); + } else { + this.delta = d; + } + + if(this.options.zindex) + this.element.style.zIndex = this.originalZ; + + if(this.options.endeffect) + this.options.endeffect(this.element); + + Draggables.deactivate(this); + Droppables.reset(); + }, + + keyPress: function(event) { + if(event.keyCode!=Event.KEY_ESC) return; + this.finishDrag(event, false); + Event.stop(event); + }, + + endDrag: function(event) { + if(!this.dragging) return; + this.stopScrolling(); + this.finishDrag(event, true); + Event.stop(event); + }, + + draw: function(point) { + var pos = Position.cumulativeOffset(this.element); + if(this.options.ghosting) { + var r = Position.realOffset(this.element); + pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY; + } + + var d = this.currentDelta(); + pos[0] -= d[0]; pos[1] -= d[1]; + + if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) { + pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft; + pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop; + } + + var p = [0,1].map(function(i){ + return (point[i]-pos[i]-this.offset[i]) + }.bind(this)); + + if(this.options.snap) { + if(Object.isFunction(this.options.snap)) { + p = this.options.snap(p[0],p[1],this); + } else { + if(Object.isArray(this.options.snap)) { + p = p.map( function(v, i) { + return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this)) + } else { + p = p.map( function(v) { + return (v/this.options.snap).round()*this.options.snap }.bind(this)) + } + }} + + var style = this.element.style; + if((!this.options.constraint) || (this.options.constraint=='horizontal')) + style.left = p[0] + "px"; + if((!this.options.constraint) || (this.options.constraint=='vertical')) + style.top = p[1] + "px"; + + if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering + }, + + stopScrolling: function() { + if(this.scrollInterval) { + clearInterval(this.scrollInterval); + this.scrollInterval = null; + Draggables._lastScrollPointer = null; + } + }, + + startScrolling: function(speed) { + if(!(speed[0] || speed[1])) return; + this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed]; + this.lastScrolled = new Date(); + this.scrollInterval = setInterval(this.scroll.bind(this), 10); + }, + + scroll: function() { + var current = new Date(); + var delta = current - this.lastScrolled; + this.lastScrolled = current; + if(this.options.scroll == window) { + with (this._getWindowScroll(this.options.scroll)) { + if (this.scrollSpeed[0] || this.scrollSpeed[1]) { + var d = delta / 1000; + this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] ); + } + } + } else { + this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000; + this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000; + } + + Position.prepare(); + Droppables.show(Draggables._lastPointer, this.element); + Draggables.notify('onDrag', this); + if (this._isScrollChild) { + Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer); + Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000; + Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000; + if (Draggables._lastScrollPointer[0] < 0) + Draggables._lastScrollPointer[0] = 0; + if (Draggables._lastScrollPointer[1] < 0) + Draggables._lastScrollPointer[1] = 0; + this.draw(Draggables._lastScrollPointer); + } + + if(this.options.change) this.options.change(this); + }, + + _getWindowScroll: function(w) { + var T, L, W, H; + with (w.document) { + if (w.document.documentElement && documentElement.scrollTop) { + T = documentElement.scrollTop; + L = documentElement.scrollLeft; + } else if (w.document.body) { + T = body.scrollTop; + L = body.scrollLeft; + } + if (w.innerWidth) { + W = w.innerWidth; + H = w.innerHeight; + } else if (w.document.documentElement && documentElement.clientWidth) { + W = documentElement.clientWidth; + H = documentElement.clientHeight; + } else { + W = body.offsetWidth; + H = body.offsetHeight + } + } + return { top: T, left: L, width: W, height: H }; + } +}); + +Draggable._dragging = { }; + +/*--------------------------------------------------------------------------*/ + +var SortableObserver = Class.create({ + initialize: function(element, observer) { + this.element = $(element); + this.observer = observer; + this.lastValue = Sortable.serialize(this.element); + }, + + onStart: function() { + this.lastValue = Sortable.serialize(this.element); + }, + + onEnd: function() { + Sortable.unmark(); + if(this.lastValue != Sortable.serialize(this.element)) + this.observer(this.element) + } +}); + +var Sortable = { + SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/, + + sortables: { }, + + _findRootElement: function(element) { + while (element.tagName.toUpperCase() != "BODY") { + if(element.id && Sortable.sortables[element.id]) return element; + element = element.parentNode; + } + }, + + options: function(element) { + element = Sortable._findRootElement($(element)); + if(!element) return; + return Sortable.sortables[element.id]; + }, + + destroy: function(element){ + var s = Sortable.options(element); + + if(s) { + Draggables.removeObserver(s.element); + s.droppables.each(function(d){ Droppables.remove(d) }); + s.draggables.invoke('destroy'); + + delete Sortable.sortables[s.element.id]; + } + }, + + create: function(element) { + element = $(element); + var options = Object.extend({ + element: element, + tag: 'li', // assumes li children, override with tag: 'tagname' + dropOnEmpty: false, + tree: false, + treeTag: 'ul', + overlap: 'vertical', // one of 'vertical', 'horizontal' + constraint: 'vertical', // one of 'vertical', 'horizontal', false + containment: element, // also takes array of elements (or id's); or false + handle: false, // or a CSS class + only: false, + delay: 0, + hoverclass: null, + ghosting: false, + quiet: false, + scroll: false, + scrollSensitivity: 20, + scrollSpeed: 15, + format: this.SERIALIZE_RULE, + + // these take arrays of elements or ids and can be + // used for better initialization performance + elements: false, + handles: false, + + onChange: Prototype.emptyFunction, + onUpdate: Prototype.emptyFunction + }, arguments[1] || { }); + + // clear any old sortable with same element + this.destroy(element); + + // build options for the draggables + var options_for_draggable = { + revert: true, + quiet: options.quiet, + scroll: options.scroll, + scrollSpeed: options.scrollSpeed, + scrollSensitivity: options.scrollSensitivity, + delay: options.delay, + ghosting: options.ghosting, + constraint: options.constraint, + handle: options.handle }; + + if(options.starteffect) + options_for_draggable.starteffect = options.starteffect; + + if(options.reverteffect) + options_for_draggable.reverteffect = options.reverteffect; + else + if(options.ghosting) options_for_draggable.reverteffect = function(element) { + element.style.top = 0; + element.style.left = 0; + }; + + if(options.endeffect) + options_for_draggable.endeffect = options.endeffect; + + if(options.zindex) + options_for_draggable.zindex = options.zindex; + + // build options for the droppables + var options_for_droppable = { + overlap: options.overlap, + containment: options.containment, + tree: options.tree, + hoverclass: options.hoverclass, + onHover: Sortable.onHover + } + + var options_for_tree = { + onHover: Sortable.onEmptyHover, + overlap: options.overlap, + containment: options.containment, + hoverclass: options.hoverclass + } + + // fix for gecko engine + Element.cleanWhitespace(element); + + options.draggables = []; + options.droppables = []; + + // drop on empty handling + if(options.dropOnEmpty || options.tree) { + Droppables.add(element, options_for_tree); + options.droppables.push(element); + } + + (options.elements || this.findElements(element, options) || []).each( function(e,i) { + var handle = options.handles ? $(options.handles[i]) : + (options.handle ? $(e).select('.' + options.handle)[0] : e); + options.draggables.push( + new Draggable(e, Object.extend(options_for_draggable, { handle: handle }))); + Droppables.add(e, options_for_droppable); + if(options.tree) e.treeNode = element; + options.droppables.push(e); + }); + + if(options.tree) { + (Sortable.findTreeElements(element, options) || []).each( function(e) { + Droppables.add(e, options_for_tree); + e.treeNode = element; + options.droppables.push(e); + }); + } + + // keep reference + this.sortables[element.id] = options; + + // for onupdate + Draggables.addObserver(new SortableObserver(element, options.onUpdate)); + + }, + + // return all suitable-for-sortable elements in a guaranteed order + findElements: function(element, options) { + return Element.findChildren( + element, options.only, options.tree ? true : false, options.tag); + }, + + findTreeElements: function(element, options) { + return Element.findChildren( + element, options.only, options.tree ? true : false, options.treeTag); + }, + + onHover: function(element, dropon, overlap) { + if(Element.isParent(dropon, element)) return; + + if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) { + return; + } else if(overlap>0.5) { + Sortable.mark(dropon, 'before'); + if(dropon.previousSibling != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, dropon); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } else { + Sortable.mark(dropon, 'after'); + var nextElement = dropon.nextSibling || null; + if(nextElement != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, nextElement); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } + }, + + onEmptyHover: function(element, dropon, overlap) { + var oldParentNode = element.parentNode; + var droponOptions = Sortable.options(dropon); + + if(!Element.isParent(dropon, element)) { + var index; + + var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only}); + var child = null; + + if(children) { + var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap); + + for (index = 0; index < children.length; index += 1) { + if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) { + offset -= Element.offsetSize (children[index], droponOptions.overlap); + } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) { + child = index + 1 < children.length ? children[index + 1] : null; + break; + } else { + child = children[index]; + break; + } + } + } + + dropon.insertBefore(element, child); + + Sortable.options(oldParentNode).onChange(element); + droponOptions.onChange(element); + } + }, + + unmark: function() { + if(Sortable._marker) Sortable._marker.hide(); + }, + + mark: function(dropon, position) { + // mark on ghosting only + var sortable = Sortable.options(dropon.parentNode); + if(sortable && !sortable.ghosting) return; + + if(!Sortable._marker) { + Sortable._marker = + ($('dropmarker') || Element.extend(document.createElement('DIV'))). + hide().addClassName('dropmarker').setStyle({position:'absolute'}); + document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); + } + var offsets = Position.cumulativeOffset(dropon); + Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'}); + + if(position=='after') + if(sortable.overlap == 'horizontal') + Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'}); + else + Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'}); + + Sortable._marker.show(); + }, + + _tree: function(element, options, parent) { + var children = Sortable.findElements(element, options) || []; + + for (var i = 0; i < children.length; ++i) { + var match = children[i].id.match(options.format); + + if (!match) continue; + + var child = { + id: encodeURIComponent(match ? match[1] : null), + element: element, + parent: parent, + children: [], + position: parent.children.length, + container: $(children[i]).down(options.treeTag) + } + + /* Get the element containing the children and recurse over it */ + if (child.container) + this._tree(child.container, options, child) + + parent.children.push (child); + } + + return parent; + }, + + tree: function(element) { + element = $(element); + var sortableOptions = this.options(element); + var options = Object.extend({ + tag: sortableOptions.tag, + treeTag: sortableOptions.treeTag, + only: sortableOptions.only, + name: element.id, + format: sortableOptions.format + }, arguments[1] || { }); + + var root = { + id: null, + parent: null, + children: [], + container: element, + position: 0 + } + + return Sortable._tree(element, options, root); + }, + + /* Construct a [i] index for a particular node */ + _constructIndex: function(node) { + var index = ''; + do { + if (node.id) index = '[' + node.position + ']' + index; + } while ((node = node.parent) != null); + return index; + }, + + sequence: function(element) { + element = $(element); + var options = Object.extend(this.options(element), arguments[1] || { }); + + return $(this.findElements(element, options) || []).map( function(item) { + return item.id.match(options.format) ? item.id.match(options.format)[1] : ''; + }); + }, + + setSequence: function(element, new_sequence) { + element = $(element); + var options = Object.extend(this.options(element), arguments[2] || { }); + + var nodeMap = { }; + this.findElements(element, options).each( function(n) { + if (n.id.match(options.format)) + nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode]; + n.parentNode.removeChild(n); + }); + + new_sequence.each(function(ident) { + var n = nodeMap[ident]; + if (n) { + n[1].appendChild(n[0]); + delete nodeMap[ident]; + } + }); + }, + + serialize: function(element) { + element = $(element); + var options = Object.extend(Sortable.options(element), arguments[1] || { }); + var name = encodeURIComponent( + (arguments[1] && arguments[1].name) ? arguments[1].name : element.id); + + if (options.tree) { + return Sortable.tree(element, arguments[1]).children.map( function (item) { + return [name + Sortable._constructIndex(item) + "[id]=" + + encodeURIComponent(item.id)].concat(item.children.map(arguments.callee)); + }).flatten().join('&'); + } else { + return Sortable.sequence(element, arguments[1]).map( function(item) { + return name + "[]=" + encodeURIComponent(item); + }).join('&'); + } + } +} + +// Returns true if child is contained within element +Element.isParent = function(child, element) { + if (!child.parentNode || child == element) return false; + if (child.parentNode == element) return true; + return Element.isParent(child.parentNode, element); +} + +Element.findChildren = function(element, only, recursive, tagName) { + if(!element.hasChildNodes()) return null; + tagName = tagName.toUpperCase(); + if(only) only = [only].flatten(); + var elements = []; + $A(element.childNodes).each( function(e) { + if(e.tagName && e.tagName.toUpperCase()==tagName && + (!only || (Element.classNames(e).detect(function(v) { return only.include(v) })))) + elements.push(e); + if(recursive) { + var grandchildren = Element.findChildren(e, only, recursive, tagName); + if(grandchildren) elements.push(grandchildren); + } + }); + + return (elements.length>0 ? elements.flatten() : []); +} + +Element.offsetSize = function (element, type) { + return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')]; +} diff --git a/public/javascripts/effects.js b/public/javascripts/effects.js new file mode 100644 index 0000000..65aed23 --- /dev/null +++ b/public/javascripts/effects.js @@ -0,0 +1,1120 @@ +// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// Contributors: +// Justin Palmer (http://encytemedia.com/) +// Mark Pilgrim (http://diveintomark.org/) +// Martin Bialasinki +// +// script.aculo.us is freely distributable under the terms of an MIT-style license. +// For details, see the script.aculo.us web site: http://script.aculo.us/ + +// converts rgb() and #xxx to #xxxxxx format, +// returns self (or first argument) if not convertable +String.prototype.parseColor = function() { + var color = '#'; + if (this.slice(0,4) == 'rgb(') { + var cols = this.slice(4,this.length-1).split(','); + var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3); + } else { + if (this.slice(0,1) == '#') { + if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase(); + if (this.length==7) color = this.toLowerCase(); + } + } + return (color.length==7 ? color : (arguments[0] || this)); +}; + +/*--------------------------------------------------------------------------*/ + +Element.collectTextNodes = function(element) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + (node.hasChildNodes() ? Element.collectTextNodes(node) : '')); + }).flatten().join(''); +}; + +Element.collectTextNodesIgnoreClass = function(element, className) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? + Element.collectTextNodesIgnoreClass(node, className) : '')); + }).flatten().join(''); +}; + +Element.setContentZoom = function(element, percent) { + element = $(element); + element.setStyle({fontSize: (percent/100) + 'em'}); + if (Prototype.Browser.WebKit) window.scrollBy(0,0); + return element; +}; + +Element.getInlineOpacity = function(element){ + return $(element).style.opacity || ''; +}; + +Element.forceRerendering = function(element) { + try { + element = $(element); + var n = document.createTextNode(' '); + element.appendChild(n); + element.removeChild(n); + } catch(e) { } +}; + +/*--------------------------------------------------------------------------*/ + +var Effect = { + _elementDoesNotExistError: { + name: 'ElementDoesNotExistError', + message: 'The specified DOM element does not exist, but is required for this effect to operate' + }, + Transitions: { + linear: Prototype.K, + sinoidal: function(pos) { + return (-Math.cos(pos*Math.PI)/2) + 0.5; + }, + reverse: function(pos) { + return 1-pos; + }, + flicker: function(pos) { + var pos = ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4; + return pos > 1 ? 1 : pos; + }, + wobble: function(pos) { + return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5; + }, + pulse: function(pos, pulses) { + pulses = pulses || 5; + return ( + ((pos % (1/pulses)) * pulses).round() == 0 ? + ((pos * pulses * 2) - (pos * pulses * 2).floor()) : + 1 - ((pos * pulses * 2) - (pos * pulses * 2).floor()) + ); + }, + spring: function(pos) { + return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6)); + }, + none: function(pos) { + return 0; + }, + full: function(pos) { + return 1; + } + }, + DefaultOptions: { + duration: 1.0, // seconds + fps: 100, // 100= assume 66fps max. + sync: false, // true for combining + from: 0.0, + to: 1.0, + delay: 0.0, + queue: 'parallel' + }, + tagifyText: function(element) { + var tagifyStyle = 'position:relative'; + if (Prototype.Browser.IE) tagifyStyle += ';zoom:1'; + + element = $(element); + $A(element.childNodes).each( function(child) { + if (child.nodeType==3) { + child.nodeValue.toArray().each( function(character) { + element.insertBefore( + new Element('span', {style: tagifyStyle}).update( + character == ' ' ? String.fromCharCode(160) : character), + child); + }); + Element.remove(child); + } + }); + }, + multiple: function(element, effect) { + var elements; + if (((typeof element == 'object') || + Object.isFunction(element)) && + (element.length)) + elements = element; + else + elements = $(element).childNodes; + + var options = Object.extend({ + speed: 0.1, + delay: 0.0 + }, arguments[2] || { }); + var masterDelay = options.delay; + + $A(elements).each( function(element, index) { + new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay })); + }); + }, + PAIRS: { + 'slide': ['SlideDown','SlideUp'], + 'blind': ['BlindDown','BlindUp'], + 'appear': ['Appear','Fade'] + }, + toggle: function(element, effect) { + element = $(element); + effect = (effect || 'appear').toLowerCase(); + var options = Object.extend({ + queue: { position:'end', scope:(element.id || 'global'), limit: 1 } + }, arguments[2] || { }); + Effect[element.visible() ? + Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options); + } +}; + +Effect.DefaultOptions.transition = Effect.Transitions.sinoidal; + +/* ------------- core effects ------------- */ + +Effect.ScopedQueue = Class.create(Enumerable, { + initialize: function() { + this.effects = []; + this.interval = null; + }, + _each: function(iterator) { + this.effects._each(iterator); + }, + add: function(effect) { + var timestamp = new Date().getTime(); + + var position = Object.isString(effect.options.queue) ? + effect.options.queue : effect.options.queue.position; + + switch(position) { + case 'front': + // move unstarted effects after this effect + this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) { + e.startOn += effect.finishOn; + e.finishOn += effect.finishOn; + }); + break; + case 'with-last': + timestamp = this.effects.pluck('startOn').max() || timestamp; + break; + case 'end': + // start effect after last queued effect has finished + timestamp = this.effects.pluck('finishOn').max() || timestamp; + break; + } + + effect.startOn += timestamp; + effect.finishOn += timestamp; + + if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit)) + this.effects.push(effect); + + if (!this.interval) + this.interval = setInterval(this.loop.bind(this), 15); + }, + remove: function(effect) { + this.effects = this.effects.reject(function(e) { return e==effect }); + if (this.effects.length == 0) { + clearInterval(this.interval); + this.interval = null; + } + }, + loop: function() { + var timePos = new Date().getTime(); + for(var i=0, len=this.effects.length;i= this.startOn) { + if (timePos >= this.finishOn) { + this.render(1.0); + this.cancel(); + this.event('beforeFinish'); + if (this.finish) this.finish(); + this.event('afterFinish'); + return; + } + var pos = (timePos - this.startOn) / this.totalTime, + frame = (pos * this.totalFrames).round(); + if (frame > this.currentFrame) { + this.render(pos); + this.currentFrame = frame; + } + } + }, + cancel: function() { + if (!this.options.sync) + Effect.Queues.get(Object.isString(this.options.queue) ? + 'global' : this.options.queue.scope).remove(this); + this.state = 'finished'; + }, + event: function(eventName) { + if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this); + if (this.options[eventName]) this.options[eventName](this); + }, + inspect: function() { + var data = $H(); + for(property in this) + if (!Object.isFunction(this[property])) data.set(property, this[property]); + return '#'; + } +}); + +Effect.Parallel = Class.create(Effect.Base, { + initialize: function(effects) { + this.effects = effects || []; + this.start(arguments[1]); + }, + update: function(position) { + this.effects.invoke('render', position); + }, + finish: function(position) { + this.effects.each( function(effect) { + effect.render(1.0); + effect.cancel(); + effect.event('beforeFinish'); + if (effect.finish) effect.finish(position); + effect.event('afterFinish'); + }); + } +}); + +Effect.Tween = Class.create(Effect.Base, { + initialize: function(object, from, to) { + object = Object.isString(object) ? $(object) : object; + var args = $A(arguments), method = args.last(), + options = args.length == 5 ? args[3] : null; + this.method = Object.isFunction(method) ? method.bind(object) : + Object.isFunction(object[method]) ? object[method].bind(object) : + function(value) { object[method] = value }; + this.start(Object.extend({ from: from, to: to }, options || { })); + }, + update: function(position) { + this.method(position); + } +}); + +Effect.Event = Class.create(Effect.Base, { + initialize: function() { + this.start(Object.extend({ duration: 0 }, arguments[0] || { })); + }, + update: Prototype.emptyFunction +}); + +Effect.Opacity = Class.create(Effect.Base, { + initialize: function(element) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + // make this work on IE on elements without 'layout' + if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout)) + this.element.setStyle({zoom: 1}); + var options = Object.extend({ + from: this.element.getOpacity() || 0.0, + to: 1.0 + }, arguments[1] || { }); + this.start(options); + }, + update: function(position) { + this.element.setOpacity(position); + } +}); + +Effect.Move = Class.create(Effect.Base, { + initialize: function(element) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + x: 0, + y: 0, + mode: 'relative' + }, arguments[1] || { }); + this.start(options); + }, + setup: function() { + this.element.makePositioned(); + this.originalLeft = parseFloat(this.element.getStyle('left') || '0'); + this.originalTop = parseFloat(this.element.getStyle('top') || '0'); + if (this.options.mode == 'absolute') { + this.options.x = this.options.x - this.originalLeft; + this.options.y = this.options.y - this.originalTop; + } + }, + update: function(position) { + this.element.setStyle({ + left: (this.options.x * position + this.originalLeft).round() + 'px', + top: (this.options.y * position + this.originalTop).round() + 'px' + }); + } +}); + +// for backwards compatibility +Effect.MoveBy = function(element, toTop, toLeft) { + return new Effect.Move(element, + Object.extend({ x: toLeft, y: toTop }, arguments[3] || { })); +}; + +Effect.Scale = Class.create(Effect.Base, { + initialize: function(element, percent) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + scaleX: true, + scaleY: true, + scaleContent: true, + scaleFromCenter: false, + scaleMode: 'box', // 'box' or 'contents' or { } with provided values + scaleFrom: 100.0, + scaleTo: percent + }, arguments[2] || { }); + this.start(options); + }, + setup: function() { + this.restoreAfterFinish = this.options.restoreAfterFinish || false; + this.elementPositioning = this.element.getStyle('position'); + + this.originalStyle = { }; + ['top','left','width','height','fontSize'].each( function(k) { + this.originalStyle[k] = this.element.style[k]; + }.bind(this)); + + this.originalTop = this.element.offsetTop; + this.originalLeft = this.element.offsetLeft; + + var fontSize = this.element.getStyle('font-size') || '100%'; + ['em','px','%','pt'].each( function(fontSizeType) { + if (fontSize.indexOf(fontSizeType)>0) { + this.fontSize = parseFloat(fontSize); + this.fontSizeType = fontSizeType; + } + }.bind(this)); + + this.factor = (this.options.scaleTo - this.options.scaleFrom)/100; + + this.dims = null; + if (this.options.scaleMode=='box') + this.dims = [this.element.offsetHeight, this.element.offsetWidth]; + if (/^content/.test(this.options.scaleMode)) + this.dims = [this.element.scrollHeight, this.element.scrollWidth]; + if (!this.dims) + this.dims = [this.options.scaleMode.originalHeight, + this.options.scaleMode.originalWidth]; + }, + update: function(position) { + var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); + if (this.options.scaleContent && this.fontSize) + this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType }); + this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale); + }, + finish: function(position) { + if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle); + }, + setDimensions: function(height, width) { + var d = { }; + if (this.options.scaleX) d.width = width.round() + 'px'; + if (this.options.scaleY) d.height = height.round() + 'px'; + if (this.options.scaleFromCenter) { + var topd = (height - this.dims[0])/2; + var leftd = (width - this.dims[1])/2; + if (this.elementPositioning == 'absolute') { + if (this.options.scaleY) d.top = this.originalTop-topd + 'px'; + if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px'; + } else { + if (this.options.scaleY) d.top = -topd + 'px'; + if (this.options.scaleX) d.left = -leftd + 'px'; + } + } + this.element.setStyle(d); + } +}); + +Effect.Highlight = Class.create(Effect.Base, { + initialize: function(element) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { }); + this.start(options); + }, + setup: function() { + // Prevent executing on elements not in the layout flow + if (this.element.getStyle('display')=='none') { this.cancel(); return; } + // Disable background image during the effect + this.oldStyle = { }; + if (!this.options.keepBackgroundImage) { + this.oldStyle.backgroundImage = this.element.getStyle('background-image'); + this.element.setStyle({backgroundImage: 'none'}); + } + if (!this.options.endcolor) + this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff'); + if (!this.options.restorecolor) + this.options.restorecolor = this.element.getStyle('background-color'); + // init color calculations + this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this)); + this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this)); + }, + update: function(position) { + this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){ + return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) }); + }, + finish: function() { + this.element.setStyle(Object.extend(this.oldStyle, { + backgroundColor: this.options.restorecolor + })); + } +}); + +Effect.ScrollTo = function(element) { + var options = arguments[1] || { }, + scrollOffsets = document.viewport.getScrollOffsets(), + elementOffsets = $(element).cumulativeOffset(), + max = (window.height || document.body.scrollHeight) - document.viewport.getHeight(); + + if (options.offset) elementOffsets[1] += options.offset; + + return new Effect.Tween(null, + scrollOffsets.top, + elementOffsets[1] > max ? max : elementOffsets[1], + options, + function(p){ scrollTo(scrollOffsets.left, p.round()) } + ); +}; + +/* ------------- combination effects ------------- */ + +Effect.Fade = function(element) { + element = $(element); + var oldOpacity = element.getInlineOpacity(); + var options = Object.extend({ + from: element.getOpacity() || 1.0, + to: 0.0, + afterFinishInternal: function(effect) { + if (effect.options.to!=0) return; + effect.element.hide().setStyle({opacity: oldOpacity}); + } + }, arguments[1] || { }); + return new Effect.Opacity(element,options); +}; + +Effect.Appear = function(element) { + element = $(element); + var options = Object.extend({ + from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0), + to: 1.0, + // force Safari to render floated elements properly + afterFinishInternal: function(effect) { + effect.element.forceRerendering(); + }, + beforeSetup: function(effect) { + effect.element.setOpacity(effect.options.from).show(); + }}, arguments[1] || { }); + return new Effect.Opacity(element,options); +}; + +Effect.Puff = function(element) { + element = $(element); + var oldStyle = { + opacity: element.getInlineOpacity(), + position: element.getStyle('position'), + top: element.style.top, + left: element.style.left, + width: element.style.width, + height: element.style.height + }; + return new Effect.Parallel( + [ new Effect.Scale(element, 200, + { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), + new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], + Object.extend({ duration: 1.0, + beforeSetupInternal: function(effect) { + Position.absolutize(effect.effects[0].element) + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().setStyle(oldStyle); } + }, arguments[1] || { }) + ); +}; + +Effect.BlindUp = function(element) { + element = $(element); + element.makeClipping(); + return new Effect.Scale(element, 0, + Object.extend({ scaleContent: false, + scaleX: false, + restoreAfterFinish: true, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping(); + } + }, arguments[1] || { }) + ); +}; + +Effect.BlindDown = function(element) { + element = $(element); + var elementDimensions = element.getDimensions(); + return new Effect.Scale(element, 100, Object.extend({ + scaleContent: false, + scaleX: false, + scaleFrom: 0, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makeClipping().setStyle({height: '0px'}).show(); + }, + afterFinishInternal: function(effect) { + effect.element.undoClipping(); + } + }, arguments[1] || { })); +}; + +Effect.SwitchOff = function(element) { + element = $(element); + var oldOpacity = element.getInlineOpacity(); + return new Effect.Appear(element, Object.extend({ + duration: 0.4, + from: 0, + transition: Effect.Transitions.flicker, + afterFinishInternal: function(effect) { + new Effect.Scale(effect.element, 1, { + duration: 0.3, scaleFromCenter: true, + scaleX: false, scaleContent: false, restoreAfterFinish: true, + beforeSetup: function(effect) { + effect.element.makePositioned().makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity}); + } + }) + } + }, arguments[1] || { })); +}; + +Effect.DropOut = function(element) { + element = $(element); + var oldStyle = { + top: element.getStyle('top'), + left: element.getStyle('left'), + opacity: element.getInlineOpacity() }; + return new Effect.Parallel( + [ new Effect.Move(element, {x: 0, y: 100, sync: true }), + new Effect.Opacity(element, { sync: true, to: 0.0 }) ], + Object.extend( + { duration: 0.5, + beforeSetup: function(effect) { + effect.effects[0].element.makePositioned(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle); + } + }, arguments[1] || { })); +}; + +Effect.Shake = function(element) { + element = $(element); + var options = Object.extend({ + distance: 20, + duration: 0.5 + }, arguments[1] || {}); + var distance = parseFloat(options.distance); + var split = parseFloat(options.duration) / 10.0; + var oldStyle = { + top: element.getStyle('top'), + left: element.getStyle('left') }; + return new Effect.Move(element, + { x: distance, y: 0, duration: split, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) { + effect.element.undoPositioned().setStyle(oldStyle); + }}) }}) }}) }}) }}) }}); +}; + +Effect.SlideDown = function(element) { + element = $(element).cleanWhitespace(); + // SlideDown need to have the content of the element wrapped in a container element with fixed height! + var oldInnerBottom = element.down().getStyle('bottom'); + var elementDimensions = element.getDimensions(); + return new Effect.Scale(element, 100, Object.extend({ + scaleContent: false, + scaleX: false, + scaleFrom: window.opera ? 0 : 1, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makePositioned(); + effect.element.down().makePositioned(); + if (window.opera) effect.element.setStyle({top: ''}); + effect.element.makeClipping().setStyle({height: '0px'}).show(); + }, + afterUpdateInternal: function(effect) { + effect.element.down().setStyle({bottom: + (effect.dims[0] - effect.element.clientHeight) + 'px' }); + }, + afterFinishInternal: function(effect) { + effect.element.undoClipping().undoPositioned(); + effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); } + }, arguments[1] || { }) + ); +}; + +Effect.SlideUp = function(element) { + element = $(element).cleanWhitespace(); + var oldInnerBottom = element.down().getStyle('bottom'); + var elementDimensions = element.getDimensions(); + return new Effect.Scale(element, window.opera ? 0 : 1, + Object.extend({ scaleContent: false, + scaleX: false, + scaleMode: 'box', + scaleFrom: 100, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makePositioned(); + effect.element.down().makePositioned(); + if (window.opera) effect.element.setStyle({top: ''}); + effect.element.makeClipping().show(); + }, + afterUpdateInternal: function(effect) { + effect.element.down().setStyle({bottom: + (effect.dims[0] - effect.element.clientHeight) + 'px' }); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().undoPositioned(); + effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); + } + }, arguments[1] || { }) + ); +}; + +// Bug in opera makes the TD containing this element expand for a instance after finish +Effect.Squish = function(element) { + return new Effect.Scale(element, window.opera ? 1 : 0, { + restoreAfterFinish: true, + beforeSetup: function(effect) { + effect.element.makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping(); + } + }); +}; + +Effect.Grow = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransition: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.full + }, arguments[1] || { }); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: element.getInlineOpacity() }; + + var dims = element.getDimensions(); + var initialMoveX, initialMoveY; + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + initialMoveX = initialMoveY = moveX = moveY = 0; + break; + case 'top-right': + initialMoveX = dims.width; + initialMoveY = moveY = 0; + moveX = -dims.width; + break; + case 'bottom-left': + initialMoveX = moveX = 0; + initialMoveY = dims.height; + moveY = -dims.height; + break; + case 'bottom-right': + initialMoveX = dims.width; + initialMoveY = dims.height; + moveX = -dims.width; + moveY = -dims.height; + break; + case 'center': + initialMoveX = dims.width / 2; + initialMoveY = dims.height / 2; + moveX = -dims.width / 2; + moveY = -dims.height / 2; + break; + } + + return new Effect.Move(element, { + x: initialMoveX, + y: initialMoveY, + duration: 0.01, + beforeSetup: function(effect) { + effect.element.hide().makeClipping().makePositioned(); + }, + afterFinishInternal: function(effect) { + new Effect.Parallel( + [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }), + new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }), + new Effect.Scale(effect.element, 100, { + scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, + sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true}) + ], Object.extend({ + beforeSetup: function(effect) { + effect.effects[0].element.setStyle({height: '0px'}).show(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle); + } + }, options) + ) + } + }); +}; + +Effect.Shrink = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransition: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.none + }, arguments[1] || { }); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: element.getInlineOpacity() }; + + var dims = element.getDimensions(); + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + moveX = moveY = 0; + break; + case 'top-right': + moveX = dims.width; + moveY = 0; + break; + case 'bottom-left': + moveX = 0; + moveY = dims.height; + break; + case 'bottom-right': + moveX = dims.width; + moveY = dims.height; + break; + case 'center': + moveX = dims.width / 2; + moveY = dims.height / 2; + break; + } + + return new Effect.Parallel( + [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }), + new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}), + new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }) + ], Object.extend({ + beforeStartInternal: function(effect) { + effect.effects[0].element.makePositioned().makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); } + }, options) + ); +}; + +Effect.Pulsate = function(element) { + element = $(element); + var options = arguments[1] || { }; + var oldOpacity = element.getInlineOpacity(); + var transition = options.transition || Effect.Transitions.sinoidal; + var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos, options.pulses)) }; + reverser.bind(transition); + return new Effect.Opacity(element, + Object.extend(Object.extend({ duration: 2.0, from: 0, + afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); } + }, options), {transition: reverser})); +}; + +Effect.Fold = function(element) { + element = $(element); + var oldStyle = { + top: element.style.top, + left: element.style.left, + width: element.style.width, + height: element.style.height }; + element.makeClipping(); + return new Effect.Scale(element, 5, Object.extend({ + scaleContent: false, + scaleX: false, + afterFinishInternal: function(effect) { + new Effect.Scale(element, 1, { + scaleContent: false, + scaleY: false, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().setStyle(oldStyle); + } }); + }}, arguments[1] || { })); +}; + +Effect.Morph = Class.create(Effect.Base, { + initialize: function(element) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + style: { } + }, arguments[1] || { }); + + if (!Object.isString(options.style)) this.style = $H(options.style); + else { + if (options.style.include(':')) + this.style = options.style.parseStyle(); + else { + this.element.addClassName(options.style); + this.style = $H(this.element.getStyles()); + this.element.removeClassName(options.style); + var css = this.element.getStyles(); + this.style = this.style.reject(function(style) { + return style.value == css[style.key]; + }); + options.afterFinishInternal = function(effect) { + effect.element.addClassName(effect.options.style); + effect.transforms.each(function(transform) { + effect.element.style[transform.style] = ''; + }); + } + } + } + this.start(options); + }, + + setup: function(){ + function parseColor(color){ + if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff'; + color = color.parseColor(); + return $R(0,2).map(function(i){ + return parseInt( color.slice(i*2+1,i*2+3), 16 ) + }); + } + this.transforms = this.style.map(function(pair){ + var property = pair[0], value = pair[1], unit = null; + + if (value.parseColor('#zzzzzz') != '#zzzzzz') { + value = value.parseColor(); + unit = 'color'; + } else if (property == 'opacity') { + value = parseFloat(value); + if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout)) + this.element.setStyle({zoom: 1}); + } else if (Element.CSS_LENGTH.test(value)) { + var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/); + value = parseFloat(components[1]); + unit = (components.length == 3) ? components[2] : null; + } + + var originalValue = this.element.getStyle(property); + return { + style: property.camelize(), + originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0), + targetValue: unit=='color' ? parseColor(value) : value, + unit: unit + }; + }.bind(this)).reject(function(transform){ + return ( + (transform.originalValue == transform.targetValue) || + ( + transform.unit != 'color' && + (isNaN(transform.originalValue) || isNaN(transform.targetValue)) + ) + ) + }); + }, + update: function(position) { + var style = { }, transform, i = this.transforms.length; + while(i--) + style[(transform = this.transforms[i]).style] = + transform.unit=='color' ? '#'+ + (Math.round(transform.originalValue[0]+ + (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() + + (Math.round(transform.originalValue[1]+ + (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() + + (Math.round(transform.originalValue[2]+ + (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() : + (transform.originalValue + + (transform.targetValue - transform.originalValue) * position).toFixed(3) + + (transform.unit === null ? '' : transform.unit); + this.element.setStyle(style, true); + } +}); + +Effect.Transform = Class.create({ + initialize: function(tracks){ + this.tracks = []; + this.options = arguments[1] || { }; + this.addTracks(tracks); + }, + addTracks: function(tracks){ + tracks.each(function(track){ + track = $H(track); + var data = track.values().first(); + this.tracks.push($H({ + ids: track.keys().first(), + effect: Effect.Morph, + options: { style: data } + })); + }.bind(this)); + return this; + }, + play: function(){ + return new Effect.Parallel( + this.tracks.map(function(track){ + var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options'); + var elements = [$(ids) || $$(ids)].flatten(); + return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) }); + }).flatten(), + this.options + ); + } +}); + +Element.CSS_PROPERTIES = $w( + 'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' + + 'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' + + 'borderRightColor borderRightStyle borderRightWidth borderSpacing ' + + 'borderTopColor borderTopStyle borderTopWidth bottom clip color ' + + 'fontSize fontWeight height left letterSpacing lineHeight ' + + 'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+ + 'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' + + 'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' + + 'right textIndent top width wordSpacing zIndex'); + +Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/; + +String.__parseStyleElement = document.createElement('div'); +String.prototype.parseStyle = function(){ + var style, styleRules = $H(); + if (Prototype.Browser.WebKit) + style = new Element('div',{style:this}).style; + else { + String.__parseStyleElement.innerHTML = '
    '; + style = String.__parseStyleElement.childNodes[0].style; + } + + Element.CSS_PROPERTIES.each(function(property){ + if (style[property]) styleRules.set(property, style[property]); + }); + + if (Prototype.Browser.IE && this.include('opacity')) + styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]); + + return styleRules; +}; + +if (document.defaultView && document.defaultView.getComputedStyle) { + Element.getStyles = function(element) { + var css = document.defaultView.getComputedStyle($(element), null); + return Element.CSS_PROPERTIES.inject({ }, function(styles, property) { + styles[property] = css[property]; + return styles; + }); + }; +} else { + Element.getStyles = function(element) { + element = $(element); + var css = element.currentStyle, styles; + styles = Element.CSS_PROPERTIES.inject({ }, function(hash, property) { + hash.set(property, css[property]); + return hash; + }); + if (!styles.opacity) styles.set('opacity', element.getOpacity()); + return styles; + }; +}; + +Effect.Methods = { + morph: function(element, style) { + element = $(element); + new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { })); + return element; + }, + visualEffect: function(element, effect, options) { + element = $(element) + var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1); + new Effect[klass](element, options); + return element; + }, + highlight: function(element, options) { + element = $(element); + new Effect.Highlight(element, options); + return element; + } +}; + +$w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+ + 'pulsate shake puff squish switchOff dropOut').each( + function(effect) { + Effect.Methods[effect] = function(element, options){ + element = $(element); + Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options); + return element; + } + } +); + +$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each( + function(f) { Effect.Methods[f] = Element[f]; } +); + +Element.addMethods(Effect.Methods); diff --git a/public/javascripts/prototype.js b/public/javascripts/prototype.js new file mode 100644 index 0000000..546f9fe --- /dev/null +++ b/public/javascripts/prototype.js @@ -0,0 +1,4225 @@ +/* Prototype JavaScript framework, version 1.6.0.1 + * (c) 2005-2007 Sam Stephenson + * + * Prototype is freely distributable under the terms of an MIT-style license. + * For details, see the Prototype web site: http://www.prototypejs.org/ + * + *--------------------------------------------------------------------------*/ + +var Prototype = { + Version: '1.6.0.1', + + Browser: { + IE: !!(window.attachEvent && !window.opera), + Opera: !!window.opera, + WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1, + Gecko: navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1, + MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/) + }, + + BrowserFeatures: { + XPath: !!document.evaluate, + ElementExtensions: !!window.HTMLElement, + SpecificElementExtensions: + document.createElement('div').__proto__ && + document.createElement('div').__proto__ !== + document.createElement('form').__proto__ + }, + + ScriptFragment: ']*>([\\S\\s]*?)<\/script>', + JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/, + + emptyFunction: function() { }, + K: function(x) { return x } +}; + +if (Prototype.Browser.MobileSafari) + Prototype.BrowserFeatures.SpecificElementExtensions = false; + + +/* Based on Alex Arnell's inheritance implementation. */ +var Class = { + create: function() { + var parent = null, properties = $A(arguments); + if (Object.isFunction(properties[0])) + parent = properties.shift(); + + function klass() { + this.initialize.apply(this, arguments); + } + + Object.extend(klass, Class.Methods); + klass.superclass = parent; + klass.subclasses = []; + + if (parent) { + var subclass = function() { }; + subclass.prototype = parent.prototype; + klass.prototype = new subclass; + parent.subclasses.push(klass); + } + + for (var i = 0; i < properties.length; i++) + klass.addMethods(properties[i]); + + if (!klass.prototype.initialize) + klass.prototype.initialize = Prototype.emptyFunction; + + klass.prototype.constructor = klass; + + return klass; + } +}; + +Class.Methods = { + addMethods: function(source) { + var ancestor = this.superclass && this.superclass.prototype; + var properties = Object.keys(source); + + if (!Object.keys({ toString: true }).length) + properties.push("toString", "valueOf"); + + for (var i = 0, length = properties.length; i < length; i++) { + var property = properties[i], value = source[property]; + if (ancestor && Object.isFunction(value) && + value.argumentNames().first() == "$super") { + var method = value, value = Object.extend((function(m) { + return function() { return ancestor[m].apply(this, arguments) }; + })(property).wrap(method), { + valueOf: function() { return method }, + toString: function() { return method.toString() } + }); + } + this.prototype[property] = value; + } + + return this; + } +}; + +var Abstract = { }; + +Object.extend = function(destination, source) { + for (var property in source) + destination[property] = source[property]; + return destination; +}; + +Object.extend(Object, { + inspect: function(object) { + try { + if (Object.isUndefined(object)) return 'undefined'; + if (object === null) return 'null'; + return object.inspect ? object.inspect() : object.toString(); + } catch (e) { + if (e instanceof RangeError) return '...'; + throw e; + } + }, + + toJSON: function(object) { + var type = typeof object; + switch (type) { + case 'undefined': + case 'function': + case 'unknown': return; + case 'boolean': return object.toString(); + } + + if (object === null) return 'null'; + if (object.toJSON) return object.toJSON(); + if (Object.isElement(object)) return; + + var results = []; + for (var property in object) { + var value = Object.toJSON(object[property]); + if (!Object.isUndefined(value)) + results.push(property.toJSON() + ': ' + value); + } + + return '{' + results.join(', ') + '}'; + }, + + toQueryString: function(object) { + return $H(object).toQueryString(); + }, + + toHTML: function(object) { + return object && object.toHTML ? object.toHTML() : String.interpret(object); + }, + + keys: function(object) { + var keys = []; + for (var property in object) + keys.push(property); + return keys; + }, + + values: function(object) { + var values = []; + for (var property in object) + values.push(object[property]); + return values; + }, + + clone: function(object) { + return Object.extend({ }, object); + }, + + isElement: function(object) { + return object && object.nodeType == 1; + }, + + isArray: function(object) { + return object && object.constructor === Array; + }, + + isHash: function(object) { + return object instanceof Hash; + }, + + isFunction: function(object) { + return typeof object == "function"; + }, + + isString: function(object) { + return typeof object == "string"; + }, + + isNumber: function(object) { + return typeof object == "number"; + }, + + isUndefined: function(object) { + return typeof object == "undefined"; + } +}); + +Object.extend(Function.prototype, { + argumentNames: function() { + var names = this.toString().match(/^[\s\(]*function[^(]*\((.*?)\)/)[1].split(",").invoke("strip"); + return names.length == 1 && !names[0] ? [] : names; + }, + + bind: function() { + if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this; + var __method = this, args = $A(arguments), object = args.shift(); + return function() { + return __method.apply(object, args.concat($A(arguments))); + } + }, + + bindAsEventListener: function() { + var __method = this, args = $A(arguments), object = args.shift(); + return function(event) { + return __method.apply(object, [event || window.event].concat(args)); + } + }, + + curry: function() { + if (!arguments.length) return this; + var __method = this, args = $A(arguments); + return function() { + return __method.apply(this, args.concat($A(arguments))); + } + }, + + delay: function() { + var __method = this, args = $A(arguments), timeout = args.shift() * 1000; + return window.setTimeout(function() { + return __method.apply(__method, args); + }, timeout); + }, + + wrap: function(wrapper) { + var __method = this; + return function() { + return wrapper.apply(this, [__method.bind(this)].concat($A(arguments))); + } + }, + + methodize: function() { + if (this._methodized) return this._methodized; + var __method = this; + return this._methodized = function() { + return __method.apply(null, [this].concat($A(arguments))); + }; + } +}); + +Function.prototype.defer = Function.prototype.delay.curry(0.01); + +Date.prototype.toJSON = function() { + return '"' + this.getUTCFullYear() + '-' + + (this.getUTCMonth() + 1).toPaddedString(2) + '-' + + this.getUTCDate().toPaddedString(2) + 'T' + + this.getUTCHours().toPaddedString(2) + ':' + + this.getUTCMinutes().toPaddedString(2) + ':' + + this.getUTCSeconds().toPaddedString(2) + 'Z"'; +}; + +var Try = { + these: function() { + var returnValue; + + for (var i = 0, length = arguments.length; i < length; i++) { + var lambda = arguments[i]; + try { + returnValue = lambda(); + break; + } catch (e) { } + } + + return returnValue; + } +}; + +RegExp.prototype.match = RegExp.prototype.test; + +RegExp.escape = function(str) { + return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); +}; + +/*--------------------------------------------------------------------------*/ + +var PeriodicalExecuter = Class.create({ + initialize: function(callback, frequency) { + this.callback = callback; + this.frequency = frequency; + this.currentlyExecuting = false; + + this.registerCallback(); + }, + + registerCallback: function() { + this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + execute: function() { + this.callback(this); + }, + + stop: function() { + if (!this.timer) return; + clearInterval(this.timer); + this.timer = null; + }, + + onTimerEvent: function() { + if (!this.currentlyExecuting) { + try { + this.currentlyExecuting = true; + this.execute(); + } finally { + this.currentlyExecuting = false; + } + } + } +}); +Object.extend(String, { + interpret: function(value) { + return value == null ? '' : String(value); + }, + specialChar: { + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '\\': '\\\\' + } +}); + +Object.extend(String.prototype, { + gsub: function(pattern, replacement) { + var result = '', source = this, match; + replacement = arguments.callee.prepareReplacement(replacement); + + while (source.length > 0) { + if (match = source.match(pattern)) { + result += source.slice(0, match.index); + result += String.interpret(replacement(match)); + source = source.slice(match.index + match[0].length); + } else { + result += source, source = ''; + } + } + return result; + }, + + sub: function(pattern, replacement, count) { + replacement = this.gsub.prepareReplacement(replacement); + count = Object.isUndefined(count) ? 1 : count; + + return this.gsub(pattern, function(match) { + if (--count < 0) return match[0]; + return replacement(match); + }); + }, + + scan: function(pattern, iterator) { + this.gsub(pattern, iterator); + return String(this); + }, + + truncate: function(length, truncation) { + length = length || 30; + truncation = Object.isUndefined(truncation) ? '...' : truncation; + return this.length > length ? + this.slice(0, length - truncation.length) + truncation : String(this); + }, + + strip: function() { + return this.replace(/^\s+/, '').replace(/\s+$/, ''); + }, + + stripTags: function() { + return this.replace(/<\/?[^>]+>/gi, ''); + }, + + stripScripts: function() { + return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); + }, + + extractScripts: function() { + var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); + var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); + return (this.match(matchAll) || []).map(function(scriptTag) { + return (scriptTag.match(matchOne) || ['', ''])[1]; + }); + }, + + evalScripts: function() { + return this.extractScripts().map(function(script) { return eval(script) }); + }, + + escapeHTML: function() { + var self = arguments.callee; + self.text.data = this; + return self.div.innerHTML; + }, + + unescapeHTML: function() { + var div = new Element('div'); + div.innerHTML = this.stripTags(); + return div.childNodes[0] ? (div.childNodes.length > 1 ? + $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) : + div.childNodes[0].nodeValue) : ''; + }, + + toQueryParams: function(separator) { + var match = this.strip().match(/([^?#]*)(#.*)?$/); + if (!match) return { }; + + return match[1].split(separator || '&').inject({ }, function(hash, pair) { + if ((pair = pair.split('='))[0]) { + var key = decodeURIComponent(pair.shift()); + var value = pair.length > 1 ? pair.join('=') : pair[0]; + if (value != undefined) value = decodeURIComponent(value); + + if (key in hash) { + if (!Object.isArray(hash[key])) hash[key] = [hash[key]]; + hash[key].push(value); + } + else hash[key] = value; + } + return hash; + }); + }, + + toArray: function() { + return this.split(''); + }, + + succ: function() { + return this.slice(0, this.length - 1) + + String.fromCharCode(this.charCodeAt(this.length - 1) + 1); + }, + + times: function(count) { + return count < 1 ? '' : new Array(count + 1).join(this); + }, + + camelize: function() { + var parts = this.split('-'), len = parts.length; + if (len == 1) return parts[0]; + + var camelized = this.charAt(0) == '-' + ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1) + : parts[0]; + + for (var i = 1; i < len; i++) + camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1); + + return camelized; + }, + + capitalize: function() { + return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase(); + }, + + underscore: function() { + return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase(); + }, + + dasherize: function() { + return this.gsub(/_/,'-'); + }, + + inspect: function(useDoubleQuotes) { + var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) { + var character = String.specialChar[match[0]]; + return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16); + }); + if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"'; + return "'" + escapedString.replace(/'/g, '\\\'') + "'"; + }, + + toJSON: function() { + return this.inspect(true); + }, + + unfilterJSON: function(filter) { + return this.sub(filter || Prototype.JSONFilter, '#{1}'); + }, + + isJSON: function() { + var str = this; + if (str.blank()) return false; + str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''); + return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str); + }, + + evalJSON: function(sanitize) { + var json = this.unfilterJSON(); + try { + if (!sanitize || json.isJSON()) return eval('(' + json + ')'); + } catch (e) { } + throw new SyntaxError('Badly formed JSON string: ' + this.inspect()); + }, + + include: function(pattern) { + return this.indexOf(pattern) > -1; + }, + + startsWith: function(pattern) { + return this.indexOf(pattern) === 0; + }, + + endsWith: function(pattern) { + var d = this.length - pattern.length; + return d >= 0 && this.lastIndexOf(pattern) === d; + }, + + empty: function() { + return this == ''; + }, + + blank: function() { + return /^\s*$/.test(this); + }, + + interpolate: function(object, pattern) { + return new Template(this, pattern).evaluate(object); + } +}); + +if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, { + escapeHTML: function() { + return this.replace(/&/g,'&').replace(//g,'>'); + }, + unescapeHTML: function() { + return this.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); + } +}); + +String.prototype.gsub.prepareReplacement = function(replacement) { + if (Object.isFunction(replacement)) return replacement; + var template = new Template(replacement); + return function(match) { return template.evaluate(match) }; +}; + +String.prototype.parseQuery = String.prototype.toQueryParams; + +Object.extend(String.prototype.escapeHTML, { + div: document.createElement('div'), + text: document.createTextNode('') +}); + +with (String.prototype.escapeHTML) div.appendChild(text); + +var Template = Class.create({ + initialize: function(template, pattern) { + this.template = template.toString(); + this.pattern = pattern || Template.Pattern; + }, + + evaluate: function(object) { + if (Object.isFunction(object.toTemplateReplacements)) + object = object.toTemplateReplacements(); + + return this.template.gsub(this.pattern, function(match) { + if (object == null) return ''; + + var before = match[1] || ''; + if (before == '\\') return match[2]; + + var ctx = object, expr = match[3]; + var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/; + match = pattern.exec(expr); + if (match == null) return before; + + while (match != null) { + var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1]; + ctx = ctx[comp]; + if (null == ctx || '' == match[3]) break; + expr = expr.substring('[' == match[3] ? match[1].length : match[0].length); + match = pattern.exec(expr); + } + + return before + String.interpret(ctx); + }.bind(this)); + } +}); +Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; + +var $break = { }; + +var Enumerable = { + each: function(iterator, context) { + var index = 0; + iterator = iterator.bind(context); + try { + this._each(function(value) { + iterator(value, index++); + }); + } catch (e) { + if (e != $break) throw e; + } + return this; + }, + + eachSlice: function(number, iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; + var index = -number, slices = [], array = this.toArray(); + while ((index += number) < array.length) + slices.push(array.slice(index, index+number)); + return slices.collect(iterator, context); + }, + + all: function(iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; + var result = true; + this.each(function(value, index) { + result = result && !!iterator(value, index); + if (!result) throw $break; + }); + return result; + }, + + any: function(iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; + var result = false; + this.each(function(value, index) { + if (result = !!iterator(value, index)) + throw $break; + }); + return result; + }, + + collect: function(iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; + var results = []; + this.each(function(value, index) { + results.push(iterator(value, index)); + }); + return results; + }, + + detect: function(iterator, context) { + iterator = iterator.bind(context); + var result; + this.each(function(value, index) { + if (iterator(value, index)) { + result = value; + throw $break; + } + }); + return result; + }, + + findAll: function(iterator, context) { + iterator = iterator.bind(context); + var results = []; + this.each(function(value, index) { + if (iterator(value, index)) + results.push(value); + }); + return results; + }, + + grep: function(filter, iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; + var results = []; + + if (Object.isString(filter)) + filter = new RegExp(filter); + + this.each(function(value, index) { + if (filter.match(value)) + results.push(iterator(value, index)); + }); + return results; + }, + + include: function(object) { + if (Object.isFunction(this.indexOf)) + if (this.indexOf(object) != -1) return true; + + var found = false; + this.each(function(value) { + if (value == object) { + found = true; + throw $break; + } + }); + return found; + }, + + inGroupsOf: function(number, fillWith) { + fillWith = Object.isUndefined(fillWith) ? null : fillWith; + return this.eachSlice(number, function(slice) { + while(slice.length < number) slice.push(fillWith); + return slice; + }); + }, + + inject: function(memo, iterator, context) { + iterator = iterator.bind(context); + this.each(function(value, index) { + memo = iterator(memo, value, index); + }); + return memo; + }, + + invoke: function(method) { + var args = $A(arguments).slice(1); + return this.map(function(value) { + return value[method].apply(value, args); + }); + }, + + max: function(iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; + var result; + this.each(function(value, index) { + value = iterator(value, index); + if (result == null || value >= result) + result = value; + }); + return result; + }, + + min: function(iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; + var result; + this.each(function(value, index) { + value = iterator(value, index); + if (result == null || value < result) + result = value; + }); + return result; + }, + + partition: function(iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; + var trues = [], falses = []; + this.each(function(value, index) { + (iterator(value, index) ? + trues : falses).push(value); + }); + return [trues, falses]; + }, + + pluck: function(property) { + var results = []; + this.each(function(value) { + results.push(value[property]); + }); + return results; + }, + + reject: function(iterator, context) { + iterator = iterator.bind(context); + var results = []; + this.each(function(value, index) { + if (!iterator(value, index)) + results.push(value); + }); + return results; + }, + + sortBy: function(iterator, context) { + iterator = iterator.bind(context); + return this.map(function(value, index) { + return {value: value, criteria: iterator(value, index)}; + }).sort(function(left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + }).pluck('value'); + }, + + toArray: function() { + return this.map(); + }, + + zip: function() { + var iterator = Prototype.K, args = $A(arguments); + if (Object.isFunction(args.last())) + iterator = args.pop(); + + var collections = [this].concat(args).map($A); + return this.map(function(value, index) { + return iterator(collections.pluck(index)); + }); + }, + + size: function() { + return this.toArray().length; + }, + + inspect: function() { + return '#'; + } +}; + +Object.extend(Enumerable, { + map: Enumerable.collect, + find: Enumerable.detect, + select: Enumerable.findAll, + filter: Enumerable.findAll, + member: Enumerable.include, + entries: Enumerable.toArray, + every: Enumerable.all, + some: Enumerable.any +}); +function $A(iterable) { + if (!iterable) return []; + if (iterable.toArray) return iterable.toArray(); + var length = iterable.length, results = new Array(length); + while (length--) results[length] = iterable[length]; + return results; +} + +if (Prototype.Browser.WebKit) { + function $A(iterable) { + if (!iterable) return []; + if (!(Object.isFunction(iterable) && iterable == '[object NodeList]') && + iterable.toArray) return iterable.toArray(); + var length = iterable.length, results = new Array(length); + while (length--) results[length] = iterable[length]; + return results; + } +} + +Array.from = $A; + +Object.extend(Array.prototype, Enumerable); + +if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse; + +Object.extend(Array.prototype, { + _each: function(iterator) { + for (var i = 0, length = this.length; i < length; i++) + iterator(this[i]); + }, + + clear: function() { + this.length = 0; + return this; + }, + + first: function() { + return this[0]; + }, + + last: function() { + return this[this.length - 1]; + }, + + compact: function() { + return this.select(function(value) { + return value != null; + }); + }, + + flatten: function() { + return this.inject([], function(array, value) { + return array.concat(Object.isArray(value) ? + value.flatten() : [value]); + }); + }, + + without: function() { + var values = $A(arguments); + return this.select(function(value) { + return !values.include(value); + }); + }, + + reverse: function(inline) { + return (inline !== false ? this : this.toArray())._reverse(); + }, + + reduce: function() { + return this.length > 1 ? this : this[0]; + }, + + uniq: function(sorted) { + return this.inject([], function(array, value, index) { + if (0 == index || (sorted ? array.last() != value : !array.include(value))) + array.push(value); + return array; + }); + }, + + intersect: function(array) { + return this.uniq().findAll(function(item) { + return array.detect(function(value) { return item === value }); + }); + }, + + clone: function() { + return [].concat(this); + }, + + size: function() { + return this.length; + }, + + inspect: function() { + return '[' + this.map(Object.inspect).join(', ') + ']'; + }, + + toJSON: function() { + var results = []; + this.each(function(object) { + var value = Object.toJSON(object); + if (!Object.isUndefined(value)) results.push(value); + }); + return '[' + results.join(', ') + ']'; + } +}); + +// use native browser JS 1.6 implementation if available +if (Object.isFunction(Array.prototype.forEach)) + Array.prototype._each = Array.prototype.forEach; + +if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) { + i || (i = 0); + var length = this.length; + if (i < 0) i = length + i; + for (; i < length; i++) + if (this[i] === item) return i; + return -1; +}; + +if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) { + i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1; + var n = this.slice(0, i).reverse().indexOf(item); + return (n < 0) ? n : i - n - 1; +}; + +Array.prototype.toArray = Array.prototype.clone; + +function $w(string) { + if (!Object.isString(string)) return []; + string = string.strip(); + return string ? string.split(/\s+/) : []; +} + +if (Prototype.Browser.Opera){ + Array.prototype.concat = function() { + var array = []; + for (var i = 0, length = this.length; i < length; i++) array.push(this[i]); + for (var i = 0, length = arguments.length; i < length; i++) { + if (Object.isArray(arguments[i])) { + for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++) + array.push(arguments[i][j]); + } else { + array.push(arguments[i]); + } + } + return array; + }; +} +Object.extend(Number.prototype, { + toColorPart: function() { + return this.toPaddedString(2, 16); + }, + + succ: function() { + return this + 1; + }, + + times: function(iterator) { + $R(0, this, true).each(iterator); + return this; + }, + + toPaddedString: function(length, radix) { + var string = this.toString(radix || 10); + return '0'.times(length - string.length) + string; + }, + + toJSON: function() { + return isFinite(this) ? this.toString() : 'null'; + } +}); + +$w('abs round ceil floor').each(function(method){ + Number.prototype[method] = Math[method].methodize(); +}); +function $H(object) { + return new Hash(object); +}; + +var Hash = Class.create(Enumerable, (function() { + + function toQueryPair(key, value) { + if (Object.isUndefined(value)) return key; + return key + '=' + encodeURIComponent(String.interpret(value)); + } + + return { + initialize: function(object) { + this._object = Object.isHash(object) ? object.toObject() : Object.clone(object); + }, + + _each: function(iterator) { + for (var key in this._object) { + var value = this._object[key], pair = [key, value]; + pair.key = key; + pair.value = value; + iterator(pair); + } + }, + + set: function(key, value) { + return this._object[key] = value; + }, + + get: function(key) { + return this._object[key]; + }, + + unset: function(key) { + var value = this._object[key]; + delete this._object[key]; + return value; + }, + + toObject: function() { + return Object.clone(this._object); + }, + + keys: function() { + return this.pluck('key'); + }, + + values: function() { + return this.pluck('value'); + }, + + index: function(value) { + var match = this.detect(function(pair) { + return pair.value === value; + }); + return match && match.key; + }, + + merge: function(object) { + return this.clone().update(object); + }, + + update: function(object) { + return new Hash(object).inject(this, function(result, pair) { + result.set(pair.key, pair.value); + return result; + }); + }, + + toQueryString: function() { + return this.map(function(pair) { + var key = encodeURIComponent(pair.key), values = pair.value; + + if (values && typeof values == 'object') { + if (Object.isArray(values)) + return values.map(toQueryPair.curry(key)).join('&'); + } + return toQueryPair(key, values); + }).join('&'); + }, + + inspect: function() { + return '#'; + }, + + toJSON: function() { + return Object.toJSON(this.toObject()); + }, + + clone: function() { + return new Hash(this); + } + } +})()); + +Hash.prototype.toTemplateReplacements = Hash.prototype.toObject; +Hash.from = $H; +var ObjectRange = Class.create(Enumerable, { + initialize: function(start, end, exclusive) { + this.start = start; + this.end = end; + this.exclusive = exclusive; + }, + + _each: function(iterator) { + var value = this.start; + while (this.include(value)) { + iterator(value); + value = value.succ(); + } + }, + + include: function(value) { + if (value < this.start) + return false; + if (this.exclusive) + return value < this.end; + return value <= this.end; + } +}); + +var $R = function(start, end, exclusive) { + return new ObjectRange(start, end, exclusive); +}; + +var Ajax = { + getTransport: function() { + return Try.these( + function() {return new XMLHttpRequest()}, + function() {return new ActiveXObject('Msxml2.XMLHTTP')}, + function() {return new ActiveXObject('Microsoft.XMLHTTP')} + ) || false; + }, + + activeRequestCount: 0 +}; + +Ajax.Responders = { + responders: [], + + _each: function(iterator) { + this.responders._each(iterator); + }, + + register: function(responder) { + if (!this.include(responder)) + this.responders.push(responder); + }, + + unregister: function(responder) { + this.responders = this.responders.without(responder); + }, + + dispatch: function(callback, request, transport, json) { + this.each(function(responder) { + if (Object.isFunction(responder[callback])) { + try { + responder[callback].apply(responder, [request, transport, json]); + } catch (e) { } + } + }); + } +}; + +Object.extend(Ajax.Responders, Enumerable); + +Ajax.Responders.register({ + onCreate: function() { Ajax.activeRequestCount++ }, + onComplete: function() { Ajax.activeRequestCount-- } +}); + +Ajax.Base = Class.create({ + initialize: function(options) { + this.options = { + method: 'post', + asynchronous: true, + contentType: 'application/x-www-form-urlencoded', + encoding: 'UTF-8', + parameters: '', + evalJSON: true, + evalJS: true + }; + Object.extend(this.options, options || { }); + + this.options.method = this.options.method.toLowerCase(); + + if (Object.isString(this.options.parameters)) + this.options.parameters = this.options.parameters.toQueryParams(); + else if (Object.isHash(this.options.parameters)) + this.options.parameters = this.options.parameters.toObject(); + } +}); + +Ajax.Request = Class.create(Ajax.Base, { + _complete: false, + + initialize: function($super, url, options) { + $super(options); + this.transport = Ajax.getTransport(); + this.request(url); + }, + + request: function(url) { + this.url = url; + this.method = this.options.method; + var params = Object.clone(this.options.parameters); + + if (!['get', 'post'].include(this.method)) { + // simulate other verbs over post + params['_method'] = this.method; + this.method = 'post'; + } + + this.parameters = params; + + if (params = Object.toQueryString(params)) { + // when GET, append parameters to URL + if (this.method == 'get') + this.url += (this.url.include('?') ? '&' : '?') + params; + else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) + params += '&_='; + } + + try { + var response = new Ajax.Response(this); + if (this.options.onCreate) this.options.onCreate(response); + Ajax.Responders.dispatch('onCreate', this, response); + + this.transport.open(this.method.toUpperCase(), this.url, + this.options.asynchronous); + + if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1); + + this.transport.onreadystatechange = this.onStateChange.bind(this); + this.setRequestHeaders(); + + this.body = this.method == 'post' ? (this.options.postBody || params) : null; + this.transport.send(this.body); + + /* Force Firefox to handle ready state 4 for synchronous requests */ + if (!this.options.asynchronous && this.transport.overrideMimeType) + this.onStateChange(); + + } + catch (e) { + this.dispatchException(e); + } + }, + + onStateChange: function() { + var readyState = this.transport.readyState; + if (readyState > 1 && !((readyState == 4) && this._complete)) + this.respondToReadyState(this.transport.readyState); + }, + + setRequestHeaders: function() { + var headers = { + 'X-Requested-With': 'XMLHttpRequest', + 'X-Prototype-Version': Prototype.Version, + 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' + }; + + if (this.method == 'post') { + headers['Content-type'] = this.options.contentType + + (this.options.encoding ? '; charset=' + this.options.encoding : ''); + + /* Force "Connection: close" for older Mozilla browsers to work + * around a bug where XMLHttpRequest sends an incorrect + * Content-length header. See Mozilla Bugzilla #246651. + */ + if (this.transport.overrideMimeType && + (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) + headers['Connection'] = 'close'; + } + + // user-defined headers + if (typeof this.options.requestHeaders == 'object') { + var extras = this.options.requestHeaders; + + if (Object.isFunction(extras.push)) + for (var i = 0, length = extras.length; i < length; i += 2) + headers[extras[i]] = extras[i+1]; + else + $H(extras).each(function(pair) { headers[pair.key] = pair.value }); + } + + for (var name in headers) + this.transport.setRequestHeader(name, headers[name]); + }, + + success: function() { + var status = this.getStatus(); + return !status || (status >= 200 && status < 300); + }, + + getStatus: function() { + try { + return this.transport.status || 0; + } catch (e) { return 0 } + }, + + respondToReadyState: function(readyState) { + var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this); + + if (state == 'Complete') { + try { + this._complete = true; + (this.options['on' + response.status] + || this.options['on' + (this.success() ? 'Success' : 'Failure')] + || Prototype.emptyFunction)(response, response.headerJSON); + } catch (e) { + this.dispatchException(e); + } + + var contentType = response.getHeader('Content-type'); + if (this.options.evalJS == 'force' + || (this.options.evalJS && contentType + && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i))) + this.evalResponse(); + } + + try { + (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON); + Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON); + } catch (e) { + this.dispatchException(e); + } + + if (state == 'Complete') { + // avoid memory leak in MSIE: clean up + this.transport.onreadystatechange = Prototype.emptyFunction; + } + }, + + getHeader: function(name) { + try { + return this.transport.getResponseHeader(name); + } catch (e) { return null } + }, + + evalResponse: function() { + try { + return eval((this.transport.responseText || '').unfilterJSON()); + } catch (e) { + this.dispatchException(e); + } + }, + + dispatchException: function(exception) { + (this.options.onException || Prototype.emptyFunction)(this, exception); + Ajax.Responders.dispatch('onException', this, exception); + } +}); + +Ajax.Request.Events = + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; + +Ajax.Response = Class.create({ + initialize: function(request){ + this.request = request; + var transport = this.transport = request.transport, + readyState = this.readyState = transport.readyState; + + if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) { + this.status = this.getStatus(); + this.statusText = this.getStatusText(); + this.responseText = String.interpret(transport.responseText); + this.headerJSON = this._getHeaderJSON(); + } + + if(readyState == 4) { + var xml = transport.responseXML; + this.responseXML = Object.isUndefined(xml) ? null : xml; + this.responseJSON = this._getResponseJSON(); + } + }, + + status: 0, + statusText: '', + + getStatus: Ajax.Request.prototype.getStatus, + + getStatusText: function() { + try { + return this.transport.statusText || ''; + } catch (e) { return '' } + }, + + getHeader: Ajax.Request.prototype.getHeader, + + getAllHeaders: function() { + try { + return this.getAllResponseHeaders(); + } catch (e) { return null } + }, + + getResponseHeader: function(name) { + return this.transport.getResponseHeader(name); + }, + + getAllResponseHeaders: function() { + return this.transport.getAllResponseHeaders(); + }, + + _getHeaderJSON: function() { + var json = this.getHeader('X-JSON'); + if (!json) return null; + json = decodeURIComponent(escape(json)); + try { + return json.evalJSON(this.request.options.sanitizeJSON); + } catch (e) { + this.request.dispatchException(e); + } + }, + + _getResponseJSON: function() { + var options = this.request.options; + if (!options.evalJSON || (options.evalJSON != 'force' && + !(this.getHeader('Content-type') || '').include('application/json')) || + this.responseText.blank()) + return null; + try { + return this.responseText.evalJSON(options.sanitizeJSON); + } catch (e) { + this.request.dispatchException(e); + } + } +}); + +Ajax.Updater = Class.create(Ajax.Request, { + initialize: function($super, container, url, options) { + this.container = { + success: (container.success || container), + failure: (container.failure || (container.success ? null : container)) + }; + + options = Object.clone(options); + var onComplete = options.onComplete; + options.onComplete = (function(response, json) { + this.updateContent(response.responseText); + if (Object.isFunction(onComplete)) onComplete(response, json); + }).bind(this); + + $super(url, options); + }, + + updateContent: function(responseText) { + var receiver = this.container[this.success() ? 'success' : 'failure'], + options = this.options; + + if (!options.evalScripts) responseText = responseText.stripScripts(); + + if (receiver = $(receiver)) { + if (options.insertion) { + if (Object.isString(options.insertion)) { + var insertion = { }; insertion[options.insertion] = responseText; + receiver.insert(insertion); + } + else options.insertion(receiver, responseText); + } + else receiver.update(responseText); + } + } +}); + +Ajax.PeriodicalUpdater = Class.create(Ajax.Base, { + initialize: function($super, container, url, options) { + $super(options); + this.onComplete = this.options.onComplete; + + this.frequency = (this.options.frequency || 2); + this.decay = (this.options.decay || 1); + + this.updater = { }; + this.container = container; + this.url = url; + + this.start(); + }, + + start: function() { + this.options.onComplete = this.updateComplete.bind(this); + this.onTimerEvent(); + }, + + stop: function() { + this.updater.options.onComplete = undefined; + clearTimeout(this.timer); + (this.onComplete || Prototype.emptyFunction).apply(this, arguments); + }, + + updateComplete: function(response) { + if (this.options.decay) { + this.decay = (response.responseText == this.lastText ? + this.decay * this.options.decay : 1); + + this.lastText = response.responseText; + } + this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency); + }, + + onTimerEvent: function() { + this.updater = new Ajax.Updater(this.container, this.url, this.options); + } +}); +function $(element) { + if (arguments.length > 1) { + for (var i = 0, elements = [], length = arguments.length; i < length; i++) + elements.push($(arguments[i])); + return elements; + } + if (Object.isString(element)) + element = document.getElementById(element); + return Element.extend(element); +} + +if (Prototype.BrowserFeatures.XPath) { + document._getElementsByXPath = function(expression, parentElement) { + var results = []; + var query = document.evaluate(expression, $(parentElement) || document, + null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + for (var i = 0, length = query.snapshotLength; i < length; i++) + results.push(Element.extend(query.snapshotItem(i))); + return results; + }; +} + +/*--------------------------------------------------------------------------*/ + +if (!window.Node) var Node = { }; + +if (!Node.ELEMENT_NODE) { + // DOM level 2 ECMAScript Language Binding + Object.extend(Node, { + ELEMENT_NODE: 1, + ATTRIBUTE_NODE: 2, + TEXT_NODE: 3, + CDATA_SECTION_NODE: 4, + ENTITY_REFERENCE_NODE: 5, + ENTITY_NODE: 6, + PROCESSING_INSTRUCTION_NODE: 7, + COMMENT_NODE: 8, + DOCUMENT_NODE: 9, + DOCUMENT_TYPE_NODE: 10, + DOCUMENT_FRAGMENT_NODE: 11, + NOTATION_NODE: 12 + }); +} + +(function() { + var element = this.Element; + this.Element = function(tagName, attributes) { + attributes = attributes || { }; + tagName = tagName.toLowerCase(); + var cache = Element.cache; + if (Prototype.Browser.IE && attributes.name) { + tagName = '<' + tagName + ' name="' + attributes.name + '">'; + delete attributes.name; + return Element.writeAttribute(document.createElement(tagName), attributes); + } + if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName)); + return Element.writeAttribute(cache[tagName].cloneNode(false), attributes); + }; + Object.extend(this.Element, element || { }); +}).call(window); + +Element.cache = { }; + +Element.Methods = { + visible: function(element) { + return $(element).style.display != 'none'; + }, + + toggle: function(element) { + element = $(element); + Element[Element.visible(element) ? 'hide' : 'show'](element); + return element; + }, + + hide: function(element) { + $(element).style.display = 'none'; + return element; + }, + + show: function(element) { + $(element).style.display = ''; + return element; + }, + + remove: function(element) { + element = $(element); + element.parentNode.removeChild(element); + return element; + }, + + update: function(element, content) { + element = $(element); + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) return element.update().insert(content); + content = Object.toHTML(content); + element.innerHTML = content.stripScripts(); + content.evalScripts.bind(content).defer(); + return element; + }, + + replace: function(element, content) { + element = $(element); + if (content && content.toElement) content = content.toElement(); + else if (!Object.isElement(content)) { + content = Object.toHTML(content); + var range = element.ownerDocument.createRange(); + range.selectNode(element); + content.evalScripts.bind(content).defer(); + content = range.createContextualFragment(content.stripScripts()); + } + element.parentNode.replaceChild(content, element); + return element; + }, + + insert: function(element, insertions) { + element = $(element); + + if (Object.isString(insertions) || Object.isNumber(insertions) || + Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML))) + insertions = {bottom:insertions}; + + var content, t, range; + + for (position in insertions) { + content = insertions[position]; + position = position.toLowerCase(); + t = Element._insertionTranslations[position]; + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) { + t.insert(element, content); + continue; + } + + content = Object.toHTML(content); + + range = element.ownerDocument.createRange(); + t.initializeRange(element, range); + t.insert(element, range.createContextualFragment(content.stripScripts())); + + content.evalScripts.bind(content).defer(); + } + + return element; + }, + + wrap: function(element, wrapper, attributes) { + element = $(element); + if (Object.isElement(wrapper)) + $(wrapper).writeAttribute(attributes || { }); + else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes); + else wrapper = new Element('div', wrapper); + if (element.parentNode) + element.parentNode.replaceChild(wrapper, element); + wrapper.appendChild(element); + return wrapper; + }, + + inspect: function(element) { + element = $(element); + var result = '<' + element.tagName.toLowerCase(); + $H({'id': 'id', 'className': 'class'}).each(function(pair) { + var property = pair.first(), attribute = pair.last(); + var value = (element[property] || '').toString(); + if (value) result += ' ' + attribute + '=' + value.inspect(true); + }); + return result + '>'; + }, + + recursivelyCollect: function(element, property) { + element = $(element); + var elements = []; + while (element = element[property]) + if (element.nodeType == 1) + elements.push(Element.extend(element)); + return elements; + }, + + ancestors: function(element) { + return $(element).recursivelyCollect('parentNode'); + }, + + descendants: function(element) { + return $(element).getElementsBySelector("*"); + }, + + firstDescendant: function(element) { + element = $(element).firstChild; + while (element && element.nodeType != 1) element = element.nextSibling; + return $(element); + }, + + immediateDescendants: function(element) { + if (!(element = $(element).firstChild)) return []; + while (element && element.nodeType != 1) element = element.nextSibling; + if (element) return [element].concat($(element).nextSiblings()); + return []; + }, + + previousSiblings: function(element) { + return $(element).recursivelyCollect('previousSibling'); + }, + + nextSiblings: function(element) { + return $(element).recursivelyCollect('nextSibling'); + }, + + siblings: function(element) { + element = $(element); + return element.previousSiblings().reverse().concat(element.nextSiblings()); + }, + + match: function(element, selector) { + if (Object.isString(selector)) + selector = new Selector(selector); + return selector.match($(element)); + }, + + up: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return $(element.parentNode); + var ancestors = element.ancestors(); + return expression ? Selector.findElement(ancestors, expression, index) : + ancestors[index || 0]; + }, + + down: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return element.firstDescendant(); + var descendants = element.descendants(); + return expression ? Selector.findElement(descendants, expression, index) : + descendants[index || 0]; + }, + + previous: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element)); + var previousSiblings = element.previousSiblings(); + return expression ? Selector.findElement(previousSiblings, expression, index) : + previousSiblings[index || 0]; + }, + + next: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element)); + var nextSiblings = element.nextSiblings(); + return expression ? Selector.findElement(nextSiblings, expression, index) : + nextSiblings[index || 0]; + }, + + select: function() { + var args = $A(arguments), element = $(args.shift()); + return Selector.findChildElements(element, args); + }, + + adjacent: function() { + var args = $A(arguments), element = $(args.shift()); + return Selector.findChildElements(element.parentNode, args).without(element); + }, + + identify: function(element) { + element = $(element); + var id = element.readAttribute('id'), self = arguments.callee; + if (id) return id; + do { id = 'anonymous_element_' + self.counter++ } while ($(id)); + element.writeAttribute('id', id); + return id; + }, + + readAttribute: function(element, name) { + element = $(element); + if (Prototype.Browser.IE) { + var t = Element._attributeTranslations.read; + if (t.values[name]) return t.values[name](element, name); + if (t.names[name]) name = t.names[name]; + if (name.include(':')) { + return (!element.attributes || !element.attributes[name]) ? null : + element.attributes[name].value; + } + } + return element.getAttribute(name); + }, + + writeAttribute: function(element, name, value) { + element = $(element); + var attributes = { }, t = Element._attributeTranslations.write; + + if (typeof name == 'object') attributes = name; + else attributes[name] = Object.isUndefined(value) ? true : value; + + for (var attr in attributes) { + name = t.names[attr] || attr; + value = attributes[attr]; + if (t.values[attr]) name = t.values[attr](element, value); + if (value === false || value === null) + element.removeAttribute(name); + else if (value === true) + element.setAttribute(name, name); + else element.setAttribute(name, value); + } + return element; + }, + + getHeight: function(element) { + return $(element).getDimensions().height; + }, + + getWidth: function(element) { + return $(element).getDimensions().width; + }, + + classNames: function(element) { + return new Element.ClassNames(element); + }, + + hasClassName: function(element, className) { + if (!(element = $(element))) return; + var elementClassName = element.className; + return (elementClassName.length > 0 && (elementClassName == className || + new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName))); + }, + + addClassName: function(element, className) { + if (!(element = $(element))) return; + if (!element.hasClassName(className)) + element.className += (element.className ? ' ' : '') + className; + return element; + }, + + removeClassName: function(element, className) { + if (!(element = $(element))) return; + element.className = element.className.replace( + new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip(); + return element; + }, + + toggleClassName: function(element, className) { + if (!(element = $(element))) return; + return element[element.hasClassName(className) ? + 'removeClassName' : 'addClassName'](className); + }, + + // removes whitespace-only text node children + cleanWhitespace: function(element) { + element = $(element); + var node = element.firstChild; + while (node) { + var nextNode = node.nextSibling; + if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) + element.removeChild(node); + node = nextNode; + } + return element; + }, + + empty: function(element) { + return $(element).innerHTML.blank(); + }, + + descendantOf: function(element, ancestor) { + element = $(element), ancestor = $(ancestor); + var originalAncestor = ancestor; + + if (element.compareDocumentPosition) + return (element.compareDocumentPosition(ancestor) & 8) === 8; + + if (element.sourceIndex && !Prototype.Browser.Opera) { + var e = element.sourceIndex, a = ancestor.sourceIndex, + nextAncestor = ancestor.nextSibling; + if (!nextAncestor) { + do { ancestor = ancestor.parentNode; } + while (!(nextAncestor = ancestor.nextSibling) && ancestor.parentNode); + } + if (nextAncestor) return (e > a && e < nextAncestor.sourceIndex); + } + + while (element = element.parentNode) + if (element == originalAncestor) return true; + return false; + }, + + scrollTo: function(element) { + element = $(element); + var pos = element.cumulativeOffset(); + window.scrollTo(pos[0], pos[1]); + return element; + }, + + getStyle: function(element, style) { + element = $(element); + style = style == 'float' ? 'cssFloat' : style.camelize(); + var value = element.style[style]; + if (!value) { + var css = document.defaultView.getComputedStyle(element, null); + value = css ? css[style] : null; + } + if (style == 'opacity') return value ? parseFloat(value) : 1.0; + return value == 'auto' ? null : value; + }, + + getOpacity: function(element) { + return $(element).getStyle('opacity'); + }, + + setStyle: function(element, styles) { + element = $(element); + var elementStyle = element.style, match; + if (Object.isString(styles)) { + element.style.cssText += ';' + styles; + return styles.include('opacity') ? + element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element; + } + for (var property in styles) + if (property == 'opacity') element.setOpacity(styles[property]); + else + elementStyle[(property == 'float' || property == 'cssFloat') ? + (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') : + property] = styles[property]; + + return element; + }, + + setOpacity: function(element, value) { + element = $(element); + element.style.opacity = (value == 1 || value === '') ? '' : + (value < 0.00001) ? 0 : value; + return element; + }, + + getDimensions: function(element) { + element = $(element); + var display = $(element).getStyle('display'); + if (display != 'none' && display != null) // Safari bug + return {width: element.offsetWidth, height: element.offsetHeight}; + + // All *Width and *Height properties give 0 on elements with display none, + // so enable the element temporarily + var els = element.style; + var originalVisibility = els.visibility; + var originalPosition = els.position; + var originalDisplay = els.display; + els.visibility = 'hidden'; + els.position = 'absolute'; + els.display = 'block'; + var originalWidth = element.clientWidth; + var originalHeight = element.clientHeight; + els.display = originalDisplay; + els.position = originalPosition; + els.visibility = originalVisibility; + return {width: originalWidth, height: originalHeight}; + }, + + makePositioned: function(element) { + element = $(element); + var pos = Element.getStyle(element, 'position'); + if (pos == 'static' || !pos) { + element._madePositioned = true; + element.style.position = 'relative'; + // Opera returns the offset relative to the positioning context, when an + // element is position relative but top and left have not been defined + if (window.opera) { + element.style.top = 0; + element.style.left = 0; + } + } + return element; + }, + + undoPositioned: function(element) { + element = $(element); + if (element._madePositioned) { + element._madePositioned = undefined; + element.style.position = + element.style.top = + element.style.left = + element.style.bottom = + element.style.right = ''; + } + return element; + }, + + makeClipping: function(element) { + element = $(element); + if (element._overflow) return element; + element._overflow = Element.getStyle(element, 'overflow') || 'auto'; + if (element._overflow !== 'hidden') + element.style.overflow = 'hidden'; + return element; + }, + + undoClipping: function(element) { + element = $(element); + if (!element._overflow) return element; + element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; + element._overflow = null; + return element; + }, + + cumulativeOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + positionedOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + if (element.tagName == 'BODY') break; + var p = Element.getStyle(element, 'position'); + if (p == 'relative' || p == 'absolute') break; + } + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + absolutize: function(element) { + element = $(element); + if (element.getStyle('position') == 'absolute') return; + // Position.prepare(); // To be done manually by Scripty when it needs it. + + var offsets = element.positionedOffset(); + var top = offsets[1]; + var left = offsets[0]; + var width = element.clientWidth; + var height = element.clientHeight; + + element._originalLeft = left - parseFloat(element.style.left || 0); + element._originalTop = top - parseFloat(element.style.top || 0); + element._originalWidth = element.style.width; + element._originalHeight = element.style.height; + + element.style.position = 'absolute'; + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.width = width + 'px'; + element.style.height = height + 'px'; + return element; + }, + + relativize: function(element) { + element = $(element); + if (element.getStyle('position') == 'relative') return; + // Position.prepare(); // To be done manually by Scripty when it needs it. + + element.style.position = 'relative'; + var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); + var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); + + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.height = element._originalHeight; + element.style.width = element._originalWidth; + return element; + }, + + cumulativeScrollOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + getOffsetParent: function(element) { + if (element.offsetParent) return $(element.offsetParent); + if (element == document.body) return $(element); + + while ((element = element.parentNode) && element != document.body) + if (Element.getStyle(element, 'position') != 'static') + return $(element); + + return $(document.body); + }, + + viewportOffset: function(forElement) { + var valueT = 0, valueL = 0; + + var element = forElement; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + + // Safari fix + if (element.offsetParent == document.body && + Element.getStyle(element, 'position') == 'absolute') break; + + } while (element = element.offsetParent); + + element = forElement; + do { + if (!Prototype.Browser.Opera || element.tagName == 'BODY') { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; + } + } while (element = element.parentNode); + + return Element._returnOffset(valueL, valueT); + }, + + clonePosition: function(element, source) { + var options = Object.extend({ + setLeft: true, + setTop: true, + setWidth: true, + setHeight: true, + offsetTop: 0, + offsetLeft: 0 + }, arguments[2] || { }); + + // find page position of source + source = $(source); + var p = source.viewportOffset(); + + // find coordinate system to use + element = $(element); + var delta = [0, 0]; + var parent = null; + // delta [0,0] will do fine with position: fixed elements, + // position:absolute needs offsetParent deltas + if (Element.getStyle(element, 'position') == 'absolute') { + parent = element.getOffsetParent(); + delta = parent.viewportOffset(); + } + + // correct by body offsets (fixes Safari) + if (parent == document.body) { + delta[0] -= document.body.offsetLeft; + delta[1] -= document.body.offsetTop; + } + + // set position + if (options.setLeft) element.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; + if (options.setTop) element.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; + if (options.setWidth) element.style.width = source.offsetWidth + 'px'; + if (options.setHeight) element.style.height = source.offsetHeight + 'px'; + return element; + } +}; + +Element.Methods.identify.counter = 1; + +Object.extend(Element.Methods, { + getElementsBySelector: Element.Methods.select, + childElements: Element.Methods.immediateDescendants +}); + +Element._attributeTranslations = { + write: { + names: { + className: 'class', + htmlFor: 'for' + }, + values: { } + } +}; + + +if (!document.createRange || Prototype.Browser.Opera) { + Element.Methods.insert = function(element, insertions) { + element = $(element); + + if (Object.isString(insertions) || Object.isNumber(insertions) || + Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML))) + insertions = { bottom: insertions }; + + var t = Element._insertionTranslations, content, position, pos, tagName; + + for (position in insertions) { + content = insertions[position]; + position = position.toLowerCase(); + pos = t[position]; + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) { + pos.insert(element, content); + continue; + } + + content = Object.toHTML(content); + tagName = ((position == 'before' || position == 'after') + ? element.parentNode : element).tagName.toUpperCase(); + + if (t.tags[tagName]) { + var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); + if (position == 'top' || position == 'after') fragments.reverse(); + fragments.each(pos.insert.curry(element)); + } + else element.insertAdjacentHTML(pos.adjacency, content.stripScripts()); + + content.evalScripts.bind(content).defer(); + } + + return element; + }; +} + +if (Prototype.Browser.Opera) { + Element.Methods.getStyle = Element.Methods.getStyle.wrap( + function(proceed, element, style) { + switch (style) { + case 'left': case 'top': case 'right': case 'bottom': + if (proceed(element, 'position') === 'static') return null; + case 'height': case 'width': + // returns '0px' for hidden elements; we want it to return null + if (!Element.visible(element)) return null; + + // returns the border-box dimensions rather than the content-box + // dimensions, so we subtract padding and borders from the value + var dim = parseInt(proceed(element, style), 10); + + if (dim !== element['offset' + style.capitalize()]) + return dim + 'px'; + + var properties; + if (style === 'height') { + properties = ['border-top-width', 'padding-top', + 'padding-bottom', 'border-bottom-width']; + } + else { + properties = ['border-left-width', 'padding-left', + 'padding-right', 'border-right-width']; + } + return properties.inject(dim, function(memo, property) { + var val = proceed(element, property); + return val === null ? memo : memo - parseInt(val, 10); + }) + 'px'; + default: return proceed(element, style); + } + } + ); + + Element.Methods.readAttribute = Element.Methods.readAttribute.wrap( + function(proceed, element, attribute) { + if (attribute === 'title') return element.title; + return proceed(element, attribute); + } + ); +} + +else if (Prototype.Browser.IE) { + $w('positionedOffset getOffsetParent viewportOffset').each(function(method) { + Element.Methods[method] = Element.Methods[method].wrap( + function(proceed, element) { + element = $(element); + var position = element.getStyle('position'); + if (position != 'static') return proceed(element); + element.setStyle({ position: 'relative' }); + var value = proceed(element); + element.setStyle({ position: position }); + return value; + } + ); + }); + + Element.Methods.getStyle = function(element, style) { + element = $(element); + style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize(); + var value = element.style[style]; + if (!value && element.currentStyle) value = element.currentStyle[style]; + + if (style == 'opacity') { + if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) + if (value[1]) return parseFloat(value[1]) / 100; + return 1.0; + } + + if (value == 'auto') { + if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none')) + return element['offset' + style.capitalize()] + 'px'; + return null; + } + return value; + }; + + Element.Methods.setOpacity = function(element, value) { + function stripAlpha(filter){ + return filter.replace(/alpha\([^\)]*\)/gi,''); + } + element = $(element); + var currentStyle = element.currentStyle; + if ((currentStyle && !currentStyle.hasLayout) || + (!currentStyle && element.style.zoom == 'normal')) + element.style.zoom = 1; + + var filter = element.getStyle('filter'), style = element.style; + if (value == 1 || value === '') { + (filter = stripAlpha(filter)) ? + style.filter = filter : style.removeAttribute('filter'); + return element; + } else if (value < 0.00001) value = 0; + style.filter = stripAlpha(filter) + + 'alpha(opacity=' + (value * 100) + ')'; + return element; + }; + + Element._attributeTranslations = { + read: { + names: { + 'class': 'className', + 'for': 'htmlFor' + }, + values: { + _getAttr: function(element, attribute) { + return element.getAttribute(attribute, 2); + }, + _getAttrNode: function(element, attribute) { + var node = element.getAttributeNode(attribute); + return node ? node.value : ""; + }, + _getEv: function(element, attribute) { + attribute = element.getAttribute(attribute); + return attribute ? attribute.toString().slice(23, -2) : null; + }, + _flag: function(element, attribute) { + return $(element).hasAttribute(attribute) ? attribute : null; + }, + style: function(element) { + return element.style.cssText.toLowerCase(); + }, + title: function(element) { + return element.title; + } + } + } + }; + + Element._attributeTranslations.write = { + names: Object.clone(Element._attributeTranslations.read.names), + values: { + checked: function(element, value) { + element.checked = !!value; + }, + + style: function(element, value) { + element.style.cssText = value ? value : ''; + } + } + }; + + Element._attributeTranslations.has = {}; + + $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' + + 'encType maxLength readOnly longDesc').each(function(attr) { + Element._attributeTranslations.write.names[attr.toLowerCase()] = attr; + Element._attributeTranslations.has[attr.toLowerCase()] = attr; + }); + + (function(v) { + Object.extend(v, { + href: v._getAttr, + src: v._getAttr, + type: v._getAttr, + action: v._getAttrNode, + disabled: v._flag, + checked: v._flag, + readonly: v._flag, + multiple: v._flag, + onload: v._getEv, + onunload: v._getEv, + onclick: v._getEv, + ondblclick: v._getEv, + onmousedown: v._getEv, + onmouseup: v._getEv, + onmouseover: v._getEv, + onmousemove: v._getEv, + onmouseout: v._getEv, + onfocus: v._getEv, + onblur: v._getEv, + onkeypress: v._getEv, + onkeydown: v._getEv, + onkeyup: v._getEv, + onsubmit: v._getEv, + onreset: v._getEv, + onselect: v._getEv, + onchange: v._getEv + }); + })(Element._attributeTranslations.read.values); +} + +else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) { + Element.Methods.setOpacity = function(element, value) { + element = $(element); + element.style.opacity = (value == 1) ? 0.999999 : + (value === '') ? '' : (value < 0.00001) ? 0 : value; + return element; + }; +} + +else if (Prototype.Browser.WebKit) { + Element.Methods.setOpacity = function(element, value) { + element = $(element); + element.style.opacity = (value == 1 || value === '') ? '' : + (value < 0.00001) ? 0 : value; + + if (value == 1) + if(element.tagName == 'IMG' && element.width) { + element.width++; element.width--; + } else try { + var n = document.createTextNode(' '); + element.appendChild(n); + element.removeChild(n); + } catch (e) { } + + return element; + }; + + // Safari returns margins on body which is incorrect if the child is absolutely + // positioned. For performance reasons, redefine Element#cumulativeOffset for + // KHTML/WebKit only. + Element.Methods.cumulativeOffset = function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == document.body) + if (Element.getStyle(element, 'position') == 'absolute') break; + + element = element.offsetParent; + } while (element); + + return Element._returnOffset(valueL, valueT); + }; +} + +if (Prototype.Browser.IE || Prototype.Browser.Opera) { + // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements + Element.Methods.update = function(element, content) { + element = $(element); + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) return element.update().insert(content); + + content = Object.toHTML(content); + var tagName = element.tagName.toUpperCase(); + + if (tagName in Element._insertionTranslations.tags) { + $A(element.childNodes).each(function(node) { element.removeChild(node) }); + Element._getContentFromAnonymousElement(tagName, content.stripScripts()) + .each(function(node) { element.appendChild(node) }); + } + else element.innerHTML = content.stripScripts(); + + content.evalScripts.bind(content).defer(); + return element; + }; +} + +if (document.createElement('div').outerHTML) { + Element.Methods.replace = function(element, content) { + element = $(element); + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) { + element.parentNode.replaceChild(content, element); + return element; + } + + content = Object.toHTML(content); + var parent = element.parentNode, tagName = parent.tagName.toUpperCase(); + + if (Element._insertionTranslations.tags[tagName]) { + var nextSibling = element.next(); + var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); + parent.removeChild(element); + if (nextSibling) + fragments.each(function(node) { parent.insertBefore(node, nextSibling) }); + else + fragments.each(function(node) { parent.appendChild(node) }); + } + else element.outerHTML = content.stripScripts(); + + content.evalScripts.bind(content).defer(); + return element; + }; +} + +Element._returnOffset = function(l, t) { + var result = [l, t]; + result.left = l; + result.top = t; + return result; +}; + +Element._getContentFromAnonymousElement = function(tagName, html) { + var div = new Element('div'), t = Element._insertionTranslations.tags[tagName]; + div.innerHTML = t[0] + html + t[1]; + t[2].times(function() { div = div.firstChild }); + return $A(div.childNodes); +}; + +Element._insertionTranslations = { + before: { + adjacency: 'beforeBegin', + insert: function(element, node) { + element.parentNode.insertBefore(node, element); + }, + initializeRange: function(element, range) { + range.setStartBefore(element); + } + }, + top: { + adjacency: 'afterBegin', + insert: function(element, node) { + element.insertBefore(node, element.firstChild); + }, + initializeRange: function(element, range) { + range.selectNodeContents(element); + range.collapse(true); + } + }, + bottom: { + adjacency: 'beforeEnd', + insert: function(element, node) { + element.appendChild(node); + } + }, + after: { + adjacency: 'afterEnd', + insert: function(element, node) { + element.parentNode.insertBefore(node, element.nextSibling); + }, + initializeRange: function(element, range) { + range.setStartAfter(element); + } + }, + tags: { + TABLE: ['', '
    ', 1], + TBODY: ['', '
    ', 2], + TR: ['', '
    ', 3], + TD: ['
    ', '
    ', 4], + SELECT: ['', 1] + } +}; + +(function() { + this.bottom.initializeRange = this.top.initializeRange; + Object.extend(this.tags, { + THEAD: this.tags.TBODY, + TFOOT: this.tags.TBODY, + TH: this.tags.TD + }); +}).call(Element._insertionTranslations); + +Element.Methods.Simulated = { + hasAttribute: function(element, attribute) { + attribute = Element._attributeTranslations.has[attribute] || attribute; + var node = $(element).getAttributeNode(attribute); + return node && node.specified; + } +}; + +Element.Methods.ByTag = { }; + +Object.extend(Element, Element.Methods); + +if (!Prototype.BrowserFeatures.ElementExtensions && + document.createElement('div').__proto__) { + window.HTMLElement = { }; + window.HTMLElement.prototype = document.createElement('div').__proto__; + Prototype.BrowserFeatures.ElementExtensions = true; +} + +Element.extend = (function() { + if (Prototype.BrowserFeatures.SpecificElementExtensions) + return Prototype.K; + + var Methods = { }, ByTag = Element.Methods.ByTag; + + var extend = Object.extend(function(element) { + if (!element || element._extendedByPrototype || + element.nodeType != 1 || element == window) return element; + + var methods = Object.clone(Methods), + tagName = element.tagName, property, value; + + // extend methods for specific tags + if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]); + + for (property in methods) { + value = methods[property]; + if (Object.isFunction(value) && !(property in element)) + element[property] = value.methodize(); + } + + element._extendedByPrototype = Prototype.emptyFunction; + return element; + + }, { + refresh: function() { + // extend methods for all tags (Safari doesn't need this) + if (!Prototype.BrowserFeatures.ElementExtensions) { + Object.extend(Methods, Element.Methods); + Object.extend(Methods, Element.Methods.Simulated); + } + } + }); + + extend.refresh(); + return extend; +})(); + +Element.hasAttribute = function(element, attribute) { + if (element.hasAttribute) return element.hasAttribute(attribute); + return Element.Methods.Simulated.hasAttribute(element, attribute); +}; + +Element.addMethods = function(methods) { + var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag; + + if (!methods) { + Object.extend(Form, Form.Methods); + Object.extend(Form.Element, Form.Element.Methods); + Object.extend(Element.Methods.ByTag, { + "FORM": Object.clone(Form.Methods), + "INPUT": Object.clone(Form.Element.Methods), + "SELECT": Object.clone(Form.Element.Methods), + "TEXTAREA": Object.clone(Form.Element.Methods) + }); + } + + if (arguments.length == 2) { + var tagName = methods; + methods = arguments[1]; + } + + if (!tagName) Object.extend(Element.Methods, methods || { }); + else { + if (Object.isArray(tagName)) tagName.each(extend); + else extend(tagName); + } + + function extend(tagName) { + tagName = tagName.toUpperCase(); + if (!Element.Methods.ByTag[tagName]) + Element.Methods.ByTag[tagName] = { }; + Object.extend(Element.Methods.ByTag[tagName], methods); + } + + function copy(methods, destination, onlyIfAbsent) { + onlyIfAbsent = onlyIfAbsent || false; + for (var property in methods) { + var value = methods[property]; + if (!Object.isFunction(value)) continue; + if (!onlyIfAbsent || !(property in destination)) + destination[property] = value.methodize(); + } + } + + function findDOMClass(tagName) { + var klass; + var trans = { + "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph", + "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList", + "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading", + "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote", + "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION": + "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD": + "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR": + "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET": + "FrameSet", "IFRAME": "IFrame" + }; + if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element'; + if (window[klass]) return window[klass]; + klass = 'HTML' + tagName + 'Element'; + if (window[klass]) return window[klass]; + klass = 'HTML' + tagName.capitalize() + 'Element'; + if (window[klass]) return window[klass]; + + window[klass] = { }; + window[klass].prototype = document.createElement(tagName).__proto__; + return window[klass]; + } + + if (F.ElementExtensions) { + copy(Element.Methods, HTMLElement.prototype); + copy(Element.Methods.Simulated, HTMLElement.prototype, true); + } + + if (F.SpecificElementExtensions) { + for (var tag in Element.Methods.ByTag) { + var klass = findDOMClass(tag); + if (Object.isUndefined(klass)) continue; + copy(T[tag], klass.prototype); + } + } + + Object.extend(Element, Element.Methods); + delete Element.ByTag; + + if (Element.extend.refresh) Element.extend.refresh(); + Element.cache = { }; +}; + +document.viewport = { + getDimensions: function() { + var dimensions = { }; + var B = Prototype.Browser; + $w('width height').each(function(d) { + var D = d.capitalize(); + dimensions[d] = (B.WebKit && !document.evaluate) ? self['inner' + D] : + (B.Opera) ? document.body['client' + D] : document.documentElement['client' + D]; + }); + return dimensions; + }, + + getWidth: function() { + return this.getDimensions().width; + }, + + getHeight: function() { + return this.getDimensions().height; + }, + + getScrollOffsets: function() { + return Element._returnOffset( + window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft, + window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop); + } +}; +/* Portions of the Selector class are derived from Jack Slocum’s DomQuery, + * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style + * license. Please see http://www.yui-ext.com/ for more information. */ + +var Selector = Class.create({ + initialize: function(expression) { + this.expression = expression.strip(); + this.compileMatcher(); + }, + + shouldUseXPath: function() { + if (!Prototype.BrowserFeatures.XPath) return false; + + var e = this.expression; + + // Safari 3 chokes on :*-of-type and :empty + if (Prototype.Browser.WebKit && + (e.include("-of-type") || e.include(":empty"))) + return false; + + // XPath can't do namespaced attributes, nor can it read + // the "checked" property from DOM nodes + if ((/(\[[\w-]*?:|:checked)/).test(this.expression)) + return false; + + return true; + }, + + compileMatcher: function() { + if (this.shouldUseXPath()) + return this.compileXPathMatcher(); + + var e = this.expression, ps = Selector.patterns, h = Selector.handlers, + c = Selector.criteria, le, p, m; + + if (Selector._cache[e]) { + this.matcher = Selector._cache[e]; + return; + } + + this.matcher = ["this.matcher = function(root) {", + "var r = root, h = Selector.handlers, c = false, n;"]; + + while (e && le != e && (/\S/).test(e)) { + le = e; + for (var i in ps) { + p = ps[i]; + if (m = e.match(p)) { + this.matcher.push(Object.isFunction(c[i]) ? c[i](m) : + new Template(c[i]).evaluate(m)); + e = e.replace(m[0], ''); + break; + } + } + } + + this.matcher.push("return h.unique(n);\n}"); + eval(this.matcher.join('\n')); + Selector._cache[this.expression] = this.matcher; + }, + + compileXPathMatcher: function() { + var e = this.expression, ps = Selector.patterns, + x = Selector.xpath, le, m; + + if (Selector._cache[e]) { + this.xpath = Selector._cache[e]; return; + } + + this.matcher = ['.//*']; + while (e && le != e && (/\S/).test(e)) { + le = e; + for (var i in ps) { + if (m = e.match(ps[i])) { + this.matcher.push(Object.isFunction(x[i]) ? x[i](m) : + new Template(x[i]).evaluate(m)); + e = e.replace(m[0], ''); + break; + } + } + } + + this.xpath = this.matcher.join(''); + Selector._cache[this.expression] = this.xpath; + }, + + findElements: function(root) { + root = root || document; + if (this.xpath) return document._getElementsByXPath(this.xpath, root); + return this.matcher(root); + }, + + match: function(element) { + this.tokens = []; + + var e = this.expression, ps = Selector.patterns, as = Selector.assertions; + var le, p, m; + + while (e && le !== e && (/\S/).test(e)) { + le = e; + for (var i in ps) { + p = ps[i]; + if (m = e.match(p)) { + // use the Selector.assertions methods unless the selector + // is too complex. + if (as[i]) { + this.tokens.push([i, Object.clone(m)]); + e = e.replace(m[0], ''); + } else { + // reluctantly do a document-wide search + // and look for a match in the array + return this.findElements(document).include(element); + } + } + } + } + + var match = true, name, matches; + for (var i = 0, token; token = this.tokens[i]; i++) { + name = token[0], matches = token[1]; + if (!Selector.assertions[name](element, matches)) { + match = false; break; + } + } + + return match; + }, + + toString: function() { + return this.expression; + }, + + inspect: function() { + return "#"; + } +}); + +Object.extend(Selector, { + _cache: { }, + + xpath: { + descendant: "//*", + child: "/*", + adjacent: "/following-sibling::*[1]", + laterSibling: '/following-sibling::*', + tagName: function(m) { + if (m[1] == '*') return ''; + return "[local-name()='" + m[1].toLowerCase() + + "' or local-name()='" + m[1].toUpperCase() + "']"; + }, + className: "[contains(concat(' ', @class, ' '), ' #{1} ')]", + id: "[@id='#{1}']", + attrPresence: function(m) { + m[1] = m[1].toLowerCase(); + return new Template("[@#{1}]").evaluate(m); + }, + attr: function(m) { + m[1] = m[1].toLowerCase(); + m[3] = m[5] || m[6]; + return new Template(Selector.xpath.operators[m[2]]).evaluate(m); + }, + pseudo: function(m) { + var h = Selector.xpath.pseudos[m[1]]; + if (!h) return ''; + if (Object.isFunction(h)) return h(m); + return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m); + }, + operators: { + '=': "[@#{1}='#{3}']", + '!=': "[@#{1}!='#{3}']", + '^=': "[starts-with(@#{1}, '#{3}')]", + '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']", + '*=': "[contains(@#{1}, '#{3}')]", + '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]", + '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]" + }, + pseudos: { + 'first-child': '[not(preceding-sibling::*)]', + 'last-child': '[not(following-sibling::*)]', + 'only-child': '[not(preceding-sibling::* or following-sibling::*)]', + 'empty': "[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]", + 'checked': "[@checked]", + 'disabled': "[@disabled]", + 'enabled': "[not(@disabled)]", + 'not': function(m) { + var e = m[6], p = Selector.patterns, + x = Selector.xpath, le, v; + + var exclusion = []; + while (e && le != e && (/\S/).test(e)) { + le = e; + for (var i in p) { + if (m = e.match(p[i])) { + v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m); + exclusion.push("(" + v.substring(1, v.length - 1) + ")"); + e = e.replace(m[0], ''); + break; + } + } + } + return "[not(" + exclusion.join(" and ") + ")]"; + }, + 'nth-child': function(m) { + return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m); + }, + 'nth-last-child': function(m) { + return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m); + }, + 'nth-of-type': function(m) { + return Selector.xpath.pseudos.nth("position() ", m); + }, + 'nth-last-of-type': function(m) { + return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m); + }, + 'first-of-type': function(m) { + m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m); + }, + 'last-of-type': function(m) { + m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m); + }, + 'only-of-type': function(m) { + var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m); + }, + nth: function(fragment, m) { + var mm, formula = m[6], predicate; + if (formula == 'even') formula = '2n+0'; + if (formula == 'odd') formula = '2n+1'; + if (mm = formula.match(/^(\d+)$/)) // digit only + return '[' + fragment + "= " + mm[1] + ']'; + if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b + if (mm[1] == "-") mm[1] = -1; + var a = mm[1] ? Number(mm[1]) : 1; + var b = mm[2] ? Number(mm[2]) : 0; + predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " + + "((#{fragment} - #{b}) div #{a} >= 0)]"; + return new Template(predicate).evaluate({ + fragment: fragment, a: a, b: b }); + } + } + } + }, + + criteria: { + tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;', + className: 'n = h.className(n, r, "#{1}", c); c = false;', + id: 'n = h.id(n, r, "#{1}", c); c = false;', + attrPresence: 'n = h.attrPresence(n, r, "#{1}"); c = false;', + attr: function(m) { + m[3] = (m[5] || m[6]); + return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}"); c = false;').evaluate(m); + }, + pseudo: function(m) { + if (m[6]) m[6] = m[6].replace(/"/g, '\\"'); + return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m); + }, + descendant: 'c = "descendant";', + child: 'c = "child";', + adjacent: 'c = "adjacent";', + laterSibling: 'c = "laterSibling";' + }, + + patterns: { + // combinators must be listed first + // (and descendant needs to be last combinator) + laterSibling: /^\s*~\s*/, + child: /^\s*>\s*/, + adjacent: /^\s*\+\s*/, + descendant: /^\s/, + + // selectors follow + tagName: /^\s*(\*|[\w\-]+)(\b|$)?/, + id: /^#([\w\-\*]+)(\b|$)/, + className: /^\.([\w\-\*]+)(\b|$)/, + pseudo: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s)|(?=:))/, + attrPresence: /^\[([\w]+)\]/, + attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ + }, + + // for Selector.match and Element#match + assertions: { + tagName: function(element, matches) { + return matches[1].toUpperCase() == element.tagName.toUpperCase(); + }, + + className: function(element, matches) { + return Element.hasClassName(element, matches[1]); + }, + + id: function(element, matches) { + return element.id === matches[1]; + }, + + attrPresence: function(element, matches) { + return Element.hasAttribute(element, matches[1]); + }, + + attr: function(element, matches) { + var nodeValue = Element.readAttribute(element, matches[1]); + return Selector.operators[matches[2]](nodeValue, matches[3]); + } + }, + + handlers: { + // UTILITY FUNCTIONS + // joins two collections + concat: function(a, b) { + for (var i = 0, node; node = b[i]; i++) + a.push(node); + return a; + }, + + // marks an array of nodes for counting + mark: function(nodes) { + for (var i = 0, node; node = nodes[i]; i++) + node._counted = true; + return nodes; + }, + + unmark: function(nodes) { + for (var i = 0, node; node = nodes[i]; i++) + node._counted = undefined; + return nodes; + }, + + // mark each child node with its position (for nth calls) + // "ofType" flag indicates whether we're indexing for nth-of-type + // rather than nth-child + index: function(parentNode, reverse, ofType) { + parentNode._counted = true; + if (reverse) { + for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) { + var node = nodes[i]; + if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++; + } + } else { + for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++) + if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++; + } + }, + + // filters out duplicates and extends all nodes + unique: function(nodes) { + if (nodes.length == 0) return nodes; + var results = [], n; + for (var i = 0, l = nodes.length; i < l; i++) + if (!(n = nodes[i])._counted) { + n._counted = true; + results.push(Element.extend(n)); + } + return Selector.handlers.unmark(results); + }, + + // COMBINATOR FUNCTIONS + descendant: function(nodes) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + h.concat(results, node.getElementsByTagName('*')); + return results; + }, + + child: function(nodes) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) { + for (var j = 0, child; child = node.childNodes[j]; j++) + if (child.nodeType == 1 && child.tagName != '!') results.push(child); + } + return results; + }, + + adjacent: function(nodes) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + var next = this.nextElementSibling(node); + if (next) results.push(next); + } + return results; + }, + + laterSibling: function(nodes) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + h.concat(results, Element.nextSiblings(node)); + return results; + }, + + nextElementSibling: function(node) { + while (node = node.nextSibling) + if (node.nodeType == 1) return node; + return null; + }, + + previousElementSibling: function(node) { + while (node = node.previousSibling) + if (node.nodeType == 1) return node; + return null; + }, + + // TOKEN FUNCTIONS + tagName: function(nodes, root, tagName, combinator) { + tagName = tagName.toUpperCase(); + var results = [], h = Selector.handlers; + if (nodes) { + if (combinator) { + // fastlane for ordinary descendant combinators + if (combinator == "descendant") { + for (var i = 0, node; node = nodes[i]; i++) + h.concat(results, node.getElementsByTagName(tagName)); + return results; + } else nodes = this[combinator](nodes); + if (tagName == "*") return nodes; + } + for (var i = 0, node; node = nodes[i]; i++) + if (node.tagName.toUpperCase() == tagName) results.push(node); + return results; + } else return root.getElementsByTagName(tagName); + }, + + id: function(nodes, root, id, combinator) { + var targetNode = $(id), h = Selector.handlers; + if (!targetNode) return []; + if (!nodes && root == document) return [targetNode]; + if (nodes) { + if (combinator) { + if (combinator == 'child') { + for (var i = 0, node; node = nodes[i]; i++) + if (targetNode.parentNode == node) return [targetNode]; + } else if (combinator == 'descendant') { + for (var i = 0, node; node = nodes[i]; i++) + if (Element.descendantOf(targetNode, node)) return [targetNode]; + } else if (combinator == 'adjacent') { + for (var i = 0, node; node = nodes[i]; i++) + if (Selector.handlers.previousElementSibling(targetNode) == node) + return [targetNode]; + } else nodes = h[combinator](nodes); + } + for (var i = 0, node; node = nodes[i]; i++) + if (node == targetNode) return [targetNode]; + return []; + } + return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : []; + }, + + className: function(nodes, root, className, combinator) { + if (nodes && combinator) nodes = this[combinator](nodes); + return Selector.handlers.byClassName(nodes, root, className); + }, + + byClassName: function(nodes, root, className) { + if (!nodes) nodes = Selector.handlers.descendant([root]); + var needle = ' ' + className + ' '; + for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) { + nodeClassName = node.className; + if (nodeClassName.length == 0) continue; + if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle)) + results.push(node); + } + return results; + }, + + attrPresence: function(nodes, root, attr) { + if (!nodes) nodes = root.getElementsByTagName("*"); + var results = []; + for (var i = 0, node; node = nodes[i]; i++) + if (Element.hasAttribute(node, attr)) results.push(node); + return results; + }, + + attr: function(nodes, root, attr, value, operator) { + if (!nodes) nodes = root.getElementsByTagName("*"); + var handler = Selector.operators[operator], results = []; + for (var i = 0, node; node = nodes[i]; i++) { + var nodeValue = Element.readAttribute(node, attr); + if (nodeValue === null) continue; + if (handler(nodeValue, value)) results.push(node); + } + return results; + }, + + pseudo: function(nodes, name, value, root, combinator) { + if (nodes && combinator) nodes = this[combinator](nodes); + if (!nodes) nodes = root.getElementsByTagName("*"); + return Selector.pseudos[name](nodes, value, root); + } + }, + + pseudos: { + 'first-child': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + if (Selector.handlers.previousElementSibling(node)) continue; + results.push(node); + } + return results; + }, + 'last-child': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + if (Selector.handlers.nextElementSibling(node)) continue; + results.push(node); + } + return results; + }, + 'only-child': function(nodes, value, root) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!h.previousElementSibling(node) && !h.nextElementSibling(node)) + results.push(node); + return results; + }, + 'nth-child': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root); + }, + 'nth-last-child': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root, true); + }, + 'nth-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root, false, true); + }, + 'nth-last-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root, true, true); + }, + 'first-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, "1", root, false, true); + }, + 'last-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, "1", root, true, true); + }, + 'only-of-type': function(nodes, formula, root) { + var p = Selector.pseudos; + return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root); + }, + + // handles the an+b logic + getIndices: function(a, b, total) { + if (a == 0) return b > 0 ? [b] : []; + return $R(1, total).inject([], function(memo, i) { + if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i); + return memo; + }); + }, + + // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type + nth: function(nodes, formula, root, reverse, ofType) { + if (nodes.length == 0) return []; + if (formula == 'even') formula = '2n+0'; + if (formula == 'odd') formula = '2n+1'; + var h = Selector.handlers, results = [], indexed = [], m; + h.mark(nodes); + for (var i = 0, node; node = nodes[i]; i++) { + if (!node.parentNode._counted) { + h.index(node.parentNode, reverse, ofType); + indexed.push(node.parentNode); + } + } + if (formula.match(/^\d+$/)) { // just a number + formula = Number(formula); + for (var i = 0, node; node = nodes[i]; i++) + if (node.nodeIndex == formula) results.push(node); + } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b + if (m[1] == "-") m[1] = -1; + var a = m[1] ? Number(m[1]) : 1; + var b = m[2] ? Number(m[2]) : 0; + var indices = Selector.pseudos.getIndices(a, b, nodes.length); + for (var i = 0, node, l = indices.length; node = nodes[i]; i++) { + for (var j = 0; j < l; j++) + if (node.nodeIndex == indices[j]) results.push(node); + } + } + h.unmark(nodes); + h.unmark(indexed); + return results; + }, + + 'empty': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + // IE treats comments as element nodes + if (node.tagName == '!' || (node.firstChild && !node.innerHTML.match(/^\s*$/))) continue; + results.push(node); + } + return results; + }, + + 'not': function(nodes, selector, root) { + var h = Selector.handlers, selectorType, m; + var exclusions = new Selector(selector).findElements(root); + h.mark(exclusions); + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!node._counted) results.push(node); + h.unmark(exclusions); + return results; + }, + + 'enabled': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!node.disabled) results.push(node); + return results; + }, + + 'disabled': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (node.disabled) results.push(node); + return results; + }, + + 'checked': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (node.checked) results.push(node); + return results; + } + }, + + operators: { + '=': function(nv, v) { return nv == v; }, + '!=': function(nv, v) { return nv != v; }, + '^=': function(nv, v) { return nv.startsWith(v); }, + '$=': function(nv, v) { return nv.endsWith(v); }, + '*=': function(nv, v) { return nv.include(v); }, + '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); }, + '|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); } + }, + + matchElements: function(elements, expression) { + var matches = new Selector(expression).findElements(), h = Selector.handlers; + h.mark(matches); + for (var i = 0, results = [], element; element = elements[i]; i++) + if (element._counted) results.push(element); + h.unmark(matches); + return results; + }, + + findElement: function(elements, expression, index) { + if (Object.isNumber(expression)) { + index = expression; expression = false; + } + return Selector.matchElements(elements, expression || '*')[index || 0]; + }, + + findChildElements: function(element, expressions) { + var exprs = expressions.join(','); + expressions = []; + exprs.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) { + expressions.push(m[1].strip()); + }); + var results = [], h = Selector.handlers; + for (var i = 0, l = expressions.length, selector; i < l; i++) { + selector = new Selector(expressions[i].strip()); + h.concat(results, selector.findElements(element)); + } + return (l > 1) ? h.unique(results) : results; + } +}); + +if (Prototype.Browser.IE) { + // IE returns comment nodes on getElementsByTagName("*"). + // Filter them out. + Selector.handlers.concat = function(a, b) { + for (var i = 0, node; node = b[i]; i++) + if (node.tagName !== "!") a.push(node); + return a; + }; +} + +function $$() { + return Selector.findChildElements(document, $A(arguments)); +} +var Form = { + reset: function(form) { + $(form).reset(); + return form; + }, + + serializeElements: function(elements, options) { + if (typeof options != 'object') options = { hash: !!options }; + else if (Object.isUndefined(options.hash)) options.hash = true; + var key, value, submitted = false, submit = options.submit; + + var data = elements.inject({ }, function(result, element) { + if (!element.disabled && element.name) { + key = element.name; value = $(element).getValue(); + if (value != null && (element.type != 'submit' || (!submitted && + submit !== false && (!submit || key == submit) && (submitted = true)))) { + if (key in result) { + // a key is already present; construct an array of values + if (!Object.isArray(result[key])) result[key] = [result[key]]; + result[key].push(value); + } + else result[key] = value; + } + } + return result; + }); + + return options.hash ? data : Object.toQueryString(data); + } +}; + +Form.Methods = { + serialize: function(form, options) { + return Form.serializeElements(Form.getElements(form), options); + }, + + getElements: function(form) { + return $A($(form).getElementsByTagName('*')).inject([], + function(elements, child) { + if (Form.Element.Serializers[child.tagName.toLowerCase()]) + elements.push(Element.extend(child)); + return elements; + } + ); + }, + + getInputs: function(form, typeName, name) { + form = $(form); + var inputs = form.getElementsByTagName('input'); + + if (!typeName && !name) return $A(inputs).map(Element.extend); + + for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) { + var input = inputs[i]; + if ((typeName && input.type != typeName) || (name && input.name != name)) + continue; + matchingInputs.push(Element.extend(input)); + } + + return matchingInputs; + }, + + disable: function(form) { + form = $(form); + Form.getElements(form).invoke('disable'); + return form; + }, + + enable: function(form) { + form = $(form); + Form.getElements(form).invoke('enable'); + return form; + }, + + findFirstElement: function(form) { + var elements = $(form).getElements().findAll(function(element) { + return 'hidden' != element.type && !element.disabled; + }); + var firstByIndex = elements.findAll(function(element) { + return element.hasAttribute('tabIndex') && element.tabIndex >= 0; + }).sortBy(function(element) { return element.tabIndex }).first(); + + return firstByIndex ? firstByIndex : elements.find(function(element) { + return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); + }); + }, + + focusFirstElement: function(form) { + form = $(form); + form.findFirstElement().activate(); + return form; + }, + + request: function(form, options) { + form = $(form), options = Object.clone(options || { }); + + var params = options.parameters, action = form.readAttribute('action') || ''; + if (action.blank()) action = window.location.href; + options.parameters = form.serialize(true); + + if (params) { + if (Object.isString(params)) params = params.toQueryParams(); + Object.extend(options.parameters, params); + } + + if (form.hasAttribute('method') && !options.method) + options.method = form.method; + + return new Ajax.Request(action, options); + } +}; + +/*--------------------------------------------------------------------------*/ + +Form.Element = { + focus: function(element) { + $(element).focus(); + return element; + }, + + select: function(element) { + $(element).select(); + return element; + } +}; + +Form.Element.Methods = { + serialize: function(element) { + element = $(element); + if (!element.disabled && element.name) { + var value = element.getValue(); + if (value != undefined) { + var pair = { }; + pair[element.name] = value; + return Object.toQueryString(pair); + } + } + return ''; + }, + + getValue: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + return Form.Element.Serializers[method](element); + }, + + setValue: function(element, value) { + element = $(element); + var method = element.tagName.toLowerCase(); + Form.Element.Serializers[method](element, value); + return element; + }, + + clear: function(element) { + $(element).value = ''; + return element; + }, + + present: function(element) { + return $(element).value != ''; + }, + + activate: function(element) { + element = $(element); + try { + element.focus(); + if (element.select && (element.tagName.toLowerCase() != 'input' || + !['button', 'reset', 'submit'].include(element.type))) + element.select(); + } catch (e) { } + return element; + }, + + disable: function(element) { + element = $(element); + element.blur(); + element.disabled = true; + return element; + }, + + enable: function(element) { + element = $(element); + element.disabled = false; + return element; + } +}; + +/*--------------------------------------------------------------------------*/ + +var Field = Form.Element; +var $F = Form.Element.Methods.getValue; + +/*--------------------------------------------------------------------------*/ + +Form.Element.Serializers = { + input: function(element, value) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + return Form.Element.Serializers.inputSelector(element, value); + default: + return Form.Element.Serializers.textarea(element, value); + } + }, + + inputSelector: function(element, value) { + if (Object.isUndefined(value)) return element.checked ? element.value : null; + else element.checked = !!value; + }, + + textarea: function(element, value) { + if (Object.isUndefined(value)) return element.value; + else element.value = value; + }, + + select: function(element, index) { + if (Object.isUndefined(index)) + return this[element.type == 'select-one' ? + 'selectOne' : 'selectMany'](element); + else { + var opt, value, single = !Object.isArray(index); + for (var i = 0, length = element.length; i < length; i++) { + opt = element.options[i]; + value = this.optionValue(opt); + if (single) { + if (value == index) { + opt.selected = true; + return; + } + } + else opt.selected = index.include(value); + } + } + }, + + selectOne: function(element) { + var index = element.selectedIndex; + return index >= 0 ? this.optionValue(element.options[index]) : null; + }, + + selectMany: function(element) { + var values, length = element.length; + if (!length) return null; + + for (var i = 0, values = []; i < length; i++) { + var opt = element.options[i]; + if (opt.selected) values.push(this.optionValue(opt)); + } + return values; + }, + + optionValue: function(opt) { + // extend element because hasAttribute may not be native + return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text; + } +}; + +/*--------------------------------------------------------------------------*/ + +Abstract.TimedObserver = Class.create(PeriodicalExecuter, { + initialize: function($super, element, frequency, callback) { + $super(callback, frequency); + this.element = $(element); + this.lastValue = this.getValue(); + }, + + execute: function() { + var value = this.getValue(); + if (Object.isString(this.lastValue) && Object.isString(value) ? + this.lastValue != value : String(this.lastValue) != String(value)) { + this.callback(this.element, value); + this.lastValue = value; + } + } +}); + +Form.Element.Observer = Class.create(Abstract.TimedObserver, { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.Observer = Class.create(Abstract.TimedObserver, { + getValue: function() { + return Form.serialize(this.element); + } +}); + +/*--------------------------------------------------------------------------*/ + +Abstract.EventObserver = Class.create({ + initialize: function(element, callback) { + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + if (this.element.tagName.toLowerCase() == 'form') + this.registerFormCallbacks(); + else + this.registerCallback(this.element); + }, + + onElementEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + }, + + registerFormCallbacks: function() { + Form.getElements(this.element).each(this.registerCallback, this); + }, + + registerCallback: function(element) { + if (element.type) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + Event.observe(element, 'click', this.onElementEvent.bind(this)); + break; + default: + Event.observe(element, 'change', this.onElementEvent.bind(this)); + break; + } + } + } +}); + +Form.Element.EventObserver = Class.create(Abstract.EventObserver, { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.EventObserver = Class.create(Abstract.EventObserver, { + getValue: function() { + return Form.serialize(this.element); + } +}); +if (!window.Event) var Event = { }; + +Object.extend(Event, { + KEY_BACKSPACE: 8, + KEY_TAB: 9, + KEY_RETURN: 13, + KEY_ESC: 27, + KEY_LEFT: 37, + KEY_UP: 38, + KEY_RIGHT: 39, + KEY_DOWN: 40, + KEY_DELETE: 46, + KEY_HOME: 36, + KEY_END: 35, + KEY_PAGEUP: 33, + KEY_PAGEDOWN: 34, + KEY_INSERT: 45, + + cache: { }, + + relatedTarget: function(event) { + var element; + switch(event.type) { + case 'mouseover': element = event.fromElement; break; + case 'mouseout': element = event.toElement; break; + default: return null; + } + return Element.extend(element); + } +}); + +Event.Methods = (function() { + var isButton; + + if (Prototype.Browser.IE) { + var buttonMap = { 0: 1, 1: 4, 2: 2 }; + isButton = function(event, code) { + return event.button == buttonMap[code]; + }; + + } else if (Prototype.Browser.WebKit) { + isButton = function(event, code) { + switch (code) { + case 0: return event.which == 1 && !event.metaKey; + case 1: return event.which == 1 && event.metaKey; + default: return false; + } + }; + + } else { + isButton = function(event, code) { + return event.which ? (event.which === code + 1) : (event.button === code); + }; + } + + return { + isLeftClick: function(event) { return isButton(event, 0) }, + isMiddleClick: function(event) { return isButton(event, 1) }, + isRightClick: function(event) { return isButton(event, 2) }, + + element: function(event) { + var node = Event.extend(event).target; + return Element.extend(node.nodeType == Node.TEXT_NODE ? node.parentNode : node); + }, + + findElement: function(event, expression) { + var element = Event.element(event); + if (!expression) return element; + var elements = [element].concat(element.ancestors()); + return Selector.findElement(elements, expression, 0); + }, + + pointer: function(event) { + return { + x: event.pageX || (event.clientX + + (document.documentElement.scrollLeft || document.body.scrollLeft)), + y: event.pageY || (event.clientY + + (document.documentElement.scrollTop || document.body.scrollTop)) + }; + }, + + pointerX: function(event) { return Event.pointer(event).x }, + pointerY: function(event) { return Event.pointer(event).y }, + + stop: function(event) { + Event.extend(event); + event.preventDefault(); + event.stopPropagation(); + event.stopped = true; + } + }; +})(); + +Event.extend = (function() { + var methods = Object.keys(Event.Methods).inject({ }, function(m, name) { + m[name] = Event.Methods[name].methodize(); + return m; + }); + + if (Prototype.Browser.IE) { + Object.extend(methods, { + stopPropagation: function() { this.cancelBubble = true }, + preventDefault: function() { this.returnValue = false }, + inspect: function() { return "[object Event]" } + }); + + return function(event) { + if (!event) return false; + if (event._extendedByPrototype) return event; + + event._extendedByPrototype = Prototype.emptyFunction; + var pointer = Event.pointer(event); + Object.extend(event, { + target: event.srcElement, + relatedTarget: Event.relatedTarget(event), + pageX: pointer.x, + pageY: pointer.y + }); + return Object.extend(event, methods); + }; + + } else { + Event.prototype = Event.prototype || document.createEvent("HTMLEvents").__proto__; + Object.extend(Event.prototype, methods); + return Prototype.K; + } +})(); + +Object.extend(Event, (function() { + var cache = Event.cache; + + function getEventID(element) { + if (element._eventID) return element._eventID; + arguments.callee.id = arguments.callee.id || 1; + return element._eventID = ++arguments.callee.id; + } + + function getDOMEventName(eventName) { + if (eventName && eventName.include(':')) return "dataavailable"; + return eventName; + } + + function getCacheForID(id) { + return cache[id] = cache[id] || { }; + } + + function getWrappersForEventName(id, eventName) { + var c = getCacheForID(id); + return c[eventName] = c[eventName] || []; + } + + function createWrapper(element, eventName, handler) { + var id = getEventID(element); + var c = getWrappersForEventName(id, eventName); + if (c.pluck("handler").include(handler)) return false; + + var wrapper = function(event) { + if (!Event || !Event.extend || + (event.eventName && event.eventName != eventName)) + return false; + + Event.extend(event); + handler.call(element, event) + }; + + wrapper.handler = handler; + c.push(wrapper); + return wrapper; + } + + function findWrapper(id, eventName, handler) { + var c = getWrappersForEventName(id, eventName); + return c.find(function(wrapper) { return wrapper.handler == handler }); + } + + function destroyWrapper(id, eventName, handler) { + var c = getCacheForID(id); + if (!c[eventName]) return false; + c[eventName] = c[eventName].without(findWrapper(id, eventName, handler)); + } + + function destroyCache() { + for (var id in cache) + for (var eventName in cache[id]) + cache[id][eventName] = null; + } + + if (window.attachEvent) { + window.attachEvent("onunload", destroyCache); + } + + return { + observe: function(element, eventName, handler) { + element = $(element); + var name = getDOMEventName(eventName); + + var wrapper = createWrapper(element, eventName, handler); + if (!wrapper) return element; + + if (element.addEventListener) { + element.addEventListener(name, wrapper, false); + } else { + element.attachEvent("on" + name, wrapper); + } + + return element; + }, + + stopObserving: function(element, eventName, handler) { + element = $(element); + var id = getEventID(element), name = getDOMEventName(eventName); + + if (!handler && eventName) { + getWrappersForEventName(id, eventName).each(function(wrapper) { + element.stopObserving(eventName, wrapper.handler); + }); + return element; + + } else if (!eventName) { + Object.keys(getCacheForID(id)).each(function(eventName) { + element.stopObserving(eventName); + }); + return element; + } + + var wrapper = findWrapper(id, eventName, handler); + if (!wrapper) return element; + + if (element.removeEventListener) { + element.removeEventListener(name, wrapper, false); + } else { + element.detachEvent("on" + name, wrapper); + } + + destroyWrapper(id, eventName, handler); + + return element; + }, + + fire: function(element, eventName, memo) { + element = $(element); + if (element == document && document.createEvent && !element.dispatchEvent) + element = document.documentElement; + + if (document.createEvent) { + var event = document.createEvent("HTMLEvents"); + event.initEvent("dataavailable", true, true); + } else { + var event = document.createEventObject(); + event.eventType = "ondataavailable"; + } + + event.eventName = eventName; + event.memo = memo || { }; + + if (document.createEvent) { + element.dispatchEvent(event); + } else { + element.fireEvent(event.eventType, event); + } + + return Event.extend(event); + } + }; +})()); + +Object.extend(Event, Event.Methods); + +Element.addMethods({ + fire: Event.fire, + observe: Event.observe, + stopObserving: Event.stopObserving +}); + +Object.extend(document, { + fire: Element.Methods.fire.methodize(), + observe: Element.Methods.observe.methodize(), + stopObserving: Element.Methods.stopObserving.methodize() +}); + +(function() { + /* Support for the DOMContentLoaded event is based on work by Dan Webb, + Matthias Miller, Dean Edwards and John Resig. */ + + var timer, fired = false; + + function fireContentLoadedEvent() { + if (fired) return; + if (timer) window.clearInterval(timer); + document.fire("dom:loaded"); + fired = true; + } + + if (document.addEventListener) { + if (Prototype.Browser.WebKit) { + timer = window.setInterval(function() { + if (/loaded|complete/.test(document.readyState)) + fireContentLoadedEvent(); + }, 0); + + Event.observe(window, "load", fireContentLoadedEvent); + + } else { + document.addEventListener("DOMContentLoaded", + fireContentLoadedEvent, false); + } + + } else { + document.write("