diff --git a/include/NebulaUtil.h b/include/NebulaUtil.h index 2ae3985455..e4319cbede 100644 --- a/include/NebulaUtil.h +++ b/include/NebulaUtil.h @@ -149,6 +149,15 @@ namespace one_util return oss.str(); } + + /** + * Creates a string from the given float, using fixed notation. If the + * number has any decimals, they will be truncated to 2. + * + * @param num + * @return + */ + std::string float_to_str(const float &num); }; #endif /* _NEBULA_UTIL_H_ */ diff --git a/include/ObjectXML.h b/include/ObjectXML.h index f87b78825e..e0c208673d 100644 --- a/include/ObjectXML.h +++ b/include/ObjectXML.h @@ -82,6 +82,17 @@ public: */ int xpath(int& value, const char * xpath_expr, const int& def); + /** + * Gets and sets a xpath attribute, if the attribute is not found a default + * is used + * @param value to set + * @param xpath_expr of the xml element + * @param def default value if the element is not found + * + * @return -1 if default was set + */ + int xpath(float& value, const char * xpath_expr, const float& def); + /** * Gets and sets a xpath attribute, if the attribute is not found a default * is used diff --git a/include/Quota.h b/include/Quota.h index 9f83bdeda6..4707db5206 100644 --- a/include/Quota.h +++ b/include/Quota.h @@ -207,15 +207,6 @@ protected: */ void cleanup_quota(const string& qid); - /** - * Creates a string from the given float, using fixed notation. If the - * number has any decimals, they will be truncated to 2. - * - * @param num - * @return - */ - string float_to_str(const float &num); - private: /** * Creates an empty quota based on the given attribute. The attribute va diff --git a/include/RequestManagerPoolInfoFilter.h b/include/RequestManagerPoolInfoFilter.h index f61af79c76..55369608eb 100644 --- a/include/RequestManagerPoolInfoFilter.h +++ b/include/RequestManagerPoolInfoFilter.h @@ -151,6 +151,31 @@ public: /* ------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------- */ +class VirtualMachinePoolShowback : public RequestManagerPoolInfoFilter +{ +public: + + VirtualMachinePoolShowback(): + RequestManagerPoolInfoFilter("VirtualMachinePoolShowback", + "Returns the virtual machine showback records", + "A:siiiii") + { + Nebula& nd = Nebula::instance(); + pool = nd.get_vmpool(); + auth_object = PoolObjectSQL::VM; + }; + + ~VirtualMachinePoolShowback(){}; + + /* -------------------------------------------------------------------- */ + + void request_execute( + xmlrpc_c::paramList const& paramList, RequestAttributes& att); +}; + +/* ------------------------------------------------------------------------- */ +/* ------------------------------------------------------------------------- */ + class VirtualMachinePoolMonitoring : public RequestManagerPoolInfoFilter { public: diff --git a/include/RequestManagerVirtualMachine.h b/include/RequestManagerVirtualMachine.h index 3ca57720a0..27d9d23722 100644 --- a/include/RequestManagerVirtualMachine.h +++ b/include/RequestManagerVirtualMachine.h @@ -363,4 +363,29 @@ public: /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ +class VirtualMachinePoolCalculateShowback : public RequestManagerVirtualMachine +{ +public: + + VirtualMachinePoolCalculateShowback(): + RequestManagerVirtualMachine("VirtualMachinePoolCalculateShowback", + "Processes all the history records, and stores the monthly cost for each VM", + "A:sii") + { + Nebula& nd = Nebula::instance(); + pool = nd.get_vmpool(); + auth_object = PoolObjectSQL::VM; + }; + + ~VirtualMachinePoolCalculateShowback(){}; + + /* -------------------------------------------------------------------- */ + + void request_execute( + xmlrpc_c::paramList const& paramList, RequestAttributes& att); +}; + +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ + #endif diff --git a/include/VirtualMachine.h b/include/VirtualMachine.h index d7ac634df3..f84e9dd221 100644 --- a/include/VirtualMachine.h +++ b/include/VirtualMachine.h @@ -1512,10 +1512,12 @@ private: ostringstream oss_vm(VirtualMachine::db_bootstrap); ostringstream oss_monit(VirtualMachine::monit_db_bootstrap); ostringstream oss_hist(History::db_bootstrap); + ostringstream oss_showback(VirtualMachine::showback_db_bootstrap); rc = db->exec(oss_vm); rc += db->exec(oss_monit); rc += db->exec(oss_hist); + rc += db->exec(oss_showback); return rc; }; @@ -1753,6 +1755,12 @@ protected: static const char * monit_db_bootstrap; + static const char * showback_table; + + static const char * showback_db_names; + + static const char * showback_db_bootstrap; + /** * Reads the Virtual Machine (identified with its OID) from the database. * @param db pointer to the db diff --git a/include/VirtualMachinePool.h b/include/VirtualMachinePool.h index e8d53ee09d..5331575643 100644 --- a/include/VirtualMachinePool.h +++ b/include/VirtualMachinePool.h @@ -230,6 +230,29 @@ public: int time_start, int time_end); + /** + * Dumps the VM showback information in XML format. A filter can be also + * added to the query as well as a time frame. + * @param oss the output stream to dump the pool contents + * @param where filter for the objects, defaults to all + * @param start_month First month (+year) to include. January is 1. + * Use -1 to unset + * @param start_year First year (+month) to include. e.g. 2014. + * Use -1 to unset + * @param end_month Last month (+year) to include. January is 1. + * Use -1 to unset + * @param end_year Last year (+month) to include. e.g. 2014. + * Use -1 to unset + * + * @return 0 on success + */ + int dump_showback(ostringstream& oss, + const string& where, + int start_month, + int start_year, + int end_month, + int end_year); + /** * Dumps the VM monitoring information entries in XML format. A filter * can be also added to the query. @@ -260,6 +283,28 @@ public: return dump_monitoring(oss, filter.str()); } + /** + * Processes all the history records, and stores the monthly cost for each + * VM + * @param start_month First month (+year) to process. January is 1. + * Use -1 to unset + * @param start_year First year (+month) to process. e.g. 2014. + * Use -1 to unset + * @param end_month Last month (+year) to process. January is 1. + * Use -1 to unset + * @param end_year Last year (+month) to process. e.g. 2014. + * Use -1 to unset + * @param error_str Returns the error reason, if any + * + * @return 0 on success + */ + int calculate_showback( + int start_month, + int start_year, + int end_month, + int end_year, + string &error_str); + private: /** * Factory method to produce VM objects @@ -279,6 +324,11 @@ private: * True or false whether to submit new VM on HOLD or not */ static bool _submit_on_hold; + + /** + * Callback used in calculate_showback + */ + int min_stime_cb(void * _min_stime, int num, char **values, char **names); }; #endif /*VIRTUAL_MACHINE_POOL_H_*/ diff --git a/install.sh b/install.sh index 6560589439..b94ab9cb83 100755 --- a/install.sh +++ b/install.sh @@ -591,6 +591,7 @@ BIN_FILES="src/nebula/oned \ src/scheduler/src/sched/mm_sched \ src/cli/onevm \ src/cli/oneacct \ + src/cli/oneshowback \ src/cli/onehost \ src/cli/onevnet \ src/cli/oneuser \ @@ -1466,7 +1467,8 @@ CLI_BIN_FILES="src/cli/onevm \ src/cli/oneflow \ src/cli/oneflow-template \ src/cli/oneacct \ - src/cli/onesecgroup" + src/cli/onesecgroup \ + src/cli/oneshowback" CLI_CONF_FILES="src/cli/etc/onegroup.yaml \ src/cli/etc/onehost.yaml \ @@ -1480,7 +1482,8 @@ CLI_CONF_FILES="src/cli/etc/onegroup.yaml \ src/cli/etc/onecluster.yaml \ src/cli/etc/onezone.yaml \ src/cli/etc/oneacct.yaml \ - src/cli/etc/onesecgroup.yaml" + src/cli/etc/onesecgroup.yaml \ + src/cli/etc/oneshowback.yaml" #----------------------------------------------------------------------------- # Sunstone files @@ -1813,6 +1816,7 @@ ONEFLOW_LIB_MODELS_FILES="src/flow/lib/models/role.rb \ #----------------------------------------------------------------------------- MAN_FILES="share/man/oneacct.1.gz \ + share/man/oneshowback.1.gz \ share/man/oneacl.1.gz \ share/man/onehost.1.gz \ share/man/oneimage.1.gz \ @@ -1825,6 +1829,7 @@ MAN_FILES="share/man/oneacct.1.gz \ share/man/onedatastore.1.gz \ share/man/onecluster.1.gz \ share/man/onezone.1.gz \ + share/man/onevcenter.1.gz \ share/man/oneflow.1.gz \ share/man/oneflow-template.1.gz \ share/man/onesecgroup.1.gz \ diff --git a/share/etc/oned.conf b/share/etc/oned.conf index b9b7c4f291..e2daaf1463 100644 --- a/share/etc/oned.conf +++ b/share/etc/oned.conf @@ -763,6 +763,8 @@ VM_RESTRICTED_ATTR = "DISK/WRITE_BYTES_SEC" VM_RESTRICTED_ATTR = "DISK/TOTAL_IOPS_SEC" VM_RESTRICTED_ATTR = "DISK/READ_IOPS_SEC" VM_RESTRICTED_ATTR = "DISK/WRITE_IOPS_SEC" +VM_RESTRICTED_ATTR = "CPU_COST" +VM_RESTRICTED_ATTR = "MEMORY_COST" #VM_RESTRICTED_ATTR = "RANK" #VM_RESTRICTED_ATTR = "SCHED_RANK" diff --git a/share/man/SConstruct b/share/man/SConstruct index 92ddb7db42..5e2a0251c5 100644 --- a/share/man/SConstruct +++ b/share/man/SConstruct @@ -48,6 +48,7 @@ env.Man('econe-terminate-instances') env.Man('econe-upload') env.Man('oneacct') +env.Man('oneshowback') env.Man('oneacl') env.Man('onecluster') env.Man('onedatastore') @@ -60,6 +61,7 @@ env.Man('onevm') env.Man('onevnet') env.Man('onegroup') env.Man('onezone') +env.Man('onevcenter') # TODO #env.Man('onesecgroup') diff --git a/share/man/oneshowback.1 b/share/man/oneshowback.1 new file mode 100644 index 0000000000..888f2fdc32 --- /dev/null +++ b/share/man/oneshowback.1 @@ -0,0 +1,50 @@ +.\" generated with Ronn/v0.7.3 +.\" http://github.com/rtomayko/ronn/tree/0.7.3 +. +.TH "ONESHOWBACK" "1" "November 2014" "" "oneshowback(1) -- OpenNebula Showback Tool" +. +.SH "NAME" +\fBoneshowback\fR +. +.SH "SYNOPSIS" +\fBoneshowback\fR \fIcommand\fR [\fIoptions\fR] +. +.SH "OPTIONS" +. +.nf + + \-s, \-\-start TIME First month of the data + \-e, \-\-end TIME Last month of the data + \-u, \-\-userfilter user User name or id to filter the results + \-g, \-\-group group Group name or id to filter the results + \-x, \-\-xml Show the resource in xml format + \-j, \-\-json Show the resource in json format + \-v, \-\-verbose Verbose mode + \-h, \-\-help Show this message + \-V, \-\-version Show version and copyright information + \-\-describe Describe list columns + \-l, \-\-list x,y,z Selects columns to display with list command + \-\-csv Write table in csv format + \-\-user name User name used to connect to OpenNebula + \-\-password password Password to authenticate with OpenNebula + \-\-endpoint endpoint URL of OpenNebula xmlrpc frontend +. +.fi +. +.SH "COMMANDS" +. +.IP "\(bu" 4 +list Returns the showback records valid options: start_time, end_time, userfilter, group, xml, json, verbose, help, version, describe, list, csv, user, password, endpoint +. +.IP "\(bu" 4 +calculate Calculates the showback records valid options: start_time, end_time +. +.IP "" 0 +. +.SH "ARGUMENT FORMATS" +. +.SH "LICENSE" +OpenNebula 4\.10\.0 Copyright 2002\-2014, OpenNebula Project (OpenNebula\.org), C12G Labs +. +.P +Licensed under the Apache License, Version 2\.0 (the "License"); you may not use this file except in compliance with the License\. You may obtain a copy of the License at http://www\.apache\.org/licenses/LICENSE\-2\.0 diff --git a/share/man/onevcenter.1 b/share/man/onevcenter.1 new file mode 100644 index 0000000000..9c1a91bfe4 --- /dev/null +++ b/share/man/onevcenter.1 @@ -0,0 +1,54 @@ +.\" generated with Ronn/v0.7.3 +.\" http://github.com/rtomayko/ronn/tree/0.7.3 +. +.TH "ONEVCENTER" "1" "November 2014" "" "onevcenter(1) -- vCenter import tool" +. +.SH "NAME" +\fBonevcenter\fR +. +.SH "SYNOPSIS" +\fBonevcenter\fR \fIcommand\fR [\fIargs\fR] [\fIoptions\fR] +. +.SH "OPTIONS" +. +.nf + + \-\-vcenter vCenter The vCenter hostname + \-\-vuser username The username to interact with vCenter + \-\-vpass password The password for the user + \-h, \-\-help Show this message + \-V, \-\-version Show version and copyright information + \-\-user name User name used to connect to OpenNebula + \-\-password password Password to authenticate with OpenNebula + \-\-endpoint endpoint URL of OpenNebula xmlrpc frontend +. +.fi +. +.SH "COMMANDS" +. +.IP "\(bu" 4 +hosts Import vCenter clusters as OpenNebula hosts valid options: vcenter, vuser, vpass +. +.IP "\(bu" 4 +templates Import vCenter VM Templates into OpenNebula valid options: vcenter, vuser, vpass +. +.IP "" 0 +. +.SH "ARGUMENT FORMATS" +. +.IP "\(bu" 4 +file Path to a file +. +.IP "\(bu" 4 +range List of id\'s in the form 1,8\.\.15 +. +.IP "\(bu" 4 +text String +. +.IP "" 0 +. +.SH "LICENSE" +OpenNebula 4\.10\.0 Copyright 2002\-2014, OpenNebula Project (OpenNebula\.org), C12G Labs +. +.P +Licensed under the Apache License, Version 2\.0 (the "License"); you may not use this file except in compliance with the License\. You may obtain a copy of the License at http://www\.apache\.org/licenses/LICENSE\-2\.0 diff --git a/src/cli/etc/oneshowback.yaml b/src/cli/etc/oneshowback.yaml new file mode 100644 index 0000000000..5169fb992d --- /dev/null +++ b/src/cli/etc/oneshowback.yaml @@ -0,0 +1,50 @@ +--- +:UID: + :desc: User ID + :size: 4 + +:USER_NAME: + :desc: User name + :size: 12 + +:GID: + :desc: Group ID + :size: 4 + +:GROUP_NAME: + :desc: Group name + :size: 12 + +:VM_ID: + :desc: Virtual Machine ID + :size: 6 + +:VM_NAME: + :desc: Virtual Machine name + :size: 12 + +:MONTH: + :desc: Month + :size: 5 + +:YEAR: + :desc: Year + :size: 5 + +:HOURS: + :desc: Hours + :size: 6 + +:COST: + :desc: Cost + :size: 15 + +:default: +- :USER_NAME +- :GROUP_NAME +- :VM_ID +- :VM_NAME +- :MONTH +- :YEAR +- :HOURS +- :COST \ No newline at end of file diff --git a/src/cli/one_helper/oneacct_helper.rb b/src/cli/one_helper/oneacct_helper.rb index 6b3b0f756e..9f889cc48f 100644 --- a/src/cli/one_helper/oneacct_helper.rb +++ b/src/cli/one_helper/oneacct_helper.rb @@ -18,19 +18,35 @@ require 'one_helper' require 'optparse/time' class AcctHelper < OpenNebulaHelper::OneHelper - START_TIME = { + START_TIME_ACCT = { :name => "start_time", :short => "-s TIME", :large => "--start TIME" , - :description => "Start date and time to take into account", + :description => "First day of the data to retrieve", :format => Time } - END_TIME = { + END_TIME_ACCT = { :name => "end_time", :short => "-e TIME", :large => "--end TIME" , - :description => "End date and time", + :description => "Last day of the data to retrieve", + :format => Time + } + + START_TIME_SHOWBACK = { + :name => "start_time", + :short => "-s TIME", + :large => "--start TIME" , + :description => "First month of the data", + :format => Time + } + + END_TIME_SHOWBACK = { + :name => "end_time", + :short => "-e TIME", + :large => "--end TIME" , + :description => "Last month of the data", :format => Time } @@ -95,8 +111,8 @@ class AcctHelper < OpenNebulaHelper::OneHelper :description => "Split the output in a table for each VM" } - ACCT_OPTIONS = [START_TIME, END_TIME, USERFILTER, GROUP, HOST, XPATH, XML, JSON, SPLIT] - + ACCT_OPTIONS = [START_TIME_ACCT, END_TIME_ACCT, USERFILTER, GROUP, HOST, XPATH, XML, JSON, SPLIT] + SHOWBACK_OPTIONS = [START_TIME_SHOWBACK, END_TIME_SHOWBACK, USERFILTER, GROUP, XML, JSON] ACCT_TABLE = CLIHelper::ShowTable.new("oneacct.yaml", nil) do column :UID, "User ID", :size=>4 do |d| @@ -152,6 +168,50 @@ class AcctHelper < OpenNebulaHelper::OneHelper default :VID, :HOSTNAME, :ACTION, :REASON, :START_TIME, :END_TIME, :MEMORY, :CPU, :NET_RX, :NET_TX end + SHOWBACK_TABLE = CLIHelper::ShowTable.new("oneshowback.yaml", nil) do + column :UID, "User ID", :size=>4 do |d| + d["UID"] + end + + column :USER_NAME, "User name", :left, :size=>12 do |d| + d["UNAME"] + end + + column :GID, "Group ID", :size=>4 do |d| + d["GID"] + end + + column :GROUP_NAME, "Group name", :left, :size=>12 do |d| + d["GNAME"] + end + + column :VM_ID, "Virtual Machine ID", :size=>6 do |d| + d["VMID"] + end + + column :VM_NAME, "Virtual Machine name", :left, :size=>12 do |d| + d["VMNAME"] + end + + column :MONTH, "Month", :size=>5 do |d| + d["MONTH"] + end + + column :YEAR, "Year", :size=>5 do |d| + d["YEAR"] + end + + column :HOURS, "Hours", :size=>6 do |d| + d["HOURS"] + end + + column :COST, "Cost", :size=>15 do |d| + d["COST"] + end + + default :USER_NAME, :GROUP_NAME, :VM_ID, :VM_NAME, :MONTH, :YEAR, :HOURS, :COST + end + def self.print_start_end_time_header(start_time, end_time) print "Showing active history records from " @@ -184,4 +244,12 @@ class AcctHelper < OpenNebulaHelper::OneHelper CLIHelper.scr_restore puts end + + def self.print_month_header(year, month) + CLIHelper.scr_bold + CLIHelper.scr_underline + puts "# Showback for #{month}/#{year}".ljust(80) + CLIHelper.scr_restore + puts + end end diff --git a/src/cli/oneacct b/src/cli/oneacct index f5774506af..40f953e11c 100755 --- a/src/cli/oneacct +++ b/src/cli/oneacct @@ -70,7 +70,7 @@ cmd = CommandParser::CmdParser.new(ARGV) do if options[:json] || options[:xml] xml_str = pool.accounting_xml(filter_flag, common_opts) if OpenNebula.is_error?(xml_str) - puts acct_hash.message + puts xml_str.message exit -1 end diff --git a/src/cli/oneshowback b/src/cli/oneshowback new file mode 100755 index 0000000000..a4ac1a336d --- /dev/null +++ b/src/cli/oneshowback @@ -0,0 +1,178 @@ +#!/usr/bin/env ruby + +# -------------------------------------------------------------------------- # +# Copyright 2002-2014, OpenNebula Project (OpenNebula.org), C12G Labs # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); you may # +# not use this file except in compliance with the License. You may obtain # +# a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +#--------------------------------------------------------------------------- # + +ONE_LOCATION=ENV["ONE_LOCATION"] + +if !ONE_LOCATION + RUBY_LIB_LOCATION="/usr/lib/one/ruby" +else + RUBY_LIB_LOCATION=ONE_LOCATION+"/lib/ruby" +end + +$: << RUBY_LIB_LOCATION +$: << RUBY_LIB_LOCATION+"/cli" + +require 'command_parser' +require 'one_helper/oneacct_helper' + +require 'json' + +cmd = CommandParser::CmdParser.new(ARGV) do + + @formats = Hash.new + + usage "`oneshowback` []" + description "" + version OpenNebulaHelper::ONE_VERSION + + helper=OpenNebulaHelper::OneHelper.new + + before_proc do + helper.set_client(options) + end + + command :list, "Returns the showback records", :options => + AcctHelper::SHOWBACK_OPTIONS + CommandParser::OPTIONS + + [OpenNebulaHelper::DESCRIBE, CLIHelper::LIST, CLIHelper::CSV_OPT] + + OpenNebulaHelper::CLIENT_OPTIONS do + + if options[:describe] + AcctHelper::SHOWBACK_TABLE.describe_columns + exit(0) + end + + filter_flag = (options[:userfilter] || VirtualMachinePool::INFO_ALL) + + start_month = -1 + start_year = -1 + + if (options[:start_time]) + start_month = options[:start_time].month + start_year = options[:start_time].year + end + + end_month = -1 + end_year = -1 + + if (options[:end_time]) + end_month = options[:end_time].month + end_year = options[:end_time].year + end + + common_opts = { + :start_month => start_month, + :start_year => start_year, + :end_month => end_month, + :end_year => end_year, + :group => options[:group], + :xpath => options[:xpath] + } + + pool = OpenNebula::VirtualMachinePool.new(helper.client) + + if options[:json] || options[:xml] + xml_str = pool.showback_xml(filter_flag, common_opts) + if OpenNebula.is_error?(xml_str) + puts xml_str.message + exit -1 + end + + if options[:json] + xmldoc = XMLElement.new + xmldoc.initialize_xml(xml_str, 'SHOWBACK_RECORDS') + + puts JSON.pretty_generate(xmldoc.to_hash) + elsif options[:xml] + puts xml_str + end + + exit_code 0 + else + + order_by = Hash.new + if !options[:csv] + order_by[:order_by_1] = 'YEAR' + order_by[:order_by_2] = 'MONTH' + end + + data_hash = pool.showback(filter_flag, + common_opts.merge(order_by)) + if OpenNebula.is_error?(data_hash) + puts data_hash.message + exit -1 + end + + if options[:csv] + a = Array.new + + if data_hash['SHOWBACK_RECORDS'] + a = data_hash['SHOWBACK_RECORDS']['SHOWBACK'] + end + + AcctHelper::SHOWBACK_TABLE.show(a, options) + exit(0) + end + + data_hash.each { |year, value| + value.each { |month, showback_array| + AcctHelper.print_month_header(year, month) + + array = showback_array['SHOWBACK_RECORDS']['SHOWBACK'] + AcctHelper::SHOWBACK_TABLE.show(array, options) + puts + } + + } + + exit_code 0 + end + end + + command :"calculate", "Calculates the showback records", :options => + [AcctHelper::START_TIME_SHOWBACK, AcctHelper::END_TIME_SHOWBACK] do + + + start_month = -1 + start_year = -1 + + if (options[:start_time]) + start_month = options[:start_time].month + start_year = options[:start_time].year + end + + end_month = -1 + end_year = -1 + + if (options[:end_time]) + end_month = options[:end_time].month + end_year = options[:end_time].year + end + + rc = OpenNebula::VirtualMachinePool.new(helper.client). + calculate_showback(start_month, start_year, end_month, end_year) + + if OpenNebula.is_error?(rc) + warn rc.message + exit -1 + else + puts rc + exit_code 0 + end + + end +end \ No newline at end of file diff --git a/src/common/NebulaUtil.cc b/src/common/NebulaUtil.cc index dce4087cbb..7c47e2c5b9 100644 --- a/src/common/NebulaUtil.cc +++ b/src/common/NebulaUtil.cc @@ -27,6 +27,7 @@ #include #include #include +#include using namespace std; @@ -241,3 +242,21 @@ vector one_util::split(const string& st, char delim, bool clean_empty) /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ + +string one_util::float_to_str(const float &num) +{ + ostringstream oss; + + if ( num == ceil(num) ) + { + oss.precision(0); + } + else + { + oss.precision(2); + } + + oss << fixed << num; + + return oss.str(); +} diff --git a/src/oca/ruby/opennebula/document_pool.rb b/src/oca/ruby/opennebula/document_pool.rb index a306c671fc..6dd117b4dc 100644 --- a/src/oca/ruby/opennebula/document_pool.rb +++ b/src/oca/ruby/opennebula/document_pool.rb @@ -22,7 +22,7 @@ module OpenNebula # and the factory method. # # @example - # require 'opennebuña/document_pool' + # require 'opennebula/document_pool' # # module OpenNebula # class CustomObjectPool < DocumentPool diff --git a/src/oca/ruby/opennebula/virtual_machine_pool.rb b/src/oca/ruby/opennebula/virtual_machine_pool.rb index e2f4539287..f1ca0dec63 100644 --- a/src/oca/ruby/opennebula/virtual_machine_pool.rb +++ b/src/oca/ruby/opennebula/virtual_machine_pool.rb @@ -25,9 +25,11 @@ module OpenNebula VM_POOL_METHODS = { - :info => "vmpool.info", - :monitoring => "vmpool.monitoring", - :accounting => "vmpool.accounting" + :info => "vmpool.info", + :monitoring => "vmpool.monitoring", + :accounting => "vmpool.accounting", + :showback => "vmpool.showback", + :calculate_showback => "vmpool.calculateshowback" } # Constants for info queries (include/RequestManagerPoolInfoFilter.h) @@ -162,6 +164,27 @@ module OpenNebula return @client.call(VM_POOL_METHODS[:monitoring], filter_flag) end + # Processes all the history records, and stores the monthly cost for + # each VM + # + # @param [Integer] start_month First month (+year) to process. January is 1. + # Use -1 to unset + # @param [Integer] start_year First year (+month) to process. e.g. 2014. + # Use -1 to unset + # @param [Integer] end_month Last month (+year) to process. January is 1. + # Use -1 to unset + # @param [Integer] end_year Last year (+month) to process. e.g. 2014. + # Use -1 to unset + def calculate_showback(start_month, start_year, end_month, end_year) + start_month ||= -1 + start_year ||= -1 + end_month ||= -1 + end_year ||= -1 + + return @client.call(VM_POOL_METHODS[:calculate_showback], + start_month, start_year, end_month, end_year) + end + # Retrieves the accounting data for all the VMs in the pool # # @param [Integer] filter_flag Optional filter flag to retrieve all or @@ -283,6 +306,92 @@ module OpenNebula xml_str end + # Retrieves the showback data for all the VMs in the pool + # + # @param [Integer] filter_flag Optional filter flag to retrieve all or + # part of the Pool. Possible values: INFO_ALL, INFO_GROUP, INFO_MINE + # or user_id + # @param [Hash] options + # @option params [Integer] :start_year First month (+year) to take + # into account, if no start time is required use -1 + # @option params [Integer] :start_month First year (+month) to take + # into account, if no start time is required use -1 + # @option params [Integer] :end_year Last month (+year) to take + # into account, if no end time is required use -1 + # @option params [Integer] :end_month Last year (+month) to take + # into account, if no end time is required use -1 + # @option params [Integer] :group Group id to filter the results + # @option params [String] :xpath Xpath expression to filter the results. + # For example: SHOWBACK[COST>0] + # @option params [String] :order_by_1 Xpath expression to group the + # @option params [String] :order_by_2 Xpath expression to group the + # returned hash. This will be the second level of the hash + # + # @return [Hash, OpenNebula::Error] + # The first level hash uses the :order_by_1 values as keys, and + # as value a Hash with the :order_by_2 values and the SHOWBACK_RECORDS + def showback(filter_flag=INFO_ALL, options={}) + data_hash = Hash.new + + rc = build_showback(filter_flag, options) do |record| + hash = data_hash + + if options[:order_by_1] + id_1 = record[options[:order_by_1]] + data_hash[id_1] ||= Hash.new + + if options[:order_by_2] + id_2 = record[options[:order_by_2]] + data_hash[id_1][id_2] ||= Hash.new + + hash = data_hash[id_1][id_2] + else + hash = data_hash[id_1] + end + end + + hash["SHOWBACK_RECORDS"] ||= Hash.new + hash["SHOWBACK_RECORDS"]["SHOWBACK"] ||= Array.new + hash["SHOWBACK_RECORDS"]["SHOWBACK"] << record.to_hash['SHOWBACK'] + end + + return rc if OpenNebula.is_error?(rc) + + data_hash + end + + # Retrieves the showback data for all the VMs in the pool, in xml + # + # @param [Integer] filter_flag Optional filter flag to retrieve all or + # part of the Pool. Possible values: INFO_ALL, INFO_GROUP, INFO_MINE + # or user_id + # @param [Hash] options + # @option params [Integer] :start_year First month (+year) to take + # into account, if no start time is required use -1 + # @option params [Integer] :start_month First year (+month) to take + # into account, if no start time is required use -1 + # @option params [Integer] :end_year Last month (+year) to take + # into account, if no end time is required use -1 + # @option params [Integer] :end_month Last year (+month) to take + # into account, if no end time is required use -1 + # @option params [Integer] :group Group id to filter the results + # @option params [String] :xpath Xpath expression to filter the results. + # For example: SHOWBACK[COST>10] + # + # @return [String] the xml representing the showback data + def showback_xml(filter_flag=INFO_ALL, options={}) + xml_str = "\n" + + rc = build_showback(filter_flag, options) do |showback| + xml_str << showback.to_xml + end + + return rc if OpenNebula.is_error?(rc) + + xml_str << "\n" + xml_str + end + private def build_accounting(filter_flag, options, &block) @@ -316,6 +425,38 @@ module OpenNebula acct_hash end + def build_showback(filter_flag, options, &block) + xml_str = @client.call(VM_POOL_METHODS[:showback], + filter_flag, + options[:start_month], + options[:start_year], + options[:end_month], + options[:end_year]) + + return xml_str if OpenNebula.is_error?(xml_str) + + xmldoc = XMLElement.new + xmldoc.initialize_xml(xml_str, 'SHOWBACK_RECORDS') + + xpath_array = Array.new + xpath_array << "SHOWBACK[GID=#{options[:group]}]" if options[:group] + xpath_array << options[:xpath] if options[:xpath] + + if xpath_array.empty? + xpath_str = "SHOWBACK" + else + xpath_str = xpath_array.join(' | ') + end + + data_hash = Hash.new + + xmldoc.each(xpath_str) do |showback| + block.call(showback) + end + + data_hash + end + def info_filter(xml_method, who, start_id, end_id, state) return xmlrpc_info(xml_method, who, start_id, end_id, state) end diff --git a/src/onedb/local/4.9.80_to_4.11.80.rb b/src/onedb/local/4.9.80_to_4.11.80.rb index 693d249f83..4fa72c0383 100644 --- a/src/onedb/local/4.9.80_to_4.11.80.rb +++ b/src/onedb/local/4.9.80_to_4.11.80.rb @@ -29,6 +29,14 @@ module Migrator init_log_time() + ######################################################################## + # Showback + ######################################################################## + + @db.run "CREATE TABLE vm_showback (vmid INTEGER, year INTEGER, month INTEGER, body MEDIUMTEXT, PRIMARY KEY(vmid, year, month));" + + log_time() + ######################################################################## # Security Groups ######################################################################## diff --git a/src/onedb/onedb.rb b/src/onedb/onedb.rb index 0a2150ee19..19b20dbb8e 100644 --- a/src/onedb/onedb.rb +++ b/src/onedb/onedb.rb @@ -119,7 +119,7 @@ class OneDB else puts "Shared: #{ret[:version]}" - puts "Local: #{ret[:version]}" + puts "Local: #{ret[:local_version]}" end return 0 diff --git a/src/rm/RequestManager.cc b/src/rm/RequestManager.cc index ffd6a58933..ec55e5bc29 100644 --- a/src/rm/RequestManager.cc +++ b/src/rm/RequestManager.cc @@ -302,6 +302,9 @@ void RequestManager::register_xml_methods() xmlrpc_c::methodPtr vm_pool_acct(new VirtualMachinePoolAccounting()); xmlrpc_c::methodPtr vm_pool_monitoring(new VirtualMachinePoolMonitoring()); + xmlrpc_c::methodPtr vm_pool_showback(new VirtualMachinePoolShowback()); + xmlrpc_c::methodPtr vm_pool_calculate_showback(new VirtualMachinePoolCalculateShowback()); + // VirtualNetwork Methods xmlrpc_c::methodPtr vn_add_ar(new VirtualNetworkAddAddressRange()); xmlrpc_c::methodPtr vn_rm_ar(new VirtualNetworkRmAddressRange()); @@ -447,6 +450,8 @@ void RequestManager::register_xml_methods() RequestManagerRegistry.addMethod("one.vmpool.info", vm_pool_info); RequestManagerRegistry.addMethod("one.vmpool.accounting", vm_pool_acct); RequestManagerRegistry.addMethod("one.vmpool.monitoring", vm_pool_monitoring); + RequestManagerRegistry.addMethod("one.vmpool.showback", vm_pool_showback); + RequestManagerRegistry.addMethod("one.vmpool.calculateshowback", vm_pool_calculate_showback); /* VM Template related methods*/ RequestManagerRegistry.addMethod("one.template.update", template_update); diff --git a/src/rm/RequestManagerPoolInfoFilter.cc b/src/rm/RequestManagerPoolInfoFilter.cc index 701db9a833..806cf2c0ba 100644 --- a/src/rm/RequestManagerPoolInfoFilter.cc +++ b/src/rm/RequestManagerPoolInfoFilter.cc @@ -165,6 +165,50 @@ void VirtualMachinePoolAccounting::request_execute( /* ------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------- */ +void VirtualMachinePoolShowback::request_execute( + xmlrpc_c::paramList const& paramList, + RequestAttributes& att) +{ + int filter_flag = xmlrpc_c::value_int(paramList.getInt(1)); + int start_month = xmlrpc_c::value_int(paramList.getInt(2)); + int start_year = xmlrpc_c::value_int(paramList.getInt(3)); + int end_month = xmlrpc_c::value_int(paramList.getInt(4)); + int end_year = xmlrpc_c::value_int(paramList.getInt(5)); + + ostringstream oss; + string where; + int rc; + + if ( filter_flag < MINE ) + { + failure_response(XML_RPC_API, + request_error("Incorrect filter_flag",""), + att); + return; + } + + where_filter(att, filter_flag, -1, -1, "", "", false, false, false, where); + + rc = (static_cast(pool))->dump_showback(oss, + where, + start_month, + start_year, + end_month, + end_year); + if ( rc != 0 ) + { + failure_response(INTERNAL,request_error("Internal Error",""), att); + return; + } + + success_response(oss.str(), att); + + return; +} + +/* ------------------------------------------------------------------------- */ +/* ------------------------------------------------------------------------- */ + void VirtualMachinePoolMonitoring::request_execute( xmlrpc_c::paramList const& paramList, RequestAttributes& att) diff --git a/src/rm/RequestManagerVirtualMachine.cc b/src/rm/RequestManagerVirtualMachine.cc index eb5ff20212..af17bd9515 100644 --- a/src/rm/RequestManagerVirtualMachine.cc +++ b/src/rm/RequestManagerVirtualMachine.cc @@ -2219,3 +2219,41 @@ void VirtualMachineRecover::request_execute( /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ + +void VirtualMachinePoolCalculateShowback::request_execute( + xmlrpc_c::paramList const& paramList, + RequestAttributes& att) +{ + int start_month = xmlrpc_c::value_int(paramList.getInt(1)); + int start_year = xmlrpc_c::value_int(paramList.getInt(2)); + int end_month = xmlrpc_c::value_int(paramList.getInt(3)); + int end_year = xmlrpc_c::value_int(paramList.getInt(4)); + + ostringstream oss; + string where; + int rc; + string error_str; + + if ( att.gid != 0 ) + { + failure_response(AUTHORIZATION, + authorization_error("Action reserved for group 0 only", att), + att); + return; + } + + rc = (static_cast(pool))->calculate_showback( + start_month, start_year, end_month, end_year, error_str); + + if (rc != 0) + { + failure_response(AUTHORIZATION, + request_error(error_str, ""), + att); + return; + } + + success_response("", att); + + return; +} diff --git a/src/scheduler/etc/sched.conf b/src/scheduler/etc/sched.conf index 0156ca8bbb..a0770977f7 100644 --- a/src/scheduler/etc/sched.conf +++ b/src/scheduler/etc/sched.conf @@ -53,7 +53,7 @@ # - system: defines the logging system: # file to log in the sched.log file # syslog to use the syslog facilities -# - debug_level: 0 = ERROR, 1 = WARNING, 2 = INFO, 3 = DEBU +# - debug_level: 0 = ERROR, 1 = WARNING, 2 = INFO, 3 = DEBUG #******************************************************************************* MESSAGE_SIZE = 1073741824 diff --git a/src/sunstone/etc/sunstone-views/admin.yaml b/src/sunstone/etc/sunstone-views/admin.yaml index 4bdcf948c0..b9999f795f 100644 --- a/src/sunstone/etc/sunstone-views/admin.yaml +++ b/src/sunstone/etc/sunstone-views/admin.yaml @@ -27,6 +27,8 @@ enabled_tabs: enterprise-tab: true zones-tab: true autorefresh: true +features: + showback: true tabs: dashboard-tab: panel_tabs: @@ -52,6 +54,8 @@ tabs: panel_tabs: user_info_tab: true user_quotas_tab: true + user_accounting_tab: true + user_showback_tab: true table_columns: - 0 # Checkbox - 1 # ID @@ -75,7 +79,11 @@ tabs: User.delete: true groups-tab: panel_tabs: + group_info_tab: true group_quotas_tab: true + group_providers_tab: true + group_accounting_tab: true + group_shoback_tab: true table_columns: - 0 # Checkbox - 1 # ID diff --git a/src/sunstone/etc/sunstone-views/cloud.yaml b/src/sunstone/etc/sunstone-views/cloud.yaml index ebd6f3dd42..4ce192930b 100644 --- a/src/sunstone/etc/sunstone-views/cloud.yaml +++ b/src/sunstone/etc/sunstone-views/cloud.yaml @@ -1,6 +1,8 @@ provision_logo: images/one_small_logo.png enabled_tabs: provision-tab: true +features: + showback: true tabs: provision-tab: panel_tabs: diff --git a/src/sunstone/etc/sunstone-views/user.yaml b/src/sunstone/etc/sunstone-views/user.yaml index ef30bd8da3..a79973bdce 100644 --- a/src/sunstone/etc/sunstone-views/user.yaml +++ b/src/sunstone/etc/sunstone-views/user.yaml @@ -26,6 +26,8 @@ enabled_tabs: community-tab: false enterprise-tab: false autorefresh: true +features: + showback: true tabs: dashboard-tab: panel_tabs: @@ -52,6 +54,8 @@ tabs: panel_tabs: user_info_tab: true user_quotas_tab: true + user_accounting_tab: true + user_showback_tab: true table_columns: - 0 # Checkbox - 1 # ID @@ -74,7 +78,11 @@ tabs: User.delete: true groups-tab: panel_tabs: + group_info_tab: true group_quotas_tab: true + group_providers_tab: true + group_accounting_tab: true + group_shoback_tab: true table_columns: - 0 # Checkbox - 1 # ID diff --git a/src/sunstone/etc/sunstone-views/vcenter.yaml b/src/sunstone/etc/sunstone-views/vcenter.yaml index 97e06407db..ac09050815 100644 --- a/src/sunstone/etc/sunstone-views/vcenter.yaml +++ b/src/sunstone/etc/sunstone-views/vcenter.yaml @@ -27,6 +27,8 @@ enabled_tabs: enterprise-tab: true zones-tab: true autorefresh: true +features: + showback: true tabs: dashboard-tab: panel_tabs: @@ -52,6 +54,8 @@ tabs: panel_tabs: user_info_tab: true user_quotas_tab: true + user_accounting_tab: true + user_showback_tab: true table_columns: - 0 # Checkbox - 1 # ID @@ -75,7 +79,11 @@ tabs: User.delete: true groups-tab: panel_tabs: + group_info_tab: true group_quotas_tab: true + group_providers_tab: true + group_accounting_tab: true + group_shoback_tab: true table_columns: - 0 # Checkbox - 1 # ID diff --git a/src/sunstone/etc/sunstone-views/vdcadmin.yaml b/src/sunstone/etc/sunstone-views/vdcadmin.yaml index 4cfd7713e3..aea47b87f7 100644 --- a/src/sunstone/etc/sunstone-views/vdcadmin.yaml +++ b/src/sunstone/etc/sunstone-views/vdcadmin.yaml @@ -1,6 +1,8 @@ provision_logo: images/one_small_logo.png enabled_tabs: provision-tab: true +features: + showback: true tabs: provision-tab: panel_tabs: diff --git a/src/sunstone/models/SunstoneServer.rb b/src/sunstone/models/SunstoneServer.rb index de26513ecc..5aaa8b2054 100644 --- a/src/sunstone/models/SunstoneServer.rb +++ b/src/sunstone/models/SunstoneServer.rb @@ -393,6 +393,33 @@ class SunstoneServer < CloudServer return [200, rc.to_json] end + def get_vm_showback(options) + pool = VirtualMachinePool.new(@client) + + filter_flag = options[:userfilter] ? options[:userfilter].to_i : VirtualMachinePool::INFO_ALL + start_month = options[:start_month] ? options[:start_month].to_i : -1 + start_year = options[:start_year] ? options[:start_year].to_i : -1 + end_month = options[:end_month] ? options[:end_month].to_i : -1 + end_year = options[:end_year] ? options[:end_year].to_i : -1 + + acct_options = { + :start_month => start_month, + :start_year => start_year, + :end_month => end_month, + :end_year => end_year, + :group => options[:group] + } + + rc = pool.showback(filter_flag, acct_options) + + if OpenNebula.is_error?(rc) + error = Error.new(rc.message) + return [500, error.to_json] + end + + return [200, rc.to_json] + end + private diff --git a/src/sunstone/public/bower_components/datatables/.bower.json b/src/sunstone/public/bower_components/datatables/.bower.json index f936277cef..f24f8cd019 100644 --- a/src/sunstone/public/bower_components/datatables/.bower.json +++ b/src/sunstone/public/bower_components/datatables/.bower.json @@ -1,6 +1,6 @@ { "name": "datatables", - "version": "1.10.3", + "version": "1.10.4", "main": [ "media/js/jquery.dataTables.js", "media/css/jquery.dataTables.css" @@ -25,11 +25,11 @@ "package.json" ], "homepage": "https://github.com/DataTables/DataTables", - "_release": "1.10.3", + "_release": "1.10.4", "_resolution": { "type": "version", - "tag": "1.10.3", - "commit": "5abc57eaf0b977075ca67e49df080834aa068cec" + "tag": "1.10.4", + "commit": "96866489a52266c4b356364d7756dde8b7a0c172" }, "_source": "git://github.com/DataTables/DataTables.git", "_target": "*", diff --git a/src/sunstone/public/bower_components/datatables/bower.json b/src/sunstone/public/bower_components/datatables/bower.json index 9e1e96506a..24b6cab4de 100644 --- a/src/sunstone/public/bower_components/datatables/bower.json +++ b/src/sunstone/public/bower_components/datatables/bower.json @@ -1,6 +1,6 @@ { "name": "datatables", - "version": "1.10.3", + "version": "1.10.4", "main": [ "media/js/jquery.dataTables.js", "media/css/jquery.dataTables.css" diff --git a/src/sunstone/public/bower_components/datatables/media/js/jquery.dataTables.js b/src/sunstone/public/bower_components/datatables/media/js/jquery.dataTables.js index 4aab04b1cc..c39ae8cc79 100644 --- a/src/sunstone/public/bower_components/datatables/media/js/jquery.dataTables.js +++ b/src/sunstone/public/bower_components/datatables/media/js/jquery.dataTables.js @@ -1,11 +1,11 @@ -/*! DataTables 1.10.3 +/*! DataTables 1.10.4 * ©2008-2014 SpryMedia Ltd - datatables.net/license */ /** * @summary DataTables * @description Paginate, search and order HTML tables - * @version 1.10.3 + * @version 1.10.4 * @file jquery.dataTables.js * @author SpryMedia Ltd (www.sprymedia.co.uk) * @contact www.sprymedia.co.uk/contact @@ -22,7 +22,7 @@ */ /*jslint evil: true, undef: true, browser: true */ -/*globals $,require,jQuery,define,_selector_run,_selector_opts,_selector_first,_selector_row_indexes,_ext,_Api,_api_register,_api_registerPlural,_re_new_lines,_re_html,_re_formatted_numeric,_re_escape_regex,_empty,_intVal,_numToDecimal,_isNumber,_isHtml,_htmlNumeric,_pluck,_pluck_order,_range,_stripHtml,_unique,_fnBuildAjax,_fnAjaxUpdate,_fnAjaxParameters,_fnAjaxUpdateDraw,_fnAjaxDataSrc,_fnAddColumn,_fnColumnOptions,_fnAdjustColumnSizing,_fnVisibleToColumnIndex,_fnColumnIndexToVisible,_fnVisbleColumns,_fnGetColumns,_fnColumnTypes,_fnApplyColumnDefs,_fnHungarianMap,_fnCamelToHungarian,_fnLanguageCompat,_fnBrowserDetect,_fnAddData,_fnAddTr,_fnNodeToDataIndex,_fnNodeToColumnIndex,_fnGetCellData,_fnSetCellData,_fnSplitObjNotation,_fnGetObjectDataFn,_fnSetObjectDataFn,_fnGetDataMaster,_fnClearTable,_fnDeleteIndex,_fnInvalidateRow,_fnGetRowElements,_fnCreateTr,_fnBuildHead,_fnDrawHead,_fnDraw,_fnReDraw,_fnAddOptionsHtml,_fnDetectHeader,_fnGetUniqueThs,_fnFeatureHtmlFilter,_fnFilterComplete,_fnFilterCustom,_fnFilterColumn,_fnFilter,_fnFilterCreateSearch,_fnEscapeRegex,_fnFilterData,_fnFeatureHtmlInfo,_fnUpdateInfo,_fnInfoMacros,_fnInitialise,_fnInitComplete,_fnLengthChange,_fnFeatureHtmlLength,_fnFeatureHtmlPaginate,_fnPageChange,_fnFeatureHtmlProcessing,_fnProcessingDisplay,_fnFeatureHtmlTable,_fnScrollDraw,_fnApplyToChildren,_fnCalculateColumnWidths,_fnThrottle,_fnConvertToWidth,_fnScrollingWidthAdjust,_fnGetWidestNode,_fnGetMaxLenString,_fnStringToCss,_fnScrollBarWidth,_fnSortFlatten,_fnSort,_fnSortAria,_fnSortListener,_fnSortAttachListener,_fnSortingClasses,_fnSortData,_fnSaveState,_fnLoadState,_fnSettingsFromNode,_fnLog,_fnMap,_fnBindAction,_fnCallbackReg,_fnCallbackFire,_fnLengthOverflow,_fnRenderer,_fnDataSource,_fnRowAttributes*/ +/*globals $,require,jQuery,define,_selector_run,_selector_opts,_selector_first,_selector_row_indexes,_ext,_Api,_api_register,_api_registerPlural,_re_new_lines,_re_html,_re_formatted_numeric,_re_escape_regex,_empty,_intVal,_numToDecimal,_isNumber,_isHtml,_htmlNumeric,_pluck,_pluck_order,_range,_stripHtml,_unique,_fnBuildAjax,_fnAjaxUpdate,_fnAjaxParameters,_fnAjaxUpdateDraw,_fnAjaxDataSrc,_fnAddColumn,_fnColumnOptions,_fnAdjustColumnSizing,_fnVisibleToColumnIndex,_fnColumnIndexToVisible,_fnVisbleColumns,_fnGetColumns,_fnColumnTypes,_fnApplyColumnDefs,_fnHungarianMap,_fnCamelToHungarian,_fnLanguageCompat,_fnBrowserDetect,_fnAddData,_fnAddTr,_fnNodeToDataIndex,_fnNodeToColumnIndex,_fnGetCellData,_fnSetCellData,_fnSplitObjNotation,_fnGetObjectDataFn,_fnSetObjectDataFn,_fnGetDataMaster,_fnClearTable,_fnDeleteIndex,_fnInvalidate,_fnGetRowElements,_fnCreateTr,_fnBuildHead,_fnDrawHead,_fnDraw,_fnReDraw,_fnAddOptionsHtml,_fnDetectHeader,_fnGetUniqueThs,_fnFeatureHtmlFilter,_fnFilterComplete,_fnFilterCustom,_fnFilterColumn,_fnFilter,_fnFilterCreateSearch,_fnEscapeRegex,_fnFilterData,_fnFeatureHtmlInfo,_fnUpdateInfo,_fnInfoMacros,_fnInitialise,_fnInitComplete,_fnLengthChange,_fnFeatureHtmlLength,_fnFeatureHtmlPaginate,_fnPageChange,_fnFeatureHtmlProcessing,_fnProcessingDisplay,_fnFeatureHtmlTable,_fnScrollDraw,_fnApplyToChildren,_fnCalculateColumnWidths,_fnThrottle,_fnConvertToWidth,_fnScrollingWidthAdjust,_fnGetWidestNode,_fnGetMaxLenString,_fnStringToCss,_fnScrollBarWidth,_fnSortFlatten,_fnSort,_fnSortAria,_fnSortListener,_fnSortAttachListener,_fnSortingClasses,_fnSortData,_fnSaveState,_fnLoadState,_fnSettingsFromNode,_fnLog,_fnMap,_fnBindAction,_fnCallbackReg,_fnCallbackFire,_fnLengthOverflow,_fnRenderer,_fnDataSource,_fnRowAttributes*/ (/** @lends */function( window, document, undefined ) { @@ -210,7 +210,9 @@ // is essential here if ( prop2 !== undefined ) { for ( ; i= end) + if ( start >= end ) { start = end - len; } + // Keep the start record on the current page + start -= (start % len); + if ( len === -1 || start < 0 ) { start = 0; @@ -6218,26 +6266,31 @@ } /* Language definitions */ - if ( oInit.oLanguage.sUrl !== "" ) + var oLanguage = oSettings.oLanguage; + $.extend( true, oLanguage, oInit.oLanguage ); + + if ( oLanguage.sUrl !== "" ) { /* Get the language definitions from a file - because this Ajax call makes the language * get async to the remainder of this function we use bInitHandedOff to indicate that * _fnInitialise will be fired by the returned Ajax handler, rather than the constructor */ - oSettings.oLanguage.sUrl = oInit.oLanguage.sUrl; - $.getJSON( oSettings.oLanguage.sUrl, null, function( json ) { - _fnLanguageCompat( json ); - _fnCamelToHungarian( defaults.oLanguage, json ); - $.extend( true, oSettings.oLanguage, oInit.oLanguage, json ); - _fnInitialise( oSettings ); + $.ajax( { + dataType: 'json', + url: oLanguage.sUrl, + success: function ( json ) { + _fnLanguageCompat( json ); + _fnCamelToHungarian( defaults.oLanguage, json ); + $.extend( true, oLanguage, json ); + _fnInitialise( oSettings ); + }, + error: function () { + // Error occurred loading language file, continue on as best we can + _fnInitialise( oSettings ); + } } ); bInitHandedOff = true; } - else - { - $.extend( true, oSettings.oLanguage, oInit.oLanguage ); - } - /* * Stripes @@ -6750,8 +6803,8 @@ return -1; }, - // Internal only at the moment - relax? - iterator: function ( flatten, type, fn ) { + // Note that `alwaysNew` is internal - use iteratorNew externally + iterator: function ( flatten, type, fn, alwaysNew ) { var a = [], ret, i, ien, j, jen, @@ -6761,6 +6814,7 @@ // Argument shifting if ( typeof flatten === 'string' ) { + alwaysNew = fn; fn = type; type = flatten; flatten = false; @@ -6810,7 +6864,7 @@ } } - if ( a.length ) { + if ( a.length || alwaysNew ) { var api = new _Api( context, flatten ? a.concat.apply( [], a ) : a ); var apiSelector = api.selector; apiSelector.rows = selector.rows; @@ -7137,35 +7191,35 @@ _api_registerPlural( 'tables().nodes()', 'table().node()' , function () { return this.iterator( 'table', function ( ctx ) { return ctx.nTable; - } ); + }, 1 ); } ); _api_registerPlural( 'tables().body()', 'table().body()' , function () { return this.iterator( 'table', function ( ctx ) { return ctx.nTBody; - } ); + }, 1 ); } ); _api_registerPlural( 'tables().header()', 'table().header()' , function () { return this.iterator( 'table', function ( ctx ) { return ctx.nTHead; - } ); + }, 1 ); } ); _api_registerPlural( 'tables().footer()', 'table().footer()' , function () { return this.iterator( 'table', function ( ctx ) { return ctx.nTFoot; - } ); + }, 1 ); } ); _api_registerPlural( 'tables().containers()', 'table().container()' , function () { return this.iterator( 'table', function ( ctx ) { return ctx.nTableWrapper; - } ); + }, 1 ); } ); @@ -7600,9 +7654,6 @@ return rows; } - // Get nodes in the order from the `rows` array - var nodes = _pluck_order( settings.aoData, rows, 'nTr' ); - // Selector - function if ( typeof sel === 'function' ) { return $.map( rows, function (idx) { @@ -7611,11 +7662,16 @@ } ); } + // Get nodes in the order from the `rows` array with null values removed + var nodes = _removeEmpty( + _pluck_order( settings.aoData, rows, 'nTr' ) + ); + // Selector - node if ( sel.nodeName ) { if ( $.inArray( sel, nodes ) !== -1 ) { - return [ sel._DT_RowIndex ];// sel is a TR node that is in the table - // and DataTables adds a prop for fast lookup + return [ sel._DT_RowIndex ]; // sel is a TR node that is in the table + // and DataTables adds a prop for fast lookup } } @@ -7649,7 +7705,7 @@ var inst = this.iterator( 'table', function ( settings ) { return __row_selector( settings, selector, opts ); - } ); + }, 1 ); // Want argument shifting here and in __row_selector? inst.selector.rows = selector; @@ -7662,32 +7718,32 @@ _api_register( 'rows().nodes()', function () { return this.iterator( 'row', function ( settings, row ) { return settings.aoData[ row ].nTr || undefined; - } ); + }, 1 ); } ); _api_register( 'rows().data()', function () { return this.iterator( true, 'rows', function ( settings, rows ) { return _pluck_order( settings.aoData, rows, '_aData' ); - } ); + }, 1 ); } ); _api_registerPlural( 'rows().cache()', 'row().cache()', function ( type ) { return this.iterator( 'row', function ( settings, row ) { var r = settings.aoData[ row ]; return type === 'search' ? r._aFilterData : r._aSortData; - } ); + }, 1 ); } ); _api_registerPlural( 'rows().invalidate()', 'row().invalidate()', function ( src ) { return this.iterator( 'row', function ( settings, row ) { - _fnInvalidateRow( settings, row, src ); + _fnInvalidate( settings, row, src ); } ); } ); _api_registerPlural( 'rows().indexes()', 'row().index()', function () { return this.iterator( 'row', function ( settings, row ) { return row; - } ); + }, 1 ); } ); _api_registerPlural( 'rows().remove()', 'row().remove()', function () { @@ -7736,7 +7792,7 @@ } return out; - } ); + }, 1 ); // Return an Api.rows() extended instance, so rows().nodes() etc can be used var modRows = this.rows( -1 ); @@ -7772,7 +7828,7 @@ ctx[0].aoData[ this[0] ]._aData = data; // Automatically invalidate - _fnInvalidateRow( ctx[0], this[0], 'data' ); + _fnInvalidate( ctx[0], this[0], 'data' ); return this; } ); @@ -8207,7 +8263,7 @@ var inst = this.iterator( 'table', function ( settings ) { return __column_selector( settings, selector, opts ); - } ); + }, 1 ); // Want argument shifting here and in _row_selector? inst.selector.cols = selector; @@ -8223,7 +8279,7 @@ _api_registerPlural( 'columns().header()', 'column().header()', function ( selector, opts ) { return this.iterator( 'column', function ( settings, column ) { return settings.aoColumns[column].nTh; - } ); + }, 1 ); } ); @@ -8233,7 +8289,7 @@ _api_registerPlural( 'columns().footer()', 'column().footer()', function ( selector, opts ) { return this.iterator( 'column', function ( settings, column ) { return settings.aoColumns[column].nTf; - } ); + }, 1 ); } ); @@ -8241,14 +8297,14 @@ * */ _api_registerPlural( 'columns().data()', 'column().data()', function () { - return this.iterator( 'column-rows', __columnData ); + return this.iterator( 'column-rows', __columnData, 1 ); } ); _api_registerPlural( 'columns().dataSrc()', 'column().dataSrc()', function () { return this.iterator( 'column', function ( settings, column ) { return settings.aoColumns[column].mData; - } ); + }, 1 ); } ); @@ -8257,23 +8313,24 @@ return _pluck_order( settings.aoData, rows, type === 'search' ? '_aFilterData' : '_aSortData', column ); - } ); + }, 1 ); } ); _api_registerPlural( 'columns().nodes()', 'column().nodes()', function () { return this.iterator( 'column-rows', function ( settings, column, i, j, rows ) { return _pluck_order( settings.aoData, rows, 'anCells', column ) ; - } ); + }, 1 ); } ); _api_registerPlural( 'columns().visible()', 'column().visible()', function ( vis, calc ) { return this.iterator( 'column', function ( settings, column ) { - return vis === undefined ? - settings.aoColumns[ column ].bVisible : - __setColumnVis( settings, column, vis, calc ); + if ( vis === undefined ) { + return settings.aoColumns[ column ].bVisible; + } // else + __setColumnVis( settings, column, vis, calc ); } ); } ); @@ -8284,7 +8341,7 @@ return type === 'visible' ? _fnColumnIndexToVisible( settings, column ) : column; - } ); + }, 1 ); } ); @@ -8304,7 +8361,7 @@ _api_register( 'columns.adjust()', function () { return this.iterator( 'table', function ( settings ) { _fnAdjustColumnSizing( settings ); - } ); + }, 1 ); } ); @@ -8334,7 +8391,7 @@ { var data = settings.aoData; var rows = _selector_row_indexes( settings, opts ); - var cells = _pluck_order( data, rows, 'anCells' ); + var cells = _removeEmpty( _pluck_order( data, rows, 'anCells' ) ); var allCells = $( [].concat.apply([], cells) ); var row; var columns = settings.aoColumns.length; @@ -8440,7 +8497,7 @@ } return a; - } ); + }, 1 ); $.extend( cells.selector, { cols: columnSelector, @@ -8454,15 +8511,18 @@ _api_registerPlural( 'cells().nodes()', 'cell().node()', function () { return this.iterator( 'cell', function ( settings, row, column ) { - return settings.aoData[ row ].anCells[ column ]; - } ); + var cells = settings.aoData[ row ].anCells; + return cells ? + cells[ column ] : + undefined; + }, 1 ); } ); _api_register( 'cells().data()', function () { return this.iterator( 'cell', function ( settings, row, column ) { return _fnGetCellData( settings, row, column ); - } ); + }, 1 ); } ); @@ -8471,14 +8531,14 @@ return this.iterator( 'cell', function ( settings, row, column ) { return settings.aoData[ row ][ type ][ column ]; - } ); + }, 1 ); } ); _api_registerPlural( 'cells().render()', 'cell().render()', function ( type ) { return this.iterator( 'cell', function ( settings, row, column ) { return _fnGetCellData( settings, row, column, type ); - } ); + }, 1 ); } ); @@ -8489,33 +8549,23 @@ column: column, columnVisible: _fnColumnIndexToVisible( settings, column ) }; + }, 1 ); + } ); + + + _api_registerPlural( 'cells().invalidate()', 'cell().invalidate()', function ( src ) { + return this.iterator( 'cell', function ( settings, row, column ) { + _fnInvalidate( settings, row, src, column ); } ); } ); - _api_register( [ - 'cells().invalidate()', - 'cell().invalidate()' - ], function ( src ) { - var selector = this.selector; - - // Use the rows method of the instance to perform the invalidation, rather - // than doing it here. This avoids needing to handle duplicate rows from - // the cells. - this.rows( selector.rows, selector.opts ).invalidate( src ); - - return this; - } ); - - - _api_register( 'cell()', function ( rowSelector, columnSelector, opts ) { return _selector_first( this.cells( rowSelector, columnSelector, opts ) ); } ); - _api_register( 'cell().data()', function ( data ) { var ctx = this.context; var cell = this[0]; @@ -8529,7 +8579,7 @@ // Set _fnSetCellData( ctx[0], cell[0].row, cell[0].column, data ); - _fnInvalidateRow( ctx[0], cell[0].row, 'data', cell[0].column ); + _fnInvalidate( ctx[0], cell[0].row, 'data', cell[0].column ); return this; } ); @@ -8803,7 +8853,7 @@ */ DataTable.tables = DataTable.fnTables = function ( visible ) { - return jQuery.map( DataTable.settings, function (o) { + return $.map( DataTable.settings, function (o) { if ( !visible || (visible && $(o.nTable).is(':visible')) ) { return o.nTable; } @@ -8830,7 +8880,16 @@ * @param {integer} freq Call frequency in mS * @return {function} Wrapped function */ - throttle: _fnThrottle + throttle: _fnThrottle, + + + /** + * Escape a string such that it can be used in a regular expression + * + * @param {string} sVal string to escape + * @returns {string} escaped string + */ + escapeRegex: _fnEscapeRegex }; @@ -9013,7 +9072,7 @@ * @type string * @default Version number */ - DataTable.version = "1.10.3"; + DataTable.version = "1.10.4"; /** * Private data store, containing all of the settings objects that are @@ -14144,110 +14203,6 @@ - var __numericReplace = function ( d, decimalPlace, re1, re2 ) { - if ( d !== 0 && (!d || d === '-') ) { - return -Infinity; - } - - // If a decimal place other than `.` is used, it needs to be given to the - // function so we can detect it and replace with a `.` which is the only - // decimal place Javascript recognises - it is not locale aware. - if ( decimalPlace ) { - d = _numToDecimal( d, decimalPlace ); - } - - if ( d.replace ) { - if ( re1 ) { - d = d.replace( re1, '' ); - } - - if ( re2 ) { - d = d.replace( re2, '' ); - } - } - - return d * 1; - }; - - - // Add the numeric 'deformatting' functions for sorting. This is done in a - // function to provide an easy ability for the language options to add - // additional methods if a non-period decimal place is used. - function _addNumericSort ( decimalPlace ) { - $.each( - { - // Plain numbers - "num": function ( d ) { - return __numericReplace( d, decimalPlace ); - }, - - // Formatted numbers - "num-fmt": function ( d ) { - return __numericReplace( d, decimalPlace, _re_formatted_numeric ); - }, - - // HTML numeric - "html-num": function ( d ) { - return __numericReplace( d, decimalPlace, _re_html ); - }, - - // HTML numeric, formatted - "html-num-fmt": function ( d ) { - return __numericReplace( d, decimalPlace, _re_html, _re_formatted_numeric ); - } - }, - function ( key, fn ) { - _ext.type.order[ key+decimalPlace+'-pre' ] = fn; - } - ); - } - - - // Default sort methods - $.extend( _ext.type.order, { - // Dates - "date-pre": function ( d ) { - return Date.parse( d ) || 0; - }, - - // html - "html-pre": function ( a ) { - return _empty(a) ? - '' : - a.replace ? - a.replace( /<.*?>/g, "" ).toLowerCase() : - a+''; - }, - - // string - "string-pre": function ( a ) { - // This is a little complex, but faster than always calling toString, - // http://jsperf.com/tostring-v-check - return _empty(a) ? - '' : - typeof a === 'string' ? - a.toLowerCase() : - ! a.toString ? - '' : - a.toString(); - }, - - // string-asc and -desc are retained only for compatibility with the old - // sort methods - "string-asc": function ( x, y ) { - return ((x < y) ? -1 : ((x > y) ? 1 : 0)); - }, - - "string-desc": function ( x, y ) { - return ((x < y) ? 1 : ((x > y) ? -1 : 0)); - } - } ); - - - // Numeric sorting types - order doesn't matter here - _addNumericSort( '' ); - - // Built in type detection. See model.ext.aTypes for information about // what is required from this methods. $.extend( DataTable.ext.type.detect, [ @@ -14305,6 +14260,10 @@ // Filter formatting functions. See model.ext.ofnSearch for information about // what is required from these methods. + // + // Note that additional search methods are added for the html numbers and + // html formatted numbers by `_addNumericSort()` when we know what the decimal + // place is $.extend( DataTable.ext.type.search, { @@ -14329,6 +14288,116 @@ + var __numericReplace = function ( d, decimalPlace, re1, re2 ) { + if ( d !== 0 && (!d || d === '-') ) { + return -Infinity; + } + + // If a decimal place other than `.` is used, it needs to be given to the + // function so we can detect it and replace with a `.` which is the only + // decimal place Javascript recognises - it is not locale aware. + if ( decimalPlace ) { + d = _numToDecimal( d, decimalPlace ); + } + + if ( d.replace ) { + if ( re1 ) { + d = d.replace( re1, '' ); + } + + if ( re2 ) { + d = d.replace( re2, '' ); + } + } + + return d * 1; + }; + + + // Add the numeric 'deformatting' functions for sorting and search. This is done + // in a function to provide an easy ability for the language options to add + // additional methods if a non-period decimal place is used. + function _addNumericSort ( decimalPlace ) { + $.each( + { + // Plain numbers + "num": function ( d ) { + return __numericReplace( d, decimalPlace ); + }, + + // Formatted numbers + "num-fmt": function ( d ) { + return __numericReplace( d, decimalPlace, _re_formatted_numeric ); + }, + + // HTML numeric + "html-num": function ( d ) { + return __numericReplace( d, decimalPlace, _re_html ); + }, + + // HTML numeric, formatted + "html-num-fmt": function ( d ) { + return __numericReplace( d, decimalPlace, _re_html, _re_formatted_numeric ); + } + }, + function ( key, fn ) { + // Add the ordering method + _ext.type.order[ key+decimalPlace+'-pre' ] = fn; + + // For HTML types add a search formatter that will strip the HTML + if ( key.match(/^html\-/) ) { + _ext.type.search[ key+decimalPlace ] = _ext.type.search.html; + } + } + ); + } + + + // Default sort methods + $.extend( _ext.type.order, { + // Dates + "date-pre": function ( d ) { + return Date.parse( d ) || 0; + }, + + // html + "html-pre": function ( a ) { + return _empty(a) ? + '' : + a.replace ? + a.replace( /<.*?>/g, "" ).toLowerCase() : + a+''; + }, + + // string + "string-pre": function ( a ) { + // This is a little complex, but faster than always calling toString, + // http://jsperf.com/tostring-v-check + return _empty(a) ? + '' : + typeof a === 'string' ? + a.toLowerCase() : + ! a.toString ? + '' : + a.toString(); + }, + + // string-asc and -desc are retained only for compatibility with the old + // sort methods + "string-asc": function ( x, y ) { + return ((x < y) ? -1 : ((x > y) ? 1 : 0)); + }, + + "string-desc": function ( x, y ) { + return ((x < y) ? 1 : ((x > y) ? -1 : 0)); + } + } ); + + + // Numeric sorting types - order doesn't matter here + _addNumericSort( '' ); + + $.extend( true, DataTable.ext.renderer, { header: { _: function ( settings, cell, column, classes ) { @@ -14517,7 +14586,7 @@ _fnGetDataMaster: _fnGetDataMaster, _fnClearTable: _fnClearTable, _fnDeleteIndex: _fnDeleteIndex, - _fnInvalidateRow: _fnInvalidateRow, + _fnInvalidate: _fnInvalidate, _fnGetRowElements: _fnGetRowElements, _fnCreateTr: _fnCreateTr, _fnBuildHead: _fnBuildHead, diff --git a/src/sunstone/public/bower_components/datatables/media/js/jquery.dataTables.min.js b/src/sunstone/public/bower_components/datatables/media/js/jquery.dataTables.min.js index 9b72ba30c0..8885017c35 100644 --- a/src/sunstone/public/bower_components/datatables/media/js/jquery.dataTables.min.js +++ b/src/sunstone/public/bower_components/datatables/media/js/jquery.dataTables.min.js @@ -1,157 +1,157 @@ -/*! DataTables 1.10.3 +/*! DataTables 1.10.4 * ©2008-2014 SpryMedia Ltd - datatables.net/license */ -(function(Da,P,l){var O=function(h){function V(a){var b,c,d={};h.each(a,function(e){if((b=e.match(/^([^A-Z]+?)([A-Z])/))&&-1!=="a aa ai ao as b fn i m o s ".indexOf(b[1]+" "))c=e.replace(b[0],b[2].toLowerCase()),d[c]=e,"o"===b[1]&&V(a[e])});a._hungarianMap=d}function G(a,b,c){a._hungarianMap||V(a);var d;h.each(b,function(e){d=a._hungarianMap[e];if(d!==l&&(c||b[d]===l))"o"===d.charAt(0)?(b[d]||(b[d]={}),h.extend(!0,b[d],b[e]),G(a[d],b[d],c)):b[d]=b[e]})}function O(a){var b=p.defaults.oLanguage,c=a.sZeroRecords; -!a.sEmptyTable&&(c&&"No data available in table"===b.sEmptyTable)&&D(a,a,"sZeroRecords","sEmptyTable");!a.sLoadingRecords&&(c&&"Loading..."===b.sLoadingRecords)&&D(a,a,"sZeroRecords","sLoadingRecords");a.sInfoThousands&&(a.sThousands=a.sInfoThousands);(a=a.sDecimal)&&db(a)}function eb(a){z(a,"ordering","bSort");z(a,"orderMulti","bSortMulti");z(a,"orderClasses","bSortClasses");z(a,"orderCellsTop","bSortCellsTop");z(a,"order","aaSorting");z(a,"orderFixed","aaSortingFixed");z(a,"paging","bPaginate"); -z(a,"pagingType","sPaginationType");z(a,"pageLength","iDisplayLength");z(a,"searching","bFilter");if(a=a.aoSearchCols)for(var b=0,c=a.length;b").css({position:"absolute",top:0,left:0,height:1,width:1,overflow:"hidden"}).append(h("
").css({position:"absolute",top:1,left:1,width:100, -overflow:"scroll"}).append(h('
').css({width:"100%",height:10}))).appendTo("body"),c=b.find(".test");a.bScrollOversize=100===c[0].offsetWidth;a.bScrollbarLeft=1!==c.offset().left;b.remove()}function hb(a,b,c,d,e,f){var g,i=!1;c!==l&&(g=c,i=!0);for(;d!==e;)a.hasOwnProperty(d)&&(g=i?b(g,a[d],d,a):a[d],i=!0,d+=f);return g}function Ea(a,b){var c=p.defaults.column,d=a.aoColumns.length,c=h.extend({},p.models.oColumn,c,{nTh:b?b:P.createElement("th"),sTitle:c.sTitle?c.sTitle:b?b.innerHTML: -"",aDataSort:c.aDataSort?c.aDataSort:[d],mData:c.mData?c.mData:d,idx:d});a.aoColumns.push(c);c=a.aoPreSearchCols;c[d]=h.extend({},p.models.oSearch,c[d]);ia(a,d,null)}function ia(a,b,c){var b=a.aoColumns[b],d=a.oClasses,e=h(b.nTh);if(!b.sWidthOrig){b.sWidthOrig=e.attr("width")||null;var f=(e.attr("style")||"").match(/width:\s*(\d+[pxem%]+)/);f&&(b.sWidthOrig=f[1])}c!==l&&null!==c&&(fb(c),G(p.defaults.column,c),c.mDataProp!==l&&!c.mData&&(c.mData=c.mDataProp),c.sType&&(b._sManualType=c.sType),c.className&& -!c.sClass&&(c.sClass=c.className),h.extend(b,c),D(b,c,"sWidth","sWidthOrig"),"number"===typeof c.iDataSort&&(b.aDataSort=[c.iDataSort]),D(b,c,"aDataSort"));var g=b.mData,i=W(g),j=b.mRender?W(b.mRender):null,c=function(a){return"string"===typeof a&&-1!==a.indexOf("@")};b._bAttrSrc=h.isPlainObject(g)&&(c(g.sort)||c(g.type)||c(g.filter));b.fnGetData=function(a,b,c){var d=i(a,b,l,c);return j&&b?j(d,b,a,c):d};b.fnSetData=function(a,b,c){return Q(g)(a,b,c)};"number"!==typeof g&&(a._rowReadObject=!0);a.oFeatures.bSort|| -(b.bSortable=!1,e.addClass(d.sSortableNone));a=-1!==h.inArray("asc",b.asSorting);c=-1!==h.inArray("desc",b.asSorting);!b.bSortable||!a&&!c?(b.sSortingClass=d.sSortableNone,b.sSortingClassJUI=""):a&&!c?(b.sSortingClass=d.sSortableAsc,b.sSortingClassJUI=d.sSortJUIAscAllowed):!a&&c?(b.sSortingClass=d.sSortableDesc,b.sSortingClassJUI=d.sSortJUIDescAllowed):(b.sSortingClass=d.sSortable,b.sSortingClassJUI=d.sSortJUI)}function X(a){if(!1!==a.oFeatures.bAutoWidth){var b=a.aoColumns;Fa(a);for(var c=0,d=b.length;c< -d;c++)b[c].nTh.style.width=b[c].sWidth}b=a.oScroll;(""!==b.sY||""!==b.sX)&&Y(a);u(a,null,"column-sizing",[a])}function ja(a,b){var c=Z(a,"bVisible");return"number"===typeof c[b]?c[b]:null}function $(a,b){var c=Z(a,"bVisible"),c=h.inArray(b,c);return-1!==c?c:null}function aa(a){return Z(a,"bVisible").length}function Z(a,b){var c=[];h.map(a.aoColumns,function(a,e){a[b]&&c.push(e)});return c}function Ga(a){var b=a.aoColumns,c=a.aoData,d=p.ext.type.detect,e,f,g,i,j,h,m,o,k;e=0;for(f=b.length;eo[f])d(m.length+o[f],n);else if("string"===typeof o[f]){i=0;for(j=m.length;ib&&a[e]--; -1!=d&&c===l&& -a.splice(d,1)}function oa(a,b,c,d){var e=a.aoData[b],f;if("dom"===c||(!c||"auto"===c)&&"dom"===e.src)e._aData=la(a,e).data;else{var g=e.anCells,i;if(g){c=0;for(f=g.length;c").appendTo(g));b=0;for(c=m.length;btr").attr("role","row");h(g).find(">tr>th, >tr>td").addClass(n.sHeaderTH);h(i).find(">tr>th, >tr>td").addClass(n.sFooterTH);if(null!==i){a=a.aoFooter[0];b=0;for(c=a.length;b=a.fnRecordsDisplay()?0:g,a.iInitDisplayStart=-1);var g=a._iDisplayStart,n=a.fnDisplayEnd();if(a.bDeferLoading)a.bDeferLoading=!1,a.iDraw++,B(a,!1);else if(i){if(!a.bDestroying&& -!kb(a))return}else a.iDraw++;if(0!==j.length){f=i?a.aoData.length:n;for(i=i?0:g;i",{"class":e?d[0]:""}).append(h("",{valign:"top",colSpan:aa(a), -"class":a.oClasses.sRowEmpty}).html(c))[0];u(a,"aoHeaderCallback","header",[h(a.nTHead).children("tr")[0],Ka(a),g,n,j]);u(a,"aoFooterCallback","footer",[h(a.nTFoot).children("tr")[0],Ka(a),g,n,j]);d=h(a.nTBody);d.children().detach();d.append(h(b));u(a,"aoDrawCallback","draw",[a]);a.bSorted=!1;a.bFiltered=!1;a.bDrawing=!1}}function M(a,b){var c=a.oFeatures,d=c.bFilter;c.bSort&&lb(a);d?ea(a,a.oPreviousSearch):a.aiDisplay=a.aiDisplayMaster.slice();!0!==b&&(a._iDisplayStart=0);a._drawHold=b;L(a);a._drawHold= -!1}function mb(a){var b=a.oClasses,c=h(a.nTable),c=h("
").insertBefore(c),d=a.oFeatures,e=h("
",{id:a.sTableId+"_wrapper","class":b.sWrapper+(a.nTFoot?"":" "+b.sNoFooter)});a.nHolding=c[0];a.nTableWrapper=e[0];a.nTableReinsertBefore=a.nTable.nextSibling;for(var f=a.sDom.split(""),g,i,j,n,m,o,k=0;k")[0];n=f[k+1];if("'"==n||'"'==n){m="";for(o=2;f[k+o]!=n;)m+=f[k+o],o++;"H"==m?m=b.sJUIHeader:"F"==m&&(m=b.sJUIFooter);-1!=m.indexOf(".")?(n=m.split("."), -j.id=n[0].substr(1,n[0].length-1),j.className=n[1]):"#"==m.charAt(0)?j.id=m.substr(1,m.length-1):j.className=m;k+=o}e.append(j);e=h(j)}else if(">"==i)e=e.parent();else if("l"==i&&d.bPaginate&&d.bLengthChange)g=nb(a);else if("f"==i&&d.bFilter)g=ob(a);else if("r"==i&&d.bProcessing)g=pb(a);else if("t"==i)g=qb(a);else if("i"==i&&d.bInfo)g=rb(a);else if("p"==i&&d.bPaginate)g=sb(a);else if(0!==p.ext.feature.length){j=p.ext.feature;o=0;for(n=j.length;o',i=d.sSearch,i=i.match(/_INPUT_/)?i.replace("_INPUT_",g):i+g,b=h("
",{id:!f.f?c+"_filter":null,"class":b.sFilter}).append(h("
").addClass(b.sLength);a.aanFeatures.l||(j[0].id=c+"_length");j.children().append(a.oLanguage.sLengthMenu.replace("_MENU_",e[0].outerHTML));h("select",j).val(a._iDisplayLength).bind("change.DT",function(){Ra(a,h(this).val());L(a)});h(a.nTable).bind("length.dt.DT",function(b,c,d){a===c&&h("select",j).val(d)});return j[0]}function sb(a){var b= -a.sPaginationType,c=p.ext.pager[b],d="function"===typeof c,e=function(a){L(a)},b=h("
").addClass(a.oClasses.sPaging+b)[0],f=a.aanFeatures;d||c.fnInit(a,b,e);f.p||(b.id=a.sTableId+"_paginate",a.aoDrawCallback.push({fn:function(a){if(d){var b=a._iDisplayStart,j=a._iDisplayLength,h=a.fnRecordsDisplay(),m=-1===j,b=m?0:Math.ceil(b/j),j=m?1:Math.ceil(h/j),h=c(b,j),o,m=0;for(o=f.p.length;mf&&(d=0)):"first"==b?d=0:"previous"==b?(d=0<=e?d-e:0,0>d&&(d=0)):"next"==b?d+e",{id:!a.aanFeatures.r?a.sTableId+"_processing":null,"class":a.oClasses.sProcessing}).html(a.oLanguage.sProcessing).insertBefore(a.nTable)[0]} -function B(a,b){a.oFeatures.bProcessing&&h(a.aanFeatures.r).css("display",b?"block":"none");u(a,null,"processing",[a,b])}function qb(a){var b=h(a.nTable);b.attr("role","grid");var c=a.oScroll;if(""===c.sX&&""===c.sY)return a.nTable;var d=c.sX,e=c.sY,f=a.oClasses,g=b.children("caption"),i=g.length?g[0]._captionSide:null,j=h(b[0].cloneNode(!1)),n=h(b[0].cloneNode(!1)),m=b.children("tfoot");c.sX&&"100%"===b.attr("width")&&b.removeAttr("width");m.length||(m=null);c=h("
",{"class":f.sScrollWrapper}).append(h("
", -{"class":f.sScrollHead}).css({overflow:"hidden",position:"relative",border:0,width:d?!d?null:s(d):"100%"}).append(h("
",{"class":f.sScrollHeadInner}).css({"box-sizing":"content-box",width:c.sXInner||"100%"}).append(j.removeAttr("id").css("margin-left",0).append(b.children("thead")))).append("top"===i?g:null)).append(h("
",{"class":f.sScrollBody}).css({overflow:"auto",height:!e?null:s(e),width:!d?null:s(d)}).append(b));m&&c.append(h("
",{"class":f.sScrollFoot}).css({overflow:"hidden", -border:0,width:d?!d?null:s(d):"100%"}).append(h("
",{"class":f.sScrollFootInner}).append(n.removeAttr("id").css("margin-left",0).append(b.children("tfoot")))).append("bottom"===i?g:null));var b=c.children(),o=b[0],f=b[1],k=m?b[2]:null;d&&h(f).scroll(function(){var a=this.scrollLeft;o.scrollLeft=a;m&&(k.scrollLeft=a)});a.nScrollHead=o;a.nScrollBody=f;a.nScrollFoot=k;a.aoDrawCallback.push({fn:Y,sName:"scrolling"});return c[0]}function Y(a){var b=a.oScroll,c=b.sX,d=b.sXInner,e=b.sY,f=b.iBarWidth, -g=h(a.nScrollHead),i=g[0].style,j=g.children("div"),n=j[0].style,m=j.children("table"),j=a.nScrollBody,o=h(j),k=j.style,l=h(a.nScrollFoot).children("div"),p=l.children("table"),r=h(a.nTHead),q=h(a.nTable),fa=q[0],N=fa.style,J=a.nTFoot?h(a.nTFoot):null,t=a.oBrowser,u=t.bScrollOversize,x,v,w,K,y,z=[],A=[],B=[],C,D=function(a){a=a.style;a.paddingTop="0";a.paddingBottom="0";a.borderTopWidth="0";a.borderBottomWidth="0";a.height=0};q.children("thead, tfoot").remove();y=r.clone().prependTo(q);x=r.find("tr"); -w=y.find("tr");y.find("th, td").removeAttr("tabindex");J&&(K=J.clone().prependTo(q),v=J.find("tr"),K=K.find("tr"));c||(k.width="100%",g[0].style.width="100%");h.each(pa(a,y),function(b,c){C=ja(a,b);c.style.width=a.aoColumns[C].sWidth});J&&F(function(a){a.style.width=""},K);b.bCollapse&&""!==e&&(k.height=o[0].offsetHeight+r[0].offsetHeight+"px");g=q.outerWidth();if(""===c){if(N.width="100%",u&&(q.find("tbody").height()>j.offsetHeight||"scroll"==o.css("overflow-y")))N.width=s(q.outerWidth()-f)}else""!== -d?N.width=s(d):g==o.width()&&o.height()g-f&&(N.width=s(g))):N.width=s(g);g=q.outerWidth();F(D,w);F(function(a){B.push(a.innerHTML);z.push(s(h(a).css("width")))},w);F(function(a,b){a.style.width=z[b]},x);h(w).height(0);J&&(F(D,K),F(function(a){A.push(s(h(a).css("width")))},K),F(function(a,b){a.style.width=A[b]},v),h(K).height(0));F(function(a,b){a.innerHTML='
'+B[b]+"
";a.style.width=z[b]}, -w);J&&F(function(a,b){a.innerHTML="";a.style.width=A[b]},K);if(q.outerWidth()j.offsetHeight||"scroll"==o.css("overflow-y")?g+f:g;if(u&&(j.scrollHeight>j.offsetHeight||"scroll"==o.css("overflow-y")))N.width=s(v-f);(""===c||""!==d)&&R(a,1,"Possible column misalignment",6)}else v="100%";k.width=s(v);i.width=s(v);J&&(a.nScrollFoot.style.width=s(v));!e&&u&&(k.height=s(fa.offsetHeight+f));e&&b.bCollapse&&(k.height=s(e),b=c&&fa.offsetWidth>j.offsetWidth?f:0,fa.offsetHeightj.clientHeight||"scroll"==o.css("overflow-y");t="padding"+(t.bScrollbarLeft?"Left":"Right");n[t]=m?f+"px":"0px";J&&(p[0].style.width=s(b),l[0].style.width=s(b),l[0].style[t]=m?f+"px":"0px");o.scroll();if((a.bSorted||a.bFiltered)&&!a._drawHold)j.scrollTop=0}function F(a,b,c){for(var d=0,e=0,f=b.length,g,i;e")); -i.find("tfoot th, tfoot td").css("width","");var p=i.find("tbody tr"),j=pa(a,i.find("thead")[0]);for(k=0;k").css("width",s(a)).appendTo(b||P.body),d=c[0].offsetWidth;c.remove();return d}function Fb(a,b){var c=a.oScroll;if(c.sX||c.sY)c=!c.sX?c.iBarWidth:0,b.style.width=s(h(b).outerWidth()-c)}function Eb(a,b){var c=Gb(a,b);if(0>c)return null;var d=a.aoData[c];return!d.nTr?h("").html(w(a,c,b,"display"))[0]:d.anCells[b]}function Gb(a,b){for(var c,d=-1,e=-1,f=0,g=a.aoData.length;fd&&(d=c.length,e=f);return e}function s(a){return null===a?"0px":"number"==typeof a?0>a?"0px":a+"px":a.match(/\d$/)?a+"px":a}function Hb(){if(!p.__scrollbarWidth){var a=h("

").css({width:"100%",height:200,padding:0})[0],b=h("

").css({position:"absolute",top:0,left:0,width:200,height:150,padding:0,overflow:"hidden",visibility:"hidden"}).append(a).appendTo("body"),c=a.offsetWidth;b.css("overflow","scroll");a=a.offsetWidth;c===a&&(a=b[0].clientWidth);b.remove();p.__scrollbarWidth= -c-a}return p.__scrollbarWidth}function T(a){var b,c,d=[],e=a.aoColumns,f,g,i,j;b=a.aaSortingFixed;c=h.isPlainObject(b);var n=[];f=function(a){a.length&&!h.isArray(a[0])?n.push(a):n.push.apply(n,a)};h.isArray(b)&&f(b);c&&b.pre&&f(b.pre);f(a.aaSorting);c&&b.post&&f(b.post);for(a=0;ae?1:0,0!==c)return"asc"===i.dir?c:-c;c=d[a];e=d[b];return ce?1:0}):h.sort(function(a,b){var c, -g,i,h,j=n.length,l=f[a]._aSortData,p=f[b]._aSortData;for(i=0;ig?1:0})}a.bSorted=!0}function Jb(a){for(var b,c,d=a.aoColumns,e=T(a),a=a.oLanguage.oAria,f=0,g=d.length;f/g,"");var h=c.nTh;h.removeAttribute("aria-sort");c.bSortable&&(0e?e+1:3));e=0;for(f=d.length;ee?e+1:3))}a.aLastSort=d}function Ib(a,b){var c=a.aoColumns[b],d=p.ext.order[c.sSortDataType],e;d&&(e=d.call(a.oInstance,a,b,$(a,b)));for(var f,g=p.ext.type.order[c.sType+"-pre"],i=0,h=a.aoData.length;i=d.length?[0,c[1]]:c)});h.extend(a.oPreviousSearch,Ab(e.search));b=0;for(c=e.columns.length;b=c&&(b=c-d);if(-1===d||0>b)b=0;a._iDisplayStart=b}function Oa(a,b){var c=a.renderer,d=p.ext.renderer[b];return h.isPlainObject(c)&&c[b]?d[c[b]]||d._:"string"===typeof c?d[c]||d._:d._}function A(a){return a.oFeatures.bServerSide?"ssp":a.ajax||a.sAjaxSource?"ajax":"dom"}function Wa(a,b){var c= -[],c=Mb.numbers_length,d=Math.floor(c/2);b<=c?c=U(0,b):a<=d?(c=U(0,c-2),c.push("ellipsis"),c.push(b-1)):(a>=b-1-d?c=U(b-(c-2),b):(c=U(a-1,a+2),c.push("ellipsis"),c.push(b-1)),c.splice(0,0,"ellipsis"),c.splice(0,0,0));c.DT_el="span";return c}function db(a){h.each({num:function(b){return za(b,a)},"num-fmt":function(b){return za(b,a,Xa)},"html-num":function(b){return za(b,a,Aa)},"html-num-fmt":function(b){return za(b,a,Aa,Xa)}},function(b,c){v.type.order[b+a+"-pre"]=c})}function Nb(a){return function(){var b= -[ya(this[p.ext.iApiIndex])].concat(Array.prototype.slice.call(arguments));return p.ext.internal[a].apply(this,b)}}var p,v,q,r,t,Ya={},Ob=/[\r\n]/g,Aa=/<.*?>/g,$b=/^[\w\+\-]/,ac=/[\w\+\-]$/,Xb=RegExp("(\\/|\\.|\\*|\\+|\\?|\\||\\(|\\)|\\[|\\]|\\{|\\}|\\\\|\\$|\\^|\\-)","g"),Xa=/[',$\u00a3\u20ac\u00a5%\u2009\u202F]/g,H=function(a){return!a||!0===a||"-"===a?!0:!1},Pb=function(a){var b=parseInt(a,10);return!isNaN(b)&&isFinite(a)?b:null},Qb=function(a,b){Ya[b]||(Ya[b]=RegExp(Qa(b),"g"));return"string"=== -typeof a&&"."!==b?a.replace(/\./g,"").replace(Ya[b],"."):a},Za=function(a,b,c){var d="string"===typeof a;b&&d&&(a=Qb(a,b));c&&d&&(a=a.replace(Xa,""));return H(a)||!isNaN(parseFloat(a))&&isFinite(a)},Rb=function(a,b,c){return H(a)?!0:!(H(a)||"string"===typeof a)?null:Za(a.replace(Aa,""),b,c)?!0:null},C=function(a,b,c){var d=[],e=0,f=a.length;if(c!==l)for(;e")[0],Yb=ua.textContent!==l,Zb=/<.*?>/g;p=function(a){this.$=function(a,b){return this.api(!0).$(a,b)};this._=function(a, -b){return this.api(!0).rows(a,b).data()};this.api=function(a){return a?new q(ya(this[v.iApiIndex])):new q(this)};this.fnAddData=function(a,b){var c=this.api(!0),d=h.isArray(a)&&(h.isArray(a[0])||h.isPlainObject(a[0]))?c.rows.add(a):c.row.add(a);(b===l||b)&&c.draw();return d.flatten().toArray()};this.fnAdjustColumnSizing=function(a){var b=this.api(!0).columns.adjust(),c=b.settings()[0],d=c.oScroll;a===l||a?b.draw(!1):(""!==d.sX||""!==d.sY)&&Y(c)};this.fnClearTable=function(a){var b=this.api(!0).clear(); -(a===l||a)&&b.draw()};this.fnClose=function(a){this.api(!0).row(a).child.hide()};this.fnDeleteRow=function(a,b,c){var d=this.api(!0),a=d.rows(a),e=a.settings()[0],h=e.aoData[a[0][0]];a.remove();b&&b.call(this,e,h);(c===l||c)&&d.draw();return h};this.fnDestroy=function(a){this.api(!0).destroy(a)};this.fnDraw=function(a){this.api(!0).draw(!a)};this.fnFilter=function(a,b,c,d,e,h){e=this.api(!0);null===b||b===l?e.search(a,c,d,h):e.column(b).search(a,c,d,h);e.draw()};this.fnGetData=function(a,b){var c= -this.api(!0);if(a!==l){var d=a.nodeName?a.nodeName.toLowerCase():"";return b!==l||"td"==d||"th"==d?c.cell(a,b).data():c.row(a).data()||null}return c.data().toArray()};this.fnGetNodes=function(a){var b=this.api(!0);return a!==l?b.row(a).node():b.rows().nodes().flatten().toArray()};this.fnGetPosition=function(a){var b=this.api(!0),c=a.nodeName.toUpperCase();return"TR"==c?b.row(a).index():"TD"==c||"TH"==c?(a=b.cell(a).index(),[a.row,a.columnVisible,a.column]):null};this.fnIsOpen=function(a){return this.api(!0).row(a).child.isShown()}; -this.fnOpen=function(a,b,c){return this.api(!0).row(a).child(b,c).show().child()[0]};this.fnPageChange=function(a,b){var c=this.api(!0).page(a);(b===l||b)&&c.draw(!1)};this.fnSetColumnVis=function(a,b,c){a=this.api(!0).column(a).visible(b);(c===l||c)&&a.columns.adjust().draw()};this.fnSettings=function(){return ya(this[v.iApiIndex])};this.fnSort=function(a){this.api(!0).order(a).draw()};this.fnSortListener=function(a,b,c){this.api(!0).order.listener(a,b,c)};this.fnUpdate=function(a,b,c,d,e){var h= -this.api(!0);c===l||null===c?h.row(b).data(a):h.cell(b,c).data(a);(e===l||e)&&h.columns.adjust();(d===l||d)&&h.draw();return 0};this.fnVersionCheck=v.fnVersionCheck;var b=this,c=a===l,d=this.length;c&&(a={});this.oApi=this.internal=v.internal;for(var e in p.ext.internal)e&&(this[e]=Nb(e));this.each(function(){var e={},g=1t<"F"ip>'),k.renderer)?h.isPlainObject(k.renderer)&&!k.renderer.header&&(k.renderer.header="jqueryui"):k.renderer="jqueryui":h.extend(n,p.ext.classes,g.oClasses);h(this).addClass(n.sTable);if(""!==k.oScroll.sX||""!==k.oScroll.sY)k.oScroll.iBarWidth=Hb();!0===k.oScroll.sX&&(k.oScroll.sX="100%");k.iInitDisplayStart===l&&(k.iInitDisplayStart= -g.iDisplayStart,k._iDisplayStart=g.iDisplayStart);null!==g.iDeferLoading&&(k.bDeferLoading=!0,i=h.isArray(g.iDeferLoading),k._iRecordsDisplay=i?g.iDeferLoading[0]:g.iDeferLoading,k._iRecordsTotal=i?g.iDeferLoading[1]:g.iDeferLoading);""!==g.oLanguage.sUrl?(k.oLanguage.sUrl=g.oLanguage.sUrl,h.getJSON(k.oLanguage.sUrl,null,function(a){O(a);G(m.oLanguage,a);h.extend(true,k.oLanguage,g.oLanguage,a);va(k)}),e=!0):h.extend(!0,k.oLanguage,g.oLanguage);null===g.asStripeClasses&&(k.asStripeClasses=[n.sStripeOdd, -n.sStripeEven]);var i=k.asStripeClasses,r=h("tbody tr:eq(0)",this);-1!==h.inArray(!0,h.map(i,function(a){return r.hasClass(a)}))&&(h("tbody tr",this).removeClass(i.join(" ")),k.asDestroyStripes=i.slice());var o=[],q,i=this.getElementsByTagName("thead");0!==i.length&&(ca(k.aoHeader,i[0]),o=pa(k));if(null===g.aoColumns){q=[];i=0;for(j=o.length;i").appendTo(this));k.nTHead=j[0];j=h(this).children("tbody");0===j.length&&(j=h("").appendTo(this));k.nTBody= -j[0];j=h(this).children("tfoot");if(0===j.length&&0").appendTo(this);0===j.length||0===j.children().length?h(this).addClass(n.sNoFooter):0a?new q(b[a],this[a]):null},filter:function(a){var b=[]; -if(x.filter)b=x.filter.call(this,a,this);else for(var c=0,d=this.length;c").addClass(b);h("td",c).addClass(b).html(a)[0].colSpan=aa(d);e.push(c[0])}};if(h.isArray(a)|| -a instanceof h)for(var g=0,i=a.length;g=0?b:g.length+b];if(typeof a==="function"){var e=Ba(c,f);return h.map(g, -function(b,f){return a(f,Vb(c,f,0,0,e),j[f])?f:null})}var k=typeof a==="string"?a.match(cc):"";if(k)switch(k[2]){case "visIdx":case "visible":b=parseInt(k[1],10);if(b<0){var l=h.map(g,function(a,b){return a.bVisible?b:null});return[l[l.length+b]]}return[ja(c,b)];case "name":return h.map(i,function(a,b){return a===k[1]?b:null})}else return h(j).filter(a).map(function(){return h.inArray(this,j)}).toArray()})});c.selector.cols=a;c.selector.opts=b;return c});t("columns().header()","column().header()", -function(){return this.iterator("column",function(a,b){return a.aoColumns[b].nTh})});t("columns().footer()","column().footer()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].nTf})});t("columns().data()","column().data()",function(){return this.iterator("column-rows",Vb)});t("columns().dataSrc()","column().dataSrc()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].mData})});t("columns().cache()","column().cache()",function(a){return this.iterator("column-rows", -function(b,c,d,e,f){return ga(b.aoData,f,"search"===a?"_aFilterData":"_aSortData",c)})});t("columns().nodes()","column().nodes()",function(){return this.iterator("column-rows",function(a,b,c,d,e){return ga(a.aoData,e,"anCells",b)})});t("columns().visible()","column().visible()",function(a,b){return this.iterator("column",function(c,d){var e;if(a===l)e=c.aoColumns[d].bVisible;else{var f=c.aoColumns;e=f[d];var g=c.aoData,i,j,n;if(a===l)e=e.bVisible;else{if(e.bVisible!==a){if(a){var m=h.inArray(!0,C(f, -"bVisible"),d+1);i=0;for(j=g.length;id;return!0};p.isDataTable=p.fnIsDataTable=function(a){var b=h(a).get(0),c=!1;h.each(p.settings,function(a,e){if(e.nTable===b||e.nScrollHead===b||e.nScrollFoot===b)c=!0}); -return c};p.tables=p.fnTables=function(a){return jQuery.map(p.settings,function(b){if(!a||a&&h(b.nTable).is(":visible"))return b.nTable})};p.util={throttle:ta};p.camelToHungarian=G;r("$()",function(a,b){var c=this.rows(b).nodes(),c=h(c);return h([].concat(c.filter(a).toArray(),c.find(a).toArray()))});h.each(["on","one","off"],function(a,b){r(b+"()",function(){var a=Array.prototype.slice.call(arguments);a[0].match(/\.dt\b/)||(a[0]+=".dt");var d=h(this.tables().nodes());d[b].apply(d,a);return this})}); -r("clear()",function(){return this.iterator("table",function(a){ma(a)})});r("settings()",function(){return new q(this.context,this.context)});r("data()",function(){return this.iterator("table",function(a){return C(a.aoData,"_aData")}).flatten()});r("destroy()",function(a){a=a||!1;return this.iterator("table",function(b){var c=b.nTableWrapper.parentNode,d=b.oClasses,e=b.nTable,f=b.nTBody,g=b.nTHead,i=b.nTFoot,j=h(e),f=h(f),l=h(b.nTableWrapper),m=h.map(b.aoData,function(a){return a.nTr}),o;b.bDestroying= -!0;u(b,"aoDestroyCallback","destroy",[b]);a||(new q(b)).columns().visible(!0);l.unbind(".DT").find(":not(tbody *)").unbind(".DT");h(Da).unbind(".DT-"+b.sInstance);e!=g.parentNode&&(j.children("thead").detach(),j.append(g));i&&e!=i.parentNode&&(j.children("tfoot").detach(),j.append(i));j.detach();l.detach();b.aaSorting=[];b.aaSortingFixed=[];wa(b);h(m).removeClass(b.asStripeClasses.join(" "));h("th, td",g).removeClass(d.sSortable+" "+d.sSortableAsc+" "+d.sSortableDesc+" "+d.sSortableNone);b.bJUI&& -(h("th span."+d.sSortIcon+", td span."+d.sSortIcon,g).detach(),h("th, td",g).each(function(){var a=h("div."+d.sSortJUIWrapper,this);h(this).append(a.contents());a.detach()}));!a&&c&&c.insertBefore(e,b.nTableReinsertBefore);f.children().detach();f.append(m);j.css("width",b.sDestroyWidth).removeClass(d.sTable);(o=b.asDestroyStripes.length)&&f.children().each(function(a){h(this).addClass(b.asDestroyStripes[a%o])});c=h.inArray(b,p.settings);-1!==c&&p.settings.splice(c,1)})});p.version="1.10.3";p.settings= -[];p.models={};p.models.oSearch={bCaseInsensitive:!0,sSearch:"",bRegex:!1,bSmart:!0};p.models.oRow={nTr:null,anCells:null,_aData:[],_aSortData:null,_aFilterData:null,_sFilterRow:null,_sRowStripe:"",src:null};p.models.oColumn={idx:null,aDataSort:null,asSorting:null,bSearchable:null,bSortable:null,bVisible:null,_sManualType:null,_bAttrSrc:!1,fnCreatedCell:null,fnGetData:null,fnSetData:null,mData:null,mRender:null,nTh:null,nTf:null,sClass:null,sContentPadding:null,sDefaultContent:null,sName:null,sSortDataType:"std", -sSortingClass:null,sSortingClassJUI:null,sTitle:null,sType:null,sWidth:null,sWidthOrig:null};p.defaults={aaData:null,aaSorting:[[0,"asc"]],aaSortingFixed:[],ajax:null,aLengthMenu:[10,25,50,100],aoColumns:null,aoColumnDefs:null,aoSearchCols:[],asStripeClasses:null,bAutoWidth:!0,bDeferRender:!1,bDestroy:!1,bFilter:!0,bInfo:!0,bJQueryUI:!1,bLengthChange:!0,bPaginate:!0,bProcessing:!1,bRetrieve:!1,bScrollCollapse:!1,bServerSide:!1,bSort:!0,bSortMulti:!0,bSortCellsTop:!1,bSortClasses:!0,bStateSave:!1, -fnCreatedRow:null,fnDrawCallback:null,fnFooterCallback:null,fnFormatNumber:function(a){return a.toString().replace(/\B(?=(\d{3})+(?!\d))/g,this.oLanguage.sThousands)},fnHeaderCallback:null,fnInfoCallback:null,fnInitComplete:null,fnPreDrawCallback:null,fnRowCallback:null,fnServerData:null,fnServerParams:null,fnStateLoadCallback:function(a){try{return JSON.parse((-1===a.iStateDuration?sessionStorage:localStorage).getItem("DataTables_"+a.sInstance+"_"+location.pathname))}catch(b){}},fnStateLoadParams:null, -fnStateLoaded:null,fnStateSaveCallback:function(a,b){try{(-1===a.iStateDuration?sessionStorage:localStorage).setItem("DataTables_"+a.sInstance+"_"+location.pathname,JSON.stringify(b))}catch(c){}},fnStateSaveParams:null,iStateDuration:7200,iDeferLoading:null,iDisplayLength:10,iDisplayStart:0,iTabIndex:0,oClasses:{},oLanguage:{oAria:{sSortAscending:": activate to sort column ascending",sSortDescending:": activate to sort column descending"},oPaginate:{sFirst:"First",sLast:"Last",sNext:"Next",sPrevious:"Previous"}, -sEmptyTable:"No data available in table",sInfo:"Showing _START_ to _END_ of _TOTAL_ entries",sInfoEmpty:"Showing 0 to 0 of 0 entries",sInfoFiltered:"(filtered from _MAX_ total entries)",sInfoPostFix:"",sDecimal:"",sThousands:",",sLengthMenu:"Show _MENU_ entries",sLoadingRecords:"Loading...",sProcessing:"Processing...",sSearch:"Search:",sSearchPlaceholder:"",sUrl:"",sZeroRecords:"No matching records found"},oSearch:h.extend({},p.models.oSearch),sAjaxDataProp:"data",sAjaxSource:null,sDom:"lfrtip",searchDelay:null, -sPaginationType:"simple_numbers",sScrollX:"",sScrollXInner:"",sScrollY:"",sServerMethod:"GET",renderer:null};V(p.defaults);p.defaults.column={aDataSort:null,iDataSort:-1,asSorting:["asc","desc"],bSearchable:!0,bSortable:!0,bVisible:!0,fnCreatedCell:null,mData:null,mRender:null,sCellType:"td",sClass:"",sContentPadding:"",sDefaultContent:null,sName:"",sSortDataType:"std",sTitle:null,sType:null,sWidth:null};V(p.defaults.column);p.models.oSettings={oFeatures:{bAutoWidth:null,bDeferRender:null,bFilter:null, -bInfo:null,bLengthChange:null,bPaginate:null,bProcessing:null,bServerSide:null,bSort:null,bSortMulti:null,bSortClasses:null,bStateSave:null},oScroll:{bCollapse:null,iBarWidth:0,sX:null,sXInner:null,sY:null},oLanguage:{fnInfoCallback:null},oBrowser:{bScrollOversize:!1,bScrollbarLeft:!1},ajax:null,aanFeatures:[],aoData:[],aiDisplay:[],aiDisplayMaster:[],aoColumns:[],aoHeader:[],aoFooter:[],oPreviousSearch:{},aoPreSearchCols:[],aaSorting:null,aaSortingFixed:[],asStripeClasses:null,asDestroyStripes:[], -sDestroyWidth:0,aoRowCallback:[],aoHeaderCallback:[],aoFooterCallback:[],aoDrawCallback:[],aoRowCreatedCallback:[],aoPreDrawCallback:[],aoInitComplete:[],aoStateSaveParams:[],aoStateLoadParams:[],aoStateLoaded:[],sTableId:"",nTable:null,nTHead:null,nTFoot:null,nTBody:null,nTableWrapper:null,bDeferLoading:!1,bInitialised:!1,aoOpenRows:[],sDom:null,searchDelay:null,sPaginationType:"two_button",iStateDuration:0,aoStateSave:[],aoStateLoad:[],oSavedState:null,oLoadedState:null,sAjaxSource:null,sAjaxDataProp:null, -bAjaxDataGet:!0,jqXHR:null,json:l,oAjaxData:l,fnServerData:null,aoServerParams:[],sServerMethod:null,fnFormatNumber:null,aLengthMenu:null,iDraw:0,bDrawing:!1,iDrawError:-1,_iDisplayLength:10,_iDisplayStart:0,_iRecordsTotal:0,_iRecordsDisplay:0,bJUI:null,oClasses:{},bFiltered:!1,bSorted:!1,bSortCellsTop:null,oInit:null,aoDestroyCallback:[],fnRecordsTotal:function(){return"ssp"==A(this)?1*this._iRecordsTotal:this.aiDisplayMaster.length},fnRecordsDisplay:function(){return"ssp"==A(this)?1*this._iRecordsDisplay: -this.aiDisplay.length},fnDisplayEnd:function(){var a=this._iDisplayLength,b=this._iDisplayStart,c=b+a,d=this.aiDisplay.length,e=this.oFeatures,f=e.bPaginate;return e.bServerSide?!1===f||-1===a?b+d:Math.min(b+a,this._iRecordsDisplay):!f||c>d||-1===a?d:c},oInstance:null,sInstance:null,iTabIndex:0,nScrollHead:null,nScrollFoot:null,aLastSort:[],oPlugins:{}};p.ext=v={classes:{},errMode:"alert",feature:[],search:[],internal:{},legacy:{ajax:null},pager:{},renderer:{pageButton:{},header:{}},order:{},type:{detect:[], -search:{},order:{}},_unique:0,fnVersionCheck:p.fnVersionCheck,iApiIndex:0,oJUIClasses:{},sVersion:p.version};h.extend(v,{afnFiltering:v.search,aTypes:v.type.detect,ofnSearch:v.type.search,oSort:v.type.order,afnSortData:v.order,aoFeatures:v.feature,oApi:v.internal,oStdClasses:v.classes,oPagination:v.pager});h.extend(p.ext.classes,{sTable:"dataTable",sNoFooter:"no-footer",sPageButton:"paginate_button",sPageButtonActive:"current",sPageButtonDisabled:"disabled",sStripeOdd:"odd",sStripeEven:"even",sRowEmpty:"dataTables_empty", -sWrapper:"dataTables_wrapper",sFilter:"dataTables_filter",sInfo:"dataTables_info",sPaging:"dataTables_paginate paging_",sLength:"dataTables_length",sProcessing:"dataTables_processing",sSortAsc:"sorting_asc",sSortDesc:"sorting_desc",sSortable:"sorting",sSortableAsc:"sorting_asc_disabled",sSortableDesc:"sorting_desc_disabled",sSortableNone:"sorting_disabled",sSortColumn:"sorting_",sFilterInput:"",sLengthSelect:"",sScrollWrapper:"dataTables_scroll",sScrollHead:"dataTables_scrollHead",sScrollHeadInner:"dataTables_scrollHeadInner", -sScrollBody:"dataTables_scrollBody",sScrollFoot:"dataTables_scrollFoot",sScrollFootInner:"dataTables_scrollFootInner",sHeaderTH:"",sFooterTH:"",sSortJUIAsc:"",sSortJUIDesc:"",sSortJUI:"",sSortJUIAscAllowed:"",sSortJUIDescAllowed:"",sSortJUIWrapper:"",sSortIcon:"",sJUIHeader:"",sJUIFooter:""});var Ca="",Ca="",E=Ca+"ui-state-default",ha=Ca+"css_right ui-icon ui-icon-",Wb=Ca+"fg-toolbar ui-toolbar ui-widget-header ui-helper-clearfix";h.extend(p.ext.oJUIClasses,p.ext.classes,{sPageButton:"fg-button ui-button "+ -E,sPageButtonActive:"ui-state-disabled",sPageButtonDisabled:"ui-state-disabled",sPaging:"dataTables_paginate fg-buttonset ui-buttonset fg-buttonset-multi ui-buttonset-multi paging_",sSortAsc:E+" sorting_asc",sSortDesc:E+" sorting_desc",sSortable:E+" sorting",sSortableAsc:E+" sorting_asc_disabled",sSortableDesc:E+" sorting_desc_disabled",sSortableNone:E+" sorting_disabled",sSortJUIAsc:ha+"triangle-1-n",sSortJUIDesc:ha+"triangle-1-s",sSortJUI:ha+"carat-2-n-s",sSortJUIAscAllowed:ha+"carat-1-n",sSortJUIDescAllowed:ha+ -"carat-1-s",sSortJUIWrapper:"DataTables_sort_wrapper",sSortIcon:"DataTables_sort_icon",sScrollHead:"dataTables_scrollHead "+E,sScrollFoot:"dataTables_scrollFoot "+E,sHeaderTH:E,sFooterTH:E,sJUIHeader:Wb+" ui-corner-tl ui-corner-tr",sJUIFooter:Wb+" ui-corner-bl ui-corner-br"});var Mb=p.ext.pager;h.extend(Mb,{simple:function(){return["previous","next"]},full:function(){return["first","previous","next","last"]},simple_numbers:function(a,b){return["previous",Wa(a,b),"next"]},full_numbers:function(a,b){return["first", -"previous",Wa(a,b),"next","last"]},_numbers:Wa,numbers_length:7});h.extend(!0,p.ext.renderer,{pageButton:{_:function(a,b,c,d,e,f){var g=a.oClasses,i=a.oLanguage.oPaginate,j,l,m=0,o=function(b,d){var k,p,r,q,s=function(b){Ta(a,b.data.action,true)};k=0;for(p=d.length;k").appendTo(b);o(r,q)}else{l=j="";switch(q){case "ellipsis":b.append("");break;case "first":j=i.sFirst;l=q+(e>0?"":" "+g.sPageButtonDisabled);break;case "previous":j= -i.sPrevious;l=q+(e>0?"":" "+g.sPageButtonDisabled);break;case "next":j=i.sNext;l=q+(e",{"class":g.sPageButton+" "+l,"aria-controls":a.sTableId,"data-dt-idx":m,tabindex:a.iTabIndex,id:c===0&&typeof q==="string"?a.sTableId+"_"+q:null}).html(j).appendTo(b);Va(r,{action:q},s);m++}}}};try{var k=h(P.activeElement).data("dt-idx");o(h(b).empty(), -d);k!==null&&h(b).find("[data-dt-idx="+k+"]").focus()}catch(p){}}}});var za=function(a,b,c,d){if(0!==a&&(!a||"-"===a))return-Infinity;b&&(a=Qb(a,b));a.replace&&(c&&(a=a.replace(c,"")),d&&(a=a.replace(d,"")));return 1*a};h.extend(v.type.order,{"date-pre":function(a){return Date.parse(a)||0},"html-pre":function(a){return H(a)?"":a.replace?a.replace(/<.*?>/g,"").toLowerCase():a+""},"string-pre":function(a){return H(a)?"":"string"===typeof a?a.toLowerCase():!a.toString?"":a.toString()},"string-asc":function(a, -b){return ab?1:0},"string-desc":function(a,b){return ab?-1:0}});db("");h.extend(p.ext.type.detect,[function(a,b){var c=b.oLanguage.sDecimal;return Za(a,c)?"num"+c:null},function(a){if(a&&!(a instanceof Date)&&(!$b.test(a)||!ac.test(a)))return null;var b=Date.parse(a);return null!==b&&!isNaN(b)||H(a)?"date":null},function(a,b){var c=b.oLanguage.sDecimal;return Za(a,c,!0)?"num-fmt"+c:null},function(a,b){var c=b.oLanguage.sDecimal;return Rb(a,c)?"html-num"+c:null},function(a,b){var c= -b.oLanguage.sDecimal;return Rb(a,c,!0)?"html-num-fmt"+c:null},function(a){return H(a)||"string"===typeof a&&-1!==a.indexOf("<")?"html":null}]);h.extend(p.ext.type.search,{html:function(a){return H(a)?a:"string"===typeof a?a.replace(Ob," ").replace(Aa,""):""},string:function(a){return H(a)?a:"string"===typeof a?a.replace(Ob," "):a}});h.extend(!0,p.ext.renderer,{header:{_:function(a,b,c,d){h(a.nTable).on("order.dt.DT",function(e,f,g,h){if(a===f){e=c.idx;b.removeClass(c.sSortingClass+" "+d.sSortAsc+ -" "+d.sSortDesc).addClass(h[e]=="asc"?d.sSortAsc:h[e]=="desc"?d.sSortDesc:c.sSortingClass)}})},jqueryui:function(a,b,c,d){h("
").addClass(d.sSortJUIWrapper).append(b.contents()).append(h("").addClass(d.sSortIcon+" "+c.sSortingClassJUI)).appendTo(b);h(a.nTable).on("order.dt.DT",function(e,f,g,h){if(a===f){e=c.idx;b.removeClass(d.sSortAsc+" "+d.sSortDesc).addClass(h[e]=="asc"?d.sSortAsc:h[e]=="desc"?d.sSortDesc:c.sSortingClass);b.find("span."+d.sSortIcon).removeClass(d.sSortJUIAsc+" "+d.sSortJUIDesc+ -" "+d.sSortJUI+" "+d.sSortJUIAscAllowed+" "+d.sSortJUIDescAllowed).addClass(h[e]=="asc"?d.sSortJUIAsc:h[e]=="desc"?d.sSortJUIDesc:c.sSortingClassJUI)}})}}});p.render={number:function(a,b,c,d){return{display:function(e){var f=0>e?"-":"",e=Math.abs(parseFloat(e)),g=parseInt(e,10),e=c?b+(e-g).toFixed(c).substring(2):"";return f+(d||"")+g.toString().replace(/\B(?=(\d{3})+(?!\d))/g,a)+e}}}};h.extend(p.ext.internal,{_fnExternApiFunc:Nb,_fnBuildAjax:qa,_fnAjaxUpdate:kb,_fnAjaxParameters:tb,_fnAjaxUpdateDraw:ub, -_fnAjaxDataSrc:ra,_fnAddColumn:Ea,_fnColumnOptions:ia,_fnAdjustColumnSizing:X,_fnVisibleToColumnIndex:ja,_fnColumnIndexToVisible:$,_fnVisbleColumns:aa,_fnGetColumns:Z,_fnColumnTypes:Ga,_fnApplyColumnDefs:ib,_fnHungarianMap:V,_fnCamelToHungarian:G,_fnLanguageCompat:O,_fnBrowserDetect:gb,_fnAddData:I,_fnAddTr:ka,_fnNodeToDataIndex:function(a,b){return b._DT_RowIndex!==l?b._DT_RowIndex:null},_fnNodeToColumnIndex:function(a,b,c){return h.inArray(c,a.aoData[b].anCells)},_fnGetCellData:w,_fnSetCellData:Ha, -_fnSplitObjNotation:Ja,_fnGetObjectDataFn:W,_fnSetObjectDataFn:Q,_fnGetDataMaster:Ka,_fnClearTable:ma,_fnDeleteIndex:na,_fnInvalidateRow:oa,_fnGetRowElements:la,_fnCreateTr:Ia,_fnBuildHead:jb,_fnDrawHead:da,_fnDraw:L,_fnReDraw:M,_fnAddOptionsHtml:mb,_fnDetectHeader:ca,_fnGetUniqueThs:pa,_fnFeatureHtmlFilter:ob,_fnFilterComplete:ea,_fnFilterCustom:xb,_fnFilterColumn:wb,_fnFilter:vb,_fnFilterCreateSearch:Pa,_fnEscapeRegex:Qa,_fnFilterData:yb,_fnFeatureHtmlInfo:rb,_fnUpdateInfo:Bb,_fnInfoMacros:Cb,_fnInitialise:va, -_fnInitComplete:sa,_fnLengthChange:Ra,_fnFeatureHtmlLength:nb,_fnFeatureHtmlPaginate:sb,_fnPageChange:Ta,_fnFeatureHtmlProcessing:pb,_fnProcessingDisplay:B,_fnFeatureHtmlTable:qb,_fnScrollDraw:Y,_fnApplyToChildren:F,_fnCalculateColumnWidths:Fa,_fnThrottle:ta,_fnConvertToWidth:Db,_fnScrollingWidthAdjust:Fb,_fnGetWidestNode:Eb,_fnGetMaxLenString:Gb,_fnStringToCss:s,_fnScrollBarWidth:Hb,_fnSortFlatten:T,_fnSort:lb,_fnSortAria:Jb,_fnSortListener:Ua,_fnSortAttachListener:Na,_fnSortingClasses:wa,_fnSortData:Ib, -_fnSaveState:xa,_fnLoadState:Kb,_fnSettingsFromNode:ya,_fnLog:R,_fnMap:D,_fnBindAction:Va,_fnCallbackReg:y,_fnCallbackFire:u,_fnLengthOverflow:Sa,_fnRenderer:Oa,_fnDataSource:A,_fnRowAttributes:La,_fnCalculateEnd:function(){}});h.fn.dataTable=p;h.fn.dataTableSettings=p.settings;h.fn.dataTableExt=p.ext;h.fn.DataTable=function(a){return h(this).dataTable(a).api()};h.each(p,function(a,b){h.fn.DataTable[a]=b});return h.fn.dataTable};"function"===typeof define&&define.amd?define("datatables",["jquery"], -O):"object"===typeof exports?O(require("jquery")):jQuery&&!jQuery.fn.dataTable&&O(jQuery)})(window,document); +(function(Da,P,l){var O=function(g){function V(a){var b,c,e={};g.each(a,function(d){if((b=d.match(/^([^A-Z]+?)([A-Z])/))&&-1!=="a aa ai ao as b fn i m o s ".indexOf(b[1]+" "))c=d.replace(b[0],b[2].toLowerCase()),e[c]=d,"o"===b[1]&&V(a[d])});a._hungarianMap=e}function G(a,b,c){a._hungarianMap||V(a);var e;g.each(b,function(d){e=a._hungarianMap[d];if(e!==l&&(c||b[e]===l))"o"===e.charAt(0)?(b[e]||(b[e]={}),g.extend(!0,b[e],b[d]),G(a[e],b[e],c)):b[e]=b[d]})}function O(a){var b=p.defaults.oLanguage,c=a.sZeroRecords; +!a.sEmptyTable&&(c&&"No data available in table"===b.sEmptyTable)&&D(a,a,"sZeroRecords","sEmptyTable");!a.sLoadingRecords&&(c&&"Loading..."===b.sLoadingRecords)&&D(a,a,"sZeroRecords","sLoadingRecords");a.sInfoThousands&&(a.sThousands=a.sInfoThousands);(a=a.sDecimal)&&cb(a)}function db(a){z(a,"ordering","bSort");z(a,"orderMulti","bSortMulti");z(a,"orderClasses","bSortClasses");z(a,"orderCellsTop","bSortCellsTop");z(a,"order","aaSorting");z(a,"orderFixed","aaSortingFixed");z(a,"paging","bPaginate"); +z(a,"pagingType","sPaginationType");z(a,"pageLength","iDisplayLength");z(a,"searching","bFilter");if(a=a.aoSearchCols)for(var b=0,c=a.length;b").css({position:"absolute",top:0,left:0,height:1,width:1,overflow:"hidden"}).append(g("
").css({position:"absolute",top:1,left:1,width:100, +overflow:"scroll"}).append(g('
').css({width:"100%",height:10}))).appendTo("body"),c=b.find(".test");a.bScrollOversize=100===c[0].offsetWidth;a.bScrollbarLeft=1!==c.offset().left;b.remove()}function gb(a,b,c,e,d,f){var h,i=!1;c!==l&&(h=c,i=!0);for(;e!==d;)a.hasOwnProperty(e)&&(h=i?b(h,a[e],e,a):a[e],i=!0,e+=f);return h}function Ea(a,b){var c=p.defaults.column,e=a.aoColumns.length,c=g.extend({},p.models.oColumn,c,{nTh:b?b:P.createElement("th"),sTitle:c.sTitle?c.sTitle:b?b.innerHTML: +"",aDataSort:c.aDataSort?c.aDataSort:[e],mData:c.mData?c.mData:e,idx:e});a.aoColumns.push(c);c=a.aoPreSearchCols;c[e]=g.extend({},p.models.oSearch,c[e]);ja(a,e,null)}function ja(a,b,c){var b=a.aoColumns[b],e=a.oClasses,d=g(b.nTh);if(!b.sWidthOrig){b.sWidthOrig=d.attr("width")||null;var f=(d.attr("style")||"").match(/width:\s*(\d+[pxem%]+)/);f&&(b.sWidthOrig=f[1])}c!==l&&null!==c&&(eb(c),G(p.defaults.column,c),c.mDataProp!==l&&!c.mData&&(c.mData=c.mDataProp),c.sType&&(b._sManualType=c.sType),c.className&& +!c.sClass&&(c.sClass=c.className),g.extend(b,c),D(b,c,"sWidth","sWidthOrig"),"number"===typeof c.iDataSort&&(b.aDataSort=[c.iDataSort]),D(b,c,"aDataSort"));var h=b.mData,i=W(h),j=b.mRender?W(b.mRender):null,c=function(a){return"string"===typeof a&&-1!==a.indexOf("@")};b._bAttrSrc=g.isPlainObject(h)&&(c(h.sort)||c(h.type)||c(h.filter));b.fnGetData=function(a,b,c){var e=i(a,b,l,c);return j&&b?j(e,b,a,c):e};b.fnSetData=function(a,b,c){return Q(h)(a,b,c)};"number"!==typeof h&&(a._rowReadObject=!0);a.oFeatures.bSort|| +(b.bSortable=!1,d.addClass(e.sSortableNone));a=-1!==g.inArray("asc",b.asSorting);c=-1!==g.inArray("desc",b.asSorting);!b.bSortable||!a&&!c?(b.sSortingClass=e.sSortableNone,b.sSortingClassJUI=""):a&&!c?(b.sSortingClass=e.sSortableAsc,b.sSortingClassJUI=e.sSortJUIAscAllowed):!a&&c?(b.sSortingClass=e.sSortableDesc,b.sSortingClassJUI=e.sSortJUIDescAllowed):(b.sSortingClass=e.sSortable,b.sSortingClassJUI=e.sSortJUI)}function X(a){if(!1!==a.oFeatures.bAutoWidth){var b=a.aoColumns;Fa(a);for(var c=0,e=b.length;c< +e;c++)b[c].nTh.style.width=b[c].sWidth}b=a.oScroll;(""!==b.sY||""!==b.sX)&&Y(a);u(a,null,"column-sizing",[a])}function ka(a,b){var c=Z(a,"bVisible");return"number"===typeof c[b]?c[b]:null}function $(a,b){var c=Z(a,"bVisible"),c=g.inArray(b,c);return-1!==c?c:null}function aa(a){return Z(a,"bVisible").length}function Z(a,b){var c=[];g.map(a.aoColumns,function(a,d){a[b]&&c.push(d)});return c}function Ga(a){var b=a.aoColumns,c=a.aoData,e=p.ext.type.detect,d,f,h,i,j,g,m,o,k;d=0;for(f=b.length;do[f])e(m.length+o[f],n);else if("string"===typeof o[f]){i=0;for(j=m.length;ib&&a[d]--; -1!=e&&c===l&& +a.splice(e,1)}function ca(a,b,c,e){var d=a.aoData[b],f,h=function(c,f){for(;c.childNodes.length;)c.removeChild(c.firstChild);c.innerHTML=v(a,b,f,"display")};if("dom"===c||(!c||"auto"===c)&&"dom"===d.src)d._aData=ma(a,d,e,e===l?l:d._aData).data;else{var i=d.anCells;if(i)if(e!==l)h(i[e],e);else{c=0;for(f=i.length;c").appendTo(h));b=0;for(c= +m.length;btr").attr("role","row");g(h).find(">tr>th, >tr>td").addClass(n.sHeaderTH);g(i).find(">tr>th, >tr>td").addClass(n.sFooterTH);if(null!==i){a=a.aoFooter[0];b=0;for(c=a.length;b=a.fnRecordsDisplay()?0:h,a.iInitDisplayStart=-1);var h=a._iDisplayStart,n=a.fnDisplayEnd();if(a.bDeferLoading)a.bDeferLoading= +!1,a.iDraw++,B(a,!1);else if(i){if(!a.bDestroying&&!jb(a))return}else a.iDraw++;if(0!==j.length){f=i?a.aoData.length:n;for(i=i?0:h;i",{"class":d? +e[0]:""}).append(g("",{valign:"top",colSpan:aa(a),"class":a.oClasses.sRowEmpty}).html(c))[0];u(a,"aoHeaderCallback","header",[g(a.nTHead).children("tr")[0],Ka(a),h,n,j]);u(a,"aoFooterCallback","footer",[g(a.nTFoot).children("tr")[0],Ka(a),h,n,j]);e=g(a.nTBody);e.children().detach();e.append(g(b));u(a,"aoDrawCallback","draw",[a]);a.bSorted=!1;a.bFiltered=!1;a.bDrawing=!1}}function M(a,b){var c=a.oFeatures,e=c.bFilter;c.bSort&&kb(a);e?fa(a,a.oPreviousSearch):a.aiDisplay=a.aiDisplayMaster.slice(); +!0!==b&&(a._iDisplayStart=0);a._drawHold=b;L(a);a._drawHold=!1}function lb(a){var b=a.oClasses,c=g(a.nTable),c=g("
").insertBefore(c),e=a.oFeatures,d=g("
",{id:a.sTableId+"_wrapper","class":b.sWrapper+(a.nTFoot?"":" "+b.sNoFooter)});a.nHolding=c[0];a.nTableWrapper=d[0];a.nTableReinsertBefore=a.nTable.nextSibling;for(var f=a.sDom.split(""),h,i,j,n,m,o,k=0;k")[0];n=f[k+1];if("'"==n||'"'==n){m="";for(o=2;f[k+o]!=n;)m+=f[k+o],o++;"H"==m?m=b.sJUIHeader: +"F"==m&&(m=b.sJUIFooter);-1!=m.indexOf(".")?(n=m.split("."),j.id=n[0].substr(1,n[0].length-1),j.className=n[1]):"#"==m.charAt(0)?j.id=m.substr(1,m.length-1):j.className=m;k+=o}d.append(j);d=g(j)}else if(">"==i)d=d.parent();else if("l"==i&&e.bPaginate&&e.bLengthChange)h=mb(a);else if("f"==i&&e.bFilter)h=nb(a);else if("r"==i&&e.bProcessing)h=ob(a);else if("t"==i)h=pb(a);else if("i"==i&&e.bInfo)h=qb(a);else if("p"==i&&e.bPaginate)h=rb(a);else if(0!==p.ext.feature.length){j=p.ext.feature;o=0;for(n=j.length;o< +n;o++)if(i==j[o].cFeature){h=j[o].fnInit(a);break}}h&&(j=a.aanFeatures,j[i]||(j[i]=[]),j[i].push(h),d.append(h))}c.replaceWith(d)}function da(a,b){var c=g(b).children("tr"),e,d,f,h,i,j,n,m,o,k;a.splice(0,a.length);f=0;for(j=c.length;f',i=e.sSearch,i=i.match(/_INPUT_/)?i.replace("_INPUT_",h):i+h,b=g("
",{id:!f.f?c+"_filter":null,"class":b.sFilter}).append(g("
").addClass(b.sLength);a.aanFeatures.l||(j[0].id=c+"_length");j.children().append(a.oLanguage.sLengthMenu.replace("_MENU_",d[0].outerHTML));g("select",j).val(a._iDisplayLength).bind("change.DT", +function(){Qa(a,g(this).val());L(a)});g(a.nTable).bind("length.dt.DT",function(b,c,f){a===c&&g("select",j).val(f)});return j[0]}function rb(a){var b=a.sPaginationType,c=p.ext.pager[b],e="function"===typeof c,d=function(a){L(a)},b=g("
").addClass(a.oClasses.sPaging+b)[0],f=a.aanFeatures;e||c.fnInit(a,b,d);f.p||(b.id=a.sTableId+"_paginate",a.aoDrawCallback.push({fn:function(a){if(e){var b=a._iDisplayStart,g=a._iDisplayLength,n=a.fnRecordsDisplay(),m=-1===g,b=m?0:Math.ceil(b/g),g=m?1:Math.ceil(n/ +g),n=c(b,g),o,m=0;for(o=f.p.length;mf&&(e=0)):"first"==b?e=0:"previous"==b?(e=0<=d?e-d:0,0>e&&(e=0)):"next"==b?e+d",{id:!a.aanFeatures.r?a.sTableId+"_processing":null,"class":a.oClasses.sProcessing}).html(a.oLanguage.sProcessing).insertBefore(a.nTable)[0]}function B(a,b){a.oFeatures.bProcessing&&g(a.aanFeatures.r).css("display",b?"block":"none");u(a,null,"processing",[a,b])}function pb(a){var b=g(a.nTable);b.attr("role","grid");var c=a.oScroll;if(""===c.sX&&""===c.sY)return a.nTable;var e=c.sX,d=c.sY,f=a.oClasses,h=b.children("caption"),i=h.length?h[0]._captionSide:null, +j=g(b[0].cloneNode(!1)),n=g(b[0].cloneNode(!1)),m=b.children("tfoot");c.sX&&"100%"===b.attr("width")&&b.removeAttr("width");m.length||(m=null);c=g("
",{"class":f.sScrollWrapper}).append(g("
",{"class":f.sScrollHead}).css({overflow:"hidden",position:"relative",border:0,width:e?!e?null:s(e):"100%"}).append(g("
",{"class":f.sScrollHeadInner}).css({"box-sizing":"content-box",width:c.sXInner||"100%"}).append(j.removeAttr("id").css("margin-left",0).append("top"===i?h:null).append(b.children("thead"))))).append(g("
", +{"class":f.sScrollBody}).css({overflow:"auto",height:!d?null:s(d),width:!e?null:s(e)}).append(b));m&&c.append(g("
",{"class":f.sScrollFoot}).css({overflow:"hidden",border:0,width:e?!e?null:s(e):"100%"}).append(g("
",{"class":f.sScrollFootInner}).append(n.removeAttr("id").css("margin-left",0).append("bottom"===i?h:null).append(b.children("tfoot")))));var b=c.children(),o=b[0],f=b[1],k=m?b[2]:null;e&&g(f).scroll(function(){var a=this.scrollLeft;o.scrollLeft=a;m&&(k.scrollLeft=a)});a.nScrollHead= +o;a.nScrollBody=f;a.nScrollFoot=k;a.aoDrawCallback.push({fn:Y,sName:"scrolling"});return c[0]}function Y(a){var b=a.oScroll,c=b.sX,e=b.sXInner,d=b.sY,f=b.iBarWidth,h=g(a.nScrollHead),i=h[0].style,j=h.children("div"),n=j[0].style,m=j.children("table"),j=a.nScrollBody,o=g(j),k=j.style,l=g(a.nScrollFoot).children("div"),p=l.children("table"),r=g(a.nTHead),q=g(a.nTable),t=q[0],N=t.style,J=a.nTFoot?g(a.nTFoot):null,u=a.oBrowser,w=u.bScrollOversize,y,v,x,K,z,A=[],B=[],C=[],D,E=function(a){a=a.style;a.paddingTop= +"0";a.paddingBottom="0";a.borderTopWidth="0";a.borderBottomWidth="0";a.height=0};q.children("thead, tfoot").remove();z=r.clone().prependTo(q);y=r.find("tr");x=z.find("tr");z.find("th, td").removeAttr("tabindex");J&&(K=J.clone().prependTo(q),v=J.find("tr"),K=K.find("tr"));c||(k.width="100%",h[0].style.width="100%");g.each(pa(a,z),function(b,c){D=ka(a,b);c.style.width=a.aoColumns[D].sWidth});J&&F(function(a){a.style.width=""},K);b.bCollapse&&""!==d&&(k.height=o[0].offsetHeight+r[0].offsetHeight+"px"); +h=q.outerWidth();if(""===c){if(N.width="100%",w&&(q.find("tbody").height()>j.offsetHeight||"scroll"==o.css("overflow-y")))N.width=s(q.outerWidth()-f)}else""!==e?N.width=s(e):h==o.width()&&o.height()h-f&&(N.width=s(h))):N.width=s(h);h=q.outerWidth();F(E,x);F(function(a){C.push(a.innerHTML);A.push(s(g(a).css("width")))},x);F(function(a,b){a.style.width=A[b]},y);g(x).height(0);J&&(F(E,K),F(function(a){B.push(s(g(a).css("width")))},K),F(function(a,b){a.style.width= +B[b]},v),g(K).height(0));F(function(a,b){a.innerHTML='
'+C[b]+"
";a.style.width=A[b]},x);J&&F(function(a,b){a.innerHTML="";a.style.width=B[b]},K);if(q.outerWidth()j.offsetHeight||"scroll"==o.css("overflow-y")?h+f:h;if(w&&(j.scrollHeight>j.offsetHeight||"scroll"==o.css("overflow-y")))N.width=s(v-f);(""===c||""!==e)&&R(a,1,"Possible column misalignment",6)}else v="100%";k.width=s(v);i.width=s(v);J&&(a.nScrollFoot.style.width= +s(v));!d&&w&&(k.height=s(t.offsetHeight+f));d&&b.bCollapse&&(k.height=s(d),b=c&&t.offsetWidth>j.offsetWidth?f:0,t.offsetHeightj.clientHeight||"scroll"==o.css("overflow-y");u="padding"+(u.bScrollbarLeft?"Left":"Right");n[u]=m?f+"px":"0px";J&&(p[0].style.width=s(b),l[0].style.width=s(b),l[0].style[u]=m?f+"px":"0px");o.scroll();if((a.bSorted||a.bFiltered)&&!a._drawHold)j.scrollTop=0}function F(a, +b,c){for(var e=0,d=0,f=b.length,h,g;d"));i.find("tfoot th, tfoot td").css("width","");var p=i.find("tbody tr"),j=pa(a,i.find("thead")[0]);for(k=0;k").css("width",s(a)).appendTo(b||P.body),e=c[0].offsetWidth;c.remove();return e}function Eb(a,b){var c=a.oScroll;if(c.sX||c.sY)c=!c.sX?c.iBarWidth:0,b.style.width=s(g(b).outerWidth()-c)}function Db(a,b){var c=Fb(a,b);if(0>c)return null; +var e=a.aoData[c];return!e.nTr?g("").html(v(a,c,b,"display"))[0]:e.anCells[b]}function Fb(a,b){for(var c,e=-1,d=-1,f=0,h=a.aoData.length;fe&&(e=c.length,d=f);return d}function s(a){return null===a?"0px":"number"==typeof a?0>a?"0px":a+"px":a.match(/\d$/)?a+"px":a}function Gb(){if(!p.__scrollbarWidth){var a=g("

").css({width:"100%",height:200,padding:0})[0],b=g("

").css({position:"absolute",top:0,left:0,width:200,height:150,padding:0, +overflow:"hidden",visibility:"hidden"}).append(a).appendTo("body"),c=a.offsetWidth;b.css("overflow","scroll");a=a.offsetWidth;c===a&&(a=b[0].clientWidth);b.remove();p.__scrollbarWidth=c-a}return p.__scrollbarWidth}function T(a){var b,c,e=[],d=a.aoColumns,f,h,i,j;b=a.aaSortingFixed;c=g.isPlainObject(b);var n=[];f=function(a){a.length&&!g.isArray(a[0])?n.push(a):n.push.apply(n,a)};g.isArray(b)&&f(b);c&&b.pre&&f(b.pre);f(a.aaSorting);c&&b.post&&f(b.post);for(a=0;ad?1:0,0!==c)return"asc"===g.dir?c:-c;c=e[a];d=e[b];return cd?1:0}):j.sort(function(a,b){var c,h,g,i,j=n.length,l=f[a]._aSortData,p=f[b]._aSortData;for(g=0;gh?1:0})}a.bSorted=!0}function Ib(a){for(var b,c,e=a.aoColumns,d=T(a),a=a.oLanguage.oAria,f=0,h=e.length;f/g,"");var j=c.nTh;j.removeAttribute("aria-sort");c.bSortable&&(0d?d+1:3));d=0;for(f=e.length;dd?d+1:3))}a.aLastSort=e}function Hb(a,b){var c=a.aoColumns[b],e=p.ext.order[c.sSortDataType],d;e&&(d=e.call(a.oInstance,a,b,$(a,b)));for(var f,h=p.ext.type.order[c.sType+"-pre"],g=0,j=a.aoData.length;g< +j;g++)if(c=a.aoData[g],c._aSortData||(c._aSortData=[]),!c._aSortData[b]||e)f=e?d[g]:v(a,g,b,"sort"),c._aSortData[b]=h?h(f):f}function xa(a){if(a.oFeatures.bStateSave&&!a.bDestroying){var b={time:+new Date,start:a._iDisplayStart,length:a._iDisplayLength,order:g.extend(!0,[],a.aaSorting),search:yb(a.oPreviousSearch),columns:g.map(a.aoColumns,function(b,e){return{visible:b.bVisible,search:yb(a.aoPreSearchCols[e])}})};u(a,"aoStateSaveParams","stateSaveParams",[a,b]);a.oSavedState=b;a.fnStateSaveCallback.call(a.oInstance, +a,b)}}function Jb(a){var b,c,e=a.aoColumns;if(a.oFeatures.bStateSave){var d=a.fnStateLoadCallback.call(a.oInstance,a);if(d&&d.time&&(b=u(a,"aoStateLoadParams","stateLoadParams",[a,d]),-1===g.inArray(!1,b)&&(b=a.iStateDuration,!(0=e.length?[0,c[1]]:c)});g.extend(a.oPreviousSearch, +zb(d.search));b=0;for(c=d.columns.length;b=c&&(b=c-e);b-=b%e;if(-1===e||0>b)b=0;a._iDisplayStart=b}function Oa(a,b){var c=a.renderer,e=p.ext.renderer[b];return g.isPlainObject(c)&& +c[b]?e[c[b]]||e._:"string"===typeof c?e[c]||e._:e._}function A(a){return a.oFeatures.bServerSide?"ssp":a.ajax||a.sAjaxSource?"ajax":"dom"}function Va(a,b){var c=[],c=Lb.numbers_length,e=Math.floor(c/2);b<=c?c=U(0,b):a<=e?(c=U(0,c-2),c.push("ellipsis"),c.push(b-1)):(a>=b-1-e?c=U(b-(c-2),b):(c=U(a-1,a+2),c.push("ellipsis"),c.push(b-1)),c.splice(0,0,"ellipsis"),c.splice(0,0,0));c.DT_el="span";return c}function cb(a){g.each({num:function(b){return za(b,a)},"num-fmt":function(b){return za(b,a,Wa)},"html-num":function(b){return za(b, +a,Aa)},"html-num-fmt":function(b){return za(b,a,Aa,Wa)}},function(b,c){w.type.order[b+a+"-pre"]=c;b.match(/^html\-/)&&(w.type.search[b+a]=w.type.search.html)})}function Mb(a){return function(){var b=[ya(this[p.ext.iApiIndex])].concat(Array.prototype.slice.call(arguments));return p.ext.internal[a].apply(this,b)}}var p,w,q,r,t,Xa={},Nb=/[\r\n]/g,Aa=/<.*?>/g,$b=/^[\w\+\-]/,ac=/[\w\+\-]$/,Xb=RegExp("(\\/|\\.|\\*|\\+|\\?|\\||\\(|\\)|\\[|\\]|\\{|\\}|\\\\|\\$|\\^|\\-)","g"),Wa=/[',$\u00a3\u20ac\u00a5%\u2009\u202F]/g, +H=function(a){return!a||!0===a||"-"===a?!0:!1},Ob=function(a){var b=parseInt(a,10);return!isNaN(b)&&isFinite(a)?b:null},Pb=function(a,b){Xa[b]||(Xa[b]=RegExp(ua(b),"g"));return"string"===typeof a&&"."!==b?a.replace(/\./g,"").replace(Xa[b],"."):a},Ya=function(a,b,c){var e="string"===typeof a;b&&e&&(a=Pb(a,b));c&&e&&(a=a.replace(Wa,""));return H(a)||!isNaN(parseFloat(a))&&isFinite(a)},Qb=function(a,b,c){return H(a)?!0:!(H(a)||"string"===typeof a)?null:Ya(a.replace(Aa,""),b,c)?!0:null},C=function(a, +b,c){var e=[],d=0,f=a.length;if(c!==l)for(;d")[0],Yb=va.textContent!==l,Zb=/<.*?>/g;p=function(a){this.$=function(a,b){return this.api(!0).$(a,b)};this._=function(a,b){return this.api(!0).rows(a,b).data()};this.api=function(a){return a?new q(ya(this[w.iApiIndex])):new q(this)};this.fnAddData=function(a,b){var c=this.api(!0),e=g.isArray(a)&&(g.isArray(a[0])||g.isPlainObject(a[0]))? +c.rows.add(a):c.row.add(a);(b===l||b)&&c.draw();return e.flatten().toArray()};this.fnAdjustColumnSizing=function(a){var b=this.api(!0).columns.adjust(),c=b.settings()[0],e=c.oScroll;a===l||a?b.draw(!1):(""!==e.sX||""!==e.sY)&&Y(c)};this.fnClearTable=function(a){var b=this.api(!0).clear();(a===l||a)&&b.draw()};this.fnClose=function(a){this.api(!0).row(a).child.hide()};this.fnDeleteRow=function(a,b,c){var e=this.api(!0),a=e.rows(a),d=a.settings()[0],g=d.aoData[a[0][0]];a.remove();b&&b.call(this,d,g); +(c===l||c)&&e.draw();return g};this.fnDestroy=function(a){this.api(!0).destroy(a)};this.fnDraw=function(a){this.api(!0).draw(!a)};this.fnFilter=function(a,b,c,e,d,g){d=this.api(!0);null===b||b===l?d.search(a,c,e,g):d.column(b).search(a,c,e,g);d.draw()};this.fnGetData=function(a,b){var c=this.api(!0);if(a!==l){var e=a.nodeName?a.nodeName.toLowerCase():"";return b!==l||"td"==e||"th"==e?c.cell(a,b).data():c.row(a).data()||null}return c.data().toArray()};this.fnGetNodes=function(a){var b=this.api(!0); +return a!==l?b.row(a).node():b.rows().nodes().flatten().toArray()};this.fnGetPosition=function(a){var b=this.api(!0),c=a.nodeName.toUpperCase();return"TR"==c?b.row(a).index():"TD"==c||"TH"==c?(a=b.cell(a).index(),[a.row,a.columnVisible,a.column]):null};this.fnIsOpen=function(a){return this.api(!0).row(a).child.isShown()};this.fnOpen=function(a,b,c){return this.api(!0).row(a).child(b,c).show().child()[0]};this.fnPageChange=function(a,b){var c=this.api(!0).page(a);(b===l||b)&&c.draw(!1)};this.fnSetColumnVis= +function(a,b,c){a=this.api(!0).column(a).visible(b);(c===l||c)&&a.columns.adjust().draw()};this.fnSettings=function(){return ya(this[w.iApiIndex])};this.fnSort=function(a){this.api(!0).order(a).draw()};this.fnSortListener=function(a,b,c){this.api(!0).order.listener(a,b,c)};this.fnUpdate=function(a,b,c,e,d){var g=this.api(!0);c===l||null===c?g.row(b).data(a):g.cell(b,c).data(a);(d===l||d)&&g.columns.adjust();(e===l||e)&&g.draw();return 0};this.fnVersionCheck=w.fnVersionCheck;var b=this,c=a===l,e=this.length; +c&&(a={});this.oApi=this.internal=w.internal;for(var d in p.ext.internal)d&&(this[d]=Mb(d));this.each(function(){var d={},d=1t<"F"ip>'),k.renderer)? +g.isPlainObject(k.renderer)&&!k.renderer.header&&(k.renderer.header="jqueryui"):k.renderer="jqueryui":g.extend(j,p.ext.classes,d.oClasses);g(this).addClass(j.sTable);if(""!==k.oScroll.sX||""!==k.oScroll.sY)k.oScroll.iBarWidth=Gb();!0===k.oScroll.sX&&(k.oScroll.sX="100%");k.iInitDisplayStart===l&&(k.iInitDisplayStart=d.iDisplayStart,k._iDisplayStart=d.iDisplayStart);null!==d.iDeferLoading&&(k.bDeferLoading=!0,h=g.isArray(d.iDeferLoading),k._iRecordsDisplay=h?d.iDeferLoading[0]:d.iDeferLoading,k._iRecordsTotal= +h?d.iDeferLoading[1]:d.iDeferLoading);var r=k.oLanguage;g.extend(!0,r,d.oLanguage);""!==r.sUrl&&(g.ajax({dataType:"json",url:r.sUrl,success:function(a){O(a);G(m.oLanguage,a);g.extend(true,r,a);ga(k)},error:function(){ga(k)}}),n=!0);null===d.asStripeClasses&&(k.asStripeClasses=[j.sStripeOdd,j.sStripeEven]);var h=k.asStripeClasses,q=g("tbody tr:eq(0)",this);-1!==g.inArray(!0,g.map(h,function(a){return q.hasClass(a)}))&&(g("tbody tr",this).removeClass(h.join(" ")),k.asDestroyStripes=h.slice());var o= +[],s,h=this.getElementsByTagName("thead");0!==h.length&&(da(k.aoHeader,h[0]),o=pa(k));if(null===d.aoColumns){s=[];h=0;for(i=o.length;h").appendTo(this));k.nTHead=i[0];i=g(this).children("tbody");0===i.length&&(i=g("").appendTo(this));k.nTBody=i[0];i=g(this).children("tfoot");if(0===i.length&&0").appendTo(this);0===i.length||0===i.children().length?g(this).addClass(j.sNoFooter): +0a?new q(b[a],this[a]):null},filter:function(a){var b=[];if(y.filter)b=y.filter.call(this,a,this);else for(var c=0,e=this.length;c").addClass(b);g("td",c).addClass(b).html(a)[0].colSpan=aa(e);d.push(c[0])}};if(g.isArray(a)||a instanceof g)for(var h=0,i=a.length;h=0?b:h.length+b];if(typeof a==="function"){var d=Ba(c,f);return g.map(h,function(b,f){return a(f,Vb(c,f,0,0,d),j[f])?f:null})}var k=typeof a==="string"?a.match(cc):"";if(k)switch(k[2]){case "visIdx":case "visible":b= +parseInt(k[1],10);if(b<0){var l=g.map(h,function(a,b){return a.bVisible?b:null});return[l[l.length+b]]}return[ka(c,b)];case "name":return g.map(i,function(a,b){return a===k[1]?b:null})}else return g(j).filter(a).map(function(){return g.inArray(this,j)}).toArray()})},1);c.selector.cols=a;c.selector.opts=b;return c});t("columns().header()","column().header()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].nTh},1)});t("columns().footer()","column().footer()",function(){return this.iterator("column", +function(a,b){return a.aoColumns[b].nTf},1)});t("columns().data()","column().data()",function(){return this.iterator("column-rows",Vb,1)});t("columns().dataSrc()","column().dataSrc()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].mData},1)});t("columns().cache()","column().cache()",function(a){return this.iterator("column-rows",function(b,c,e,d,f){return ha(b.aoData,f,"search"===a?"_aFilterData":"_aSortData",c)},1)});t("columns().nodes()","column().nodes()",function(){return this.iterator("column-rows", +function(a,b,c,e,d){return ha(a.aoData,d,"anCells",b)},1)});t("columns().visible()","column().visible()",function(a,b){return this.iterator("column",function(c,e){if(a===l)return c.aoColumns[e].bVisible;var d=c.aoColumns,f=d[e],h=c.aoData,i,j,n;if(a!==l&&f.bVisible!==a){if(a){var m=g.inArray(!0,C(d,"bVisible"),e+1);i=0;for(j=h.length;ie;return!0};p.isDataTable=p.fnIsDataTable=function(a){var b=g(a).get(0),c=!1;g.each(p.settings,function(a,d){if(d.nTable===b||d.nScrollHead===b||d.nScrollFoot===b)c=!0});return c};p.tables=p.fnTables=function(a){return g.map(p.settings,function(b){if(!a||a&&g(b.nTable).is(":visible"))return b.nTable})};p.util={throttle:ta,escapeRegex:ua}; +p.camelToHungarian=G;r("$()",function(a,b){var c=this.rows(b).nodes(),c=g(c);return g([].concat(c.filter(a).toArray(),c.find(a).toArray()))});g.each(["on","one","off"],function(a,b){r(b+"()",function(){var a=Array.prototype.slice.call(arguments);a[0].match(/\.dt\b/)||(a[0]+=".dt");var e=g(this.tables().nodes());e[b].apply(e,a);return this})});r("clear()",function(){return this.iterator("table",function(a){na(a)})});r("settings()",function(){return new q(this.context,this.context)});r("data()",function(){return this.iterator("table", +function(a){return C(a.aoData,"_aData")}).flatten()});r("destroy()",function(a){a=a||!1;return this.iterator("table",function(b){var c=b.nTableWrapper.parentNode,e=b.oClasses,d=b.nTable,f=b.nTBody,h=b.nTHead,i=b.nTFoot,j=g(d),f=g(f),l=g(b.nTableWrapper),m=g.map(b.aoData,function(a){return a.nTr}),o;b.bDestroying=!0;u(b,"aoDestroyCallback","destroy",[b]);a||(new q(b)).columns().visible(!0);l.unbind(".DT").find(":not(tbody *)").unbind(".DT");g(Da).unbind(".DT-"+b.sInstance);d!=h.parentNode&&(j.children("thead").detach(), +j.append(h));i&&d!=i.parentNode&&(j.children("tfoot").detach(),j.append(i));j.detach();l.detach();b.aaSorting=[];b.aaSortingFixed=[];wa(b);g(m).removeClass(b.asStripeClasses.join(" "));g("th, td",h).removeClass(e.sSortable+" "+e.sSortableAsc+" "+e.sSortableDesc+" "+e.sSortableNone);b.bJUI&&(g("th span."+e.sSortIcon+", td span."+e.sSortIcon,h).detach(),g("th, td",h).each(function(){var a=g("div."+e.sSortJUIWrapper,this);g(this).append(a.contents());a.detach()}));!a&&c&&c.insertBefore(d,b.nTableReinsertBefore); +f.children().detach();f.append(m);j.css("width",b.sDestroyWidth).removeClass(e.sTable);(o=b.asDestroyStripes.length)&&f.children().each(function(a){g(this).addClass(b.asDestroyStripes[a%o])});c=g.inArray(b,p.settings);-1!==c&&p.settings.splice(c,1)})});p.version="1.10.4";p.settings=[];p.models={};p.models.oSearch={bCaseInsensitive:!0,sSearch:"",bRegex:!1,bSmart:!0};p.models.oRow={nTr:null,anCells:null,_aData:[],_aSortData:null,_aFilterData:null,_sFilterRow:null,_sRowStripe:"",src:null};p.models.oColumn= +{idx:null,aDataSort:null,asSorting:null,bSearchable:null,bSortable:null,bVisible:null,_sManualType:null,_bAttrSrc:!1,fnCreatedCell:null,fnGetData:null,fnSetData:null,mData:null,mRender:null,nTh:null,nTf:null,sClass:null,sContentPadding:null,sDefaultContent:null,sName:null,sSortDataType:"std",sSortingClass:null,sSortingClassJUI:null,sTitle:null,sType:null,sWidth:null,sWidthOrig:null};p.defaults={aaData:null,aaSorting:[[0,"asc"]],aaSortingFixed:[],ajax:null,aLengthMenu:[10,25,50,100],aoColumns:null, +aoColumnDefs:null,aoSearchCols:[],asStripeClasses:null,bAutoWidth:!0,bDeferRender:!1,bDestroy:!1,bFilter:!0,bInfo:!0,bJQueryUI:!1,bLengthChange:!0,bPaginate:!0,bProcessing:!1,bRetrieve:!1,bScrollCollapse:!1,bServerSide:!1,bSort:!0,bSortMulti:!0,bSortCellsTop:!1,bSortClasses:!0,bStateSave:!1,fnCreatedRow:null,fnDrawCallback:null,fnFooterCallback:null,fnFormatNumber:function(a){return a.toString().replace(/\B(?=(\d{3})+(?!\d))/g,this.oLanguage.sThousands)},fnHeaderCallback:null,fnInfoCallback:null, +fnInitComplete:null,fnPreDrawCallback:null,fnRowCallback:null,fnServerData:null,fnServerParams:null,fnStateLoadCallback:function(a){try{return JSON.parse((-1===a.iStateDuration?sessionStorage:localStorage).getItem("DataTables_"+a.sInstance+"_"+location.pathname))}catch(b){}},fnStateLoadParams:null,fnStateLoaded:null,fnStateSaveCallback:function(a,b){try{(-1===a.iStateDuration?sessionStorage:localStorage).setItem("DataTables_"+a.sInstance+"_"+location.pathname,JSON.stringify(b))}catch(c){}},fnStateSaveParams:null, +iStateDuration:7200,iDeferLoading:null,iDisplayLength:10,iDisplayStart:0,iTabIndex:0,oClasses:{},oLanguage:{oAria:{sSortAscending:": activate to sort column ascending",sSortDescending:": activate to sort column descending"},oPaginate:{sFirst:"First",sLast:"Last",sNext:"Next",sPrevious:"Previous"},sEmptyTable:"No data available in table",sInfo:"Showing _START_ to _END_ of _TOTAL_ entries",sInfoEmpty:"Showing 0 to 0 of 0 entries",sInfoFiltered:"(filtered from _MAX_ total entries)",sInfoPostFix:"",sDecimal:"", +sThousands:",",sLengthMenu:"Show _MENU_ entries",sLoadingRecords:"Loading...",sProcessing:"Processing...",sSearch:"Search:",sSearchPlaceholder:"",sUrl:"",sZeroRecords:"No matching records found"},oSearch:g.extend({},p.models.oSearch),sAjaxDataProp:"data",sAjaxSource:null,sDom:"lfrtip",searchDelay:null,sPaginationType:"simple_numbers",sScrollX:"",sScrollXInner:"",sScrollY:"",sServerMethod:"GET",renderer:null};V(p.defaults);p.defaults.column={aDataSort:null,iDataSort:-1,asSorting:["asc","desc"],bSearchable:!0, +bSortable:!0,bVisible:!0,fnCreatedCell:null,mData:null,mRender:null,sCellType:"td",sClass:"",sContentPadding:"",sDefaultContent:null,sName:"",sSortDataType:"std",sTitle:null,sType:null,sWidth:null};V(p.defaults.column);p.models.oSettings={oFeatures:{bAutoWidth:null,bDeferRender:null,bFilter:null,bInfo:null,bLengthChange:null,bPaginate:null,bProcessing:null,bServerSide:null,bSort:null,bSortMulti:null,bSortClasses:null,bStateSave:null},oScroll:{bCollapse:null,iBarWidth:0,sX:null,sXInner:null,sY:null}, +oLanguage:{fnInfoCallback:null},oBrowser:{bScrollOversize:!1,bScrollbarLeft:!1},ajax:null,aanFeatures:[],aoData:[],aiDisplay:[],aiDisplayMaster:[],aoColumns:[],aoHeader:[],aoFooter:[],oPreviousSearch:{},aoPreSearchCols:[],aaSorting:null,aaSortingFixed:[],asStripeClasses:null,asDestroyStripes:[],sDestroyWidth:0,aoRowCallback:[],aoHeaderCallback:[],aoFooterCallback:[],aoDrawCallback:[],aoRowCreatedCallback:[],aoPreDrawCallback:[],aoInitComplete:[],aoStateSaveParams:[],aoStateLoadParams:[],aoStateLoaded:[], +sTableId:"",nTable:null,nTHead:null,nTFoot:null,nTBody:null,nTableWrapper:null,bDeferLoading:!1,bInitialised:!1,aoOpenRows:[],sDom:null,searchDelay:null,sPaginationType:"two_button",iStateDuration:0,aoStateSave:[],aoStateLoad:[],oSavedState:null,oLoadedState:null,sAjaxSource:null,sAjaxDataProp:null,bAjaxDataGet:!0,jqXHR:null,json:l,oAjaxData:l,fnServerData:null,aoServerParams:[],sServerMethod:null,fnFormatNumber:null,aLengthMenu:null,iDraw:0,bDrawing:!1,iDrawError:-1,_iDisplayLength:10,_iDisplayStart:0, +_iRecordsTotal:0,_iRecordsDisplay:0,bJUI:null,oClasses:{},bFiltered:!1,bSorted:!1,bSortCellsTop:null,oInit:null,aoDestroyCallback:[],fnRecordsTotal:function(){return"ssp"==A(this)?1*this._iRecordsTotal:this.aiDisplayMaster.length},fnRecordsDisplay:function(){return"ssp"==A(this)?1*this._iRecordsDisplay:this.aiDisplay.length},fnDisplayEnd:function(){var a=this._iDisplayLength,b=this._iDisplayStart,c=b+a,e=this.aiDisplay.length,d=this.oFeatures,f=d.bPaginate;return d.bServerSide?!1===f||-1===a?b+e: +Math.min(b+a,this._iRecordsDisplay):!f||c>e||-1===a?e:c},oInstance:null,sInstance:null,iTabIndex:0,nScrollHead:null,nScrollFoot:null,aLastSort:[],oPlugins:{}};p.ext=w={classes:{},errMode:"alert",feature:[],search:[],internal:{},legacy:{ajax:null},pager:{},renderer:{pageButton:{},header:{}},order:{},type:{detect:[],search:{},order:{}},_unique:0,fnVersionCheck:p.fnVersionCheck,iApiIndex:0,oJUIClasses:{},sVersion:p.version};g.extend(w,{afnFiltering:w.search,aTypes:w.type.detect,ofnSearch:w.type.search, +oSort:w.type.order,afnSortData:w.order,aoFeatures:w.feature,oApi:w.internal,oStdClasses:w.classes,oPagination:w.pager});g.extend(p.ext.classes,{sTable:"dataTable",sNoFooter:"no-footer",sPageButton:"paginate_button",sPageButtonActive:"current",sPageButtonDisabled:"disabled",sStripeOdd:"odd",sStripeEven:"even",sRowEmpty:"dataTables_empty",sWrapper:"dataTables_wrapper",sFilter:"dataTables_filter",sInfo:"dataTables_info",sPaging:"dataTables_paginate paging_",sLength:"dataTables_length",sProcessing:"dataTables_processing", +sSortAsc:"sorting_asc",sSortDesc:"sorting_desc",sSortable:"sorting",sSortableAsc:"sorting_asc_disabled",sSortableDesc:"sorting_desc_disabled",sSortableNone:"sorting_disabled",sSortColumn:"sorting_",sFilterInput:"",sLengthSelect:"",sScrollWrapper:"dataTables_scroll",sScrollHead:"dataTables_scrollHead",sScrollHeadInner:"dataTables_scrollHeadInner",sScrollBody:"dataTables_scrollBody",sScrollFoot:"dataTables_scrollFoot",sScrollFootInner:"dataTables_scrollFootInner",sHeaderTH:"",sFooterTH:"",sSortJUIAsc:"", +sSortJUIDesc:"",sSortJUI:"",sSortJUIAscAllowed:"",sSortJUIDescAllowed:"",sSortJUIWrapper:"",sSortIcon:"",sJUIHeader:"",sJUIFooter:""});var Ca="",Ca="",E=Ca+"ui-state-default",ia=Ca+"css_right ui-icon ui-icon-",Wb=Ca+"fg-toolbar ui-toolbar ui-widget-header ui-helper-clearfix";g.extend(p.ext.oJUIClasses,p.ext.classes,{sPageButton:"fg-button ui-button "+E,sPageButtonActive:"ui-state-disabled",sPageButtonDisabled:"ui-state-disabled",sPaging:"dataTables_paginate fg-buttonset ui-buttonset fg-buttonset-multi ui-buttonset-multi paging_", +sSortAsc:E+" sorting_asc",sSortDesc:E+" sorting_desc",sSortable:E+" sorting",sSortableAsc:E+" sorting_asc_disabled",sSortableDesc:E+" sorting_desc_disabled",sSortableNone:E+" sorting_disabled",sSortJUIAsc:ia+"triangle-1-n",sSortJUIDesc:ia+"triangle-1-s",sSortJUI:ia+"carat-2-n-s",sSortJUIAscAllowed:ia+"carat-1-n",sSortJUIDescAllowed:ia+"carat-1-s",sSortJUIWrapper:"DataTables_sort_wrapper",sSortIcon:"DataTables_sort_icon",sScrollHead:"dataTables_scrollHead "+E,sScrollFoot:"dataTables_scrollFoot "+E, +sHeaderTH:E,sFooterTH:E,sJUIHeader:Wb+" ui-corner-tl ui-corner-tr",sJUIFooter:Wb+" ui-corner-bl ui-corner-br"});var Lb=p.ext.pager;g.extend(Lb,{simple:function(){return["previous","next"]},full:function(){return["first","previous","next","last"]},simple_numbers:function(a,b){return["previous",Va(a,b),"next"]},full_numbers:function(a,b){return["first","previous",Va(a,b),"next","last"]},_numbers:Va,numbers_length:7});g.extend(!0,p.ext.renderer,{pageButton:{_:function(a,b,c,e,d,f){var h=a.oClasses,i= +a.oLanguage.oPaginate,j,l,m=0,o=function(b,e){var k,p,r,q,s=function(b){Sa(a,b.data.action,true)};k=0;for(p=e.length;k").appendTo(b);o(r,q)}else{l=j="";switch(q){case "ellipsis":b.append("");break;case "first":j=i.sFirst;l=q+(d>0?"":" "+h.sPageButtonDisabled);break;case "previous":j=i.sPrevious;l=q+(d>0?"":" "+h.sPageButtonDisabled);break;case "next":j=i.sNext;l=q+(d",{"class":h.sPageButton+" "+l,"aria-controls":a.sTableId,"data-dt-idx":m,tabindex:a.iTabIndex,id:c===0&&typeof q==="string"?a.sTableId+"_"+q:null}).html(j).appendTo(b);Ua(r,{action:q},s);m++}}}};try{var k=g(P.activeElement).data("dt-idx");o(g(b).empty(),e);k!==null&&g(b).find("[data-dt-idx="+k+"]").focus()}catch(p){}}}});g.extend(p.ext.type.detect,[function(a,b){var c=b.oLanguage.sDecimal; +return Ya(a,c)?"num"+c:null},function(a){if(a&&!(a instanceof Date)&&(!$b.test(a)||!ac.test(a)))return null;var b=Date.parse(a);return null!==b&&!isNaN(b)||H(a)?"date":null},function(a,b){var c=b.oLanguage.sDecimal;return Ya(a,c,!0)?"num-fmt"+c:null},function(a,b){var c=b.oLanguage.sDecimal;return Qb(a,c)?"html-num"+c:null},function(a,b){var c=b.oLanguage.sDecimal;return Qb(a,c,!0)?"html-num-fmt"+c:null},function(a){return H(a)||"string"===typeof a&&-1!==a.indexOf("<")?"html":null}]);g.extend(p.ext.type.search, +{html:function(a){return H(a)?a:"string"===typeof a?a.replace(Nb," ").replace(Aa,""):""},string:function(a){return H(a)?a:"string"===typeof a?a.replace(Nb," "):a}});var za=function(a,b,c,e){if(0!==a&&(!a||"-"===a))return-Infinity;b&&(a=Pb(a,b));a.replace&&(c&&(a=a.replace(c,"")),e&&(a=a.replace(e,"")));return 1*a};g.extend(w.type.order,{"date-pre":function(a){return Date.parse(a)||0},"html-pre":function(a){return H(a)?"":a.replace?a.replace(/<.*?>/g,"").toLowerCase():a+""},"string-pre":function(a){return H(a)? +"":"string"===typeof a?a.toLowerCase():!a.toString?"":a.toString()},"string-asc":function(a,b){return ab?1:0},"string-desc":function(a,b){return ab?-1:0}});cb("");g.extend(!0,p.ext.renderer,{header:{_:function(a,b,c,e){g(a.nTable).on("order.dt.DT",function(d,f,h,g){if(a===f){d=c.idx;b.removeClass(c.sSortingClass+" "+e.sSortAsc+" "+e.sSortDesc).addClass(g[d]=="asc"?e.sSortAsc:g[d]=="desc"?e.sSortDesc:c.sSortingClass)}})},jqueryui:function(a,b,c,e){g("
").addClass(e.sSortJUIWrapper).append(b.contents()).append(g("").addClass(e.sSortIcon+ +" "+c.sSortingClassJUI)).appendTo(b);g(a.nTable).on("order.dt.DT",function(d,f,g,i){if(a===f){d=c.idx;b.removeClass(e.sSortAsc+" "+e.sSortDesc).addClass(i[d]=="asc"?e.sSortAsc:i[d]=="desc"?e.sSortDesc:c.sSortingClass);b.find("span."+e.sSortIcon).removeClass(e.sSortJUIAsc+" "+e.sSortJUIDesc+" "+e.sSortJUI+" "+e.sSortJUIAscAllowed+" "+e.sSortJUIDescAllowed).addClass(i[d]=="asc"?e.sSortJUIAsc:i[d]=="desc"?e.sSortJUIDesc:c.sSortingClassJUI)}})}}});p.render={number:function(a,b,c,e){return{display:function(d){var f= +0>d?"-":"",d=Math.abs(parseFloat(d)),g=parseInt(d,10),d=c?b+(d-g).toFixed(c).substring(2):"";return f+(e||"")+g.toString().replace(/\B(?=(\d{3})+(?!\d))/g,a)+d}}}};g.extend(p.ext.internal,{_fnExternApiFunc:Mb,_fnBuildAjax:qa,_fnAjaxUpdate:jb,_fnAjaxParameters:sb,_fnAjaxUpdateDraw:tb,_fnAjaxDataSrc:ra,_fnAddColumn:Ea,_fnColumnOptions:ja,_fnAdjustColumnSizing:X,_fnVisibleToColumnIndex:ka,_fnColumnIndexToVisible:$,_fnVisbleColumns:aa,_fnGetColumns:Z,_fnColumnTypes:Ga,_fnApplyColumnDefs:hb,_fnHungarianMap:V, +_fnCamelToHungarian:G,_fnLanguageCompat:O,_fnBrowserDetect:fb,_fnAddData:I,_fnAddTr:la,_fnNodeToDataIndex:function(a,b){return b._DT_RowIndex!==l?b._DT_RowIndex:null},_fnNodeToColumnIndex:function(a,b,c){return g.inArray(c,a.aoData[b].anCells)},_fnGetCellData:v,_fnSetCellData:Ha,_fnSplitObjNotation:Ja,_fnGetObjectDataFn:W,_fnSetObjectDataFn:Q,_fnGetDataMaster:Ka,_fnClearTable:na,_fnDeleteIndex:oa,_fnInvalidate:ca,_fnGetRowElements:ma,_fnCreateTr:Ia,_fnBuildHead:ib,_fnDrawHead:ea,_fnDraw:L,_fnReDraw:M, +_fnAddOptionsHtml:lb,_fnDetectHeader:da,_fnGetUniqueThs:pa,_fnFeatureHtmlFilter:nb,_fnFilterComplete:fa,_fnFilterCustom:wb,_fnFilterColumn:vb,_fnFilter:ub,_fnFilterCreateSearch:Pa,_fnEscapeRegex:ua,_fnFilterData:xb,_fnFeatureHtmlInfo:qb,_fnUpdateInfo:Ab,_fnInfoMacros:Bb,_fnInitialise:ga,_fnInitComplete:sa,_fnLengthChange:Qa,_fnFeatureHtmlLength:mb,_fnFeatureHtmlPaginate:rb,_fnPageChange:Sa,_fnFeatureHtmlProcessing:ob,_fnProcessingDisplay:B,_fnFeatureHtmlTable:pb,_fnScrollDraw:Y,_fnApplyToChildren:F, +_fnCalculateColumnWidths:Fa,_fnThrottle:ta,_fnConvertToWidth:Cb,_fnScrollingWidthAdjust:Eb,_fnGetWidestNode:Db,_fnGetMaxLenString:Fb,_fnStringToCss:s,_fnScrollBarWidth:Gb,_fnSortFlatten:T,_fnSort:kb,_fnSortAria:Ib,_fnSortListener:Ta,_fnSortAttachListener:Na,_fnSortingClasses:wa,_fnSortData:Hb,_fnSaveState:xa,_fnLoadState:Jb,_fnSettingsFromNode:ya,_fnLog:R,_fnMap:D,_fnBindAction:Ua,_fnCallbackReg:x,_fnCallbackFire:u,_fnLengthOverflow:Ra,_fnRenderer:Oa,_fnDataSource:A,_fnRowAttributes:La,_fnCalculateEnd:function(){}}); +g.fn.dataTable=p;g.fn.dataTableSettings=p.settings;g.fn.dataTableExt=p.ext;g.fn.DataTable=function(a){return g(this).dataTable(a).api()};g.each(p,function(a,b){g.fn.DataTable[a]=b});return g.fn.dataTable};"function"===typeof define&&define.amd?define("datatables",["jquery"],O):"object"===typeof exports?O(require("jquery")):jQuery&&!jQuery.fn.dataTable&&O(jQuery)})(window,document); diff --git a/src/sunstone/public/bower_components/foundation-datatables/.bower.json b/src/sunstone/public/bower_components/foundation-datatables/.bower.json index 88f4ed40d6..00284c97e3 100644 --- a/src/sunstone/public/bower_components/foundation-datatables/.bower.json +++ b/src/sunstone/public/bower_components/foundation-datatables/.bower.json @@ -1,14 +1,14 @@ { "name": "foundation-datatables", "homepage": "https://github.com/DataTables/Plugins", - "_release": "15ba23c72a", + "version": "1.0.0", + "_release": "1.0.0", "_resolution": { - "type": "branch", - "branch": "master", - "commit": "15ba23c72a02c4665e0686bef83f3880c02eeddd" + "type": "version", + "tag": "1.0.0", + "commit": "1be8930f1cf5a3d90f046e245d921714f14f153b" }, "_source": "https://github.com/DataTables/Plugins.git", "_target": "*", - "_originalSource": "https://github.com/DataTables/Plugins.git", - "_direct": true + "_originalSource": "https://github.com/DataTables/Plugins.git" } \ No newline at end of file diff --git a/src/sunstone/public/bower_components/foundation-datatables/api/column().title().js b/src/sunstone/public/bower_components/foundation-datatables/api/column().title().js new file mode 100644 index 0000000000..56394eacf5 --- /dev/null +++ b/src/sunstone/public/bower_components/foundation-datatables/api/column().title().js @@ -0,0 +1,21 @@ +/** + * This plug-in will read the text from the header cell of a column, returning + * that value. + * + * @name column().title() + * @summary Get the title of a column + * @author Alejandro Navarro + * @requires DataTables 1.10+ + * + * @returns {String} Column title + * + * @example + * // Read the title text of column index 3 + * var table = $('#example').DataTable(); + * table.column( 3 ).title(); + */ + +$.fn.dataTable.Api.register( 'column().title()', function () { + var colheader = this.header(); + return $(colheader).text().trim(); +} ); \ No newline at end of file diff --git a/src/sunstone/public/bower_components/foundation-datatables/features/alphabetSearch/dataTables.alphabetSearch.css b/src/sunstone/public/bower_components/foundation-datatables/features/alphabetSearch/dataTables.alphabetSearch.css new file mode 100644 index 0000000000..3258e1e57a --- /dev/null +++ b/src/sunstone/public/bower_components/foundation-datatables/features/alphabetSearch/dataTables.alphabetSearch.css @@ -0,0 +1,39 @@ +div.alphabet { + position: relative; + display: table; + width: 100%; + margin-bottom: 1em; +} + +div.alphabet span { + display: table-cell; + color: #3174c7; + cursor: pointer; + text-align: center; + width: 3.5% +} + +div.alphabet span:hover { + text-decoration: underline; +} + +div.alphabet span.active { + color: black; +} + +div.alphabet span.empty { + color: red; +} + +div.alphabetInfo { + display: block; + position: absolute; + background-color: #111; + border-radius: 3px; + color: white; + top: 2em; + height: 1.8em; + padding-top: 0.4em; + text-align: center; + z-index: 1; +} diff --git a/src/sunstone/public/bower_components/foundation-datatables/features/alphabetSearch/dataTables.alphabetSearch.js b/src/sunstone/public/bower_components/foundation-datatables/features/alphabetSearch/dataTables.alphabetSearch.js new file mode 100644 index 0000000000..4382f270b4 --- /dev/null +++ b/src/sunstone/public/bower_components/foundation-datatables/features/alphabetSearch/dataTables.alphabetSearch.js @@ -0,0 +1,162 @@ +/*! AlphabetSearch for DataTables v1.0.0 + * 2014 SpryMedia Ltd - datatables.net/license + */ + +/** + * @summary AlphabetSearch + * @description Show an set of alphabet buttons alongside a table providing + * search input options + * @version 1.0.0 + * @file dataTables.alphabetSearch.js + * @author SpryMedia Ltd (www.sprymedia.co.uk) + * @contact www.sprymedia.co.uk/contact + * @copyright Copyright 2014 SpryMedia Ltd. + * + * License MIT - http://datatables.net/license/mit + * + * For more detailed information please see: + * http://datatables.net/blog/2014-09-22 + */ +(function(){ + + +// Search function +$.fn.dataTable.Api.register( 'alphabetSearch()', function ( searchTerm ) { + this.iterator( 'table', function ( context ) { + context.alphabetSearch = searchTerm; + } ); + + return this; +} ); + +// Recalculate the alphabet display for updated data +$.fn.dataTable.Api.register( 'alphabetSearch.recalc()', function ( searchTerm ) { + this.iterator( 'table', function ( context ) { + draw( + new $.fn.dataTable.Api( context ), + $('div.alphabet', this.table().container()) + ); + } ); + + return this; +} ); + + +// Search plug-in +$.fn.dataTable.ext.search.push( function ( context, searchData ) { + // Ensure that there is a search applied to this table before running it + if ( ! context.alphabetSearch ) { + return true; + } + + if ( searchData[0].charAt(0) === context.alphabetSearch ) { + return true; + } + + return false; +} ); + + +// Private support methods +function bin ( data ) { + var letter, bins = {}; + + for ( var i=0, ien=data.length ; i') + .data( 'letter', '' ) + .data( 'match-count', columnData.length ) + .html( 'None' ) + .appendTo( alphabet ); + + for ( var i=0 ; i<26 ; i++ ) { + var letter = String.fromCharCode( 65 + i ); + + $('') + .data( 'letter', letter ) + .data( 'match-count', bins[letter] || 0 ) + .addClass( ! bins[letter] ? 'empty' : '' ) + .html( letter ) + .appendTo( alphabet ); + } + + $('
') + .appendTo( alphabet ); +} + + +$.fn.dataTable.AlphabetSearch = function ( context ) { + var table = new $.fn.dataTable.Api( context ); + var alphabet = $('
'); + + draw( table, alphabet ); + + // Trigger a search + alphabet.on( 'click', 'span', function () { + alphabet.find( '.active' ).removeClass( 'active' ); + $(this).addClass( 'active' ); + + table + .alphabetSearch( $(this).data('letter') ) + .draw(); + } ); + + // Mouse events to show helper information + alphabet + .on( 'mouseenter', 'span', function () { + alphabet + .find('div.alphabetInfo') + .css( { + opacity: 1, + left: $(this).position().left, + width: $(this).width() + } ) + .html( $(this).data('match-count') ); + } ) + .on( 'mouseleave', 'span', function () { + alphabet + .find('div.alphabetInfo') + .css('opacity', 0); + } ); + + // API method to get the alphabet container node + this.node = function () { + return alphabet; + }; +}; + +$.fn.DataTable.AlphabetSearch = $.fn.dataTable.AlphabetSearch; + + +// Register a search plug-in +$.fn.dataTable.ext.feature.push( { + fnInit: function ( settings ) { + var search = new $.fn.dataTable.AlphabetSearch( settings ); + return search.node(); + }, + cFeature: 'A' +} ); + + +}()); + diff --git a/src/sunstone/public/bower_components/foundation-datatables/features/alphabetSearch/dataTables.alphabetSearch.min.js b/src/sunstone/public/bower_components/foundation-datatables/features/alphabetSearch/dataTables.alphabetSearch.min.js new file mode 100644 index 0000000000..72d7fb55c7 --- /dev/null +++ b/src/sunstone/public/bower_components/foundation-datatables/features/alphabetSearch/dataTables.alphabetSearch.min.js @@ -0,0 +1,8 @@ +/*! + AlphabetSearch for DataTables v1.0.0 + 2014 SpryMedia Ltd - datatables.net/license +*/ +(function(){function f(b,c){c.empty();c.append("Search: ");for(var a=b.column(0).data(),d,e={},g=0,f=a.length;g').data("letter","").data("match-count",a.length).html("None").appendTo(c);for(a=0;26>a;a++)d=String.fromCharCode(65+a),$("").data("letter",d).data("match-count",e[d]||0).addClass(!e[d]?"empty":"").html(d).appendTo(c);$('
').appendTo(c)}$.fn.dataTable.Api.register("alphabetSearch()", +function(b){this.iterator("table",function(c){c.alphabetSearch=b});return this});$.fn.dataTable.Api.register("alphabetSearch.recalc()",function(){this.iterator("table",function(b){f(new $.fn.dataTable.Api(b),$("div.alphabet",this.table().container()))});return this});$.fn.dataTable.ext.search.push(function(b,c){return!b.alphabetSearch||c[0].charAt(0)===b.alphabetSearch?!0:!1});$.fn.dataTable.AlphabetSearch=function(b){var c=new $.fn.dataTable.Api(b),a=$('
');f(c,a);a.on("click", +"span",function(){a.find(".active").removeClass("active");$(this).addClass("active");c.alphabetSearch($(this).data("letter")).draw()});a.on("mouseenter","span",function(){a.find("div.alphabetInfo").css({opacity:1,left:$(this).position().left,width:$(this).width()}).html($(this).data("match-count"))}).on("mouseleave","span",function(){a.find("div.alphabetInfo").css("opacity",0)});this.node=function(){return a}};$.fn.DataTable.AlphabetSearch=$.fn.dataTable.AlphabetSearch;$.fn.dataTable.ext.feature.push({fnInit:function(b){return(new $.fn.dataTable.AlphabetSearch(b)).node()}, +cFeature:"A"})})(); diff --git a/src/sunstone/public/bower_components/foundation-datatables/features/lengthChange.js b/src/sunstone/public/bower_components/foundation-datatables/features/lengthChange.js deleted file mode 100644 index 4f06a7274e..0000000000 --- a/src/sunstone/public/bower_components/foundation-datatables/features/lengthChange.js +++ /dev/null @@ -1,84 +0,0 @@ -(function(window, document, $, undefined) { - - -$.fn.dataTableExt.oApi.fnLengthChange = function ( oSettings, iDisplay ) { - oSettings._iDisplayLength = iDisplay; - oSettings.oApi._fnCalculateEnd( oSettings ); - - /* If we have space to show extra rows (backing up from the end point - then do so */ - if ( oSettings._iDisplayEnd == oSettings.aiDisplay.length ) { - oSettings._iDisplayStart = oSettings._iDisplayEnd - oSettings._iDisplayLength; - if ( oSettings._iDisplayStart < 0 ) { - oSettings._iDisplayStart = 0; - } - } - - if ( oSettings._iDisplayLength == -1 ) { - oSettings._iDisplayStart = 0; - } - - oSettings.oApi._fnDraw( oSettings ); -}; - - -$.fn.dataTable.LengthLinks = function ( oSettings ) { - var container = $('
').addClass( oSettings.oClasses.sLength ); - var lastLength = -1; - var draw = function () { - // No point in updating - nothing has changed - if ( oSettings._iDisplayLength === lastLength ) { - return; - } - - var menu = oSettings.aLengthMenu; - var lang = menu.length===2 && $.isArray(menu[0]) ? menu[1] : menu; - var lens = menu.length===2 && $.isArray(menu[0]) ? menu[0] : menu; - - var out = $.map( lens, function (el, i) { - return el == oSettings._iDisplayLength ? - ''+lang[i]+'' : - ''+lang[i]+''; - } ); - - container.html( oSettings.oLanguage.sLengthMenu.replace( '_MENU_', out.join(' ') ) ); - lastLength = oSettings._iDisplayLength; - }; - - // API, so the feature wrapper can return the node to insert - this.container = container; - - // Update on each draw - oSettings.aoDrawCallback.push( { - "fn": function () { - draw(); - }, - "sName": "PagingControl" - } ); - - // Listen for events to change the page length - container.on( 'click', 'a', function (e) { - e.preventDefault(); - oSettings.oInstance.fnLengthChange( parseInt( $(this).attr('data-length'), 10 ) ); - } ); -} - -// Subscribe the feature plug-in to DataTables, ready for use -$.fn.dataTableExt.aoFeatures.push( { - "fnInit": function( oSettings ) { - var l = new $.fn.dataTable.LengthLinks( oSettings ); - return l.container[0]; - }, - "cFeature": "L", - "sFeature": "LengthLinks" -} ); - - -})(window, document, jQuery); - - -// DataTables initialisation -$(document).ready(function() { - $('#example').dataTable( { - "sDom": "Lfrtip" - } ); -} ); \ No newline at end of file diff --git a/src/sunstone/public/bower_components/foundation-datatables/features/lengthLinks/dataTables.lengthLinks.css b/src/sunstone/public/bower_components/foundation-datatables/features/lengthLinks/dataTables.lengthLinks.css new file mode 100644 index 0000000000..c78d21d4dc --- /dev/null +++ b/src/sunstone/public/bower_components/foundation-datatables/features/lengthLinks/dataTables.lengthLinks.css @@ -0,0 +1,4 @@ + +div.dataTables_length a.active { + color: black; +} \ No newline at end of file diff --git a/src/sunstone/public/bower_components/foundation-datatables/features/lengthLinks/dataTables.lengthLinks.js b/src/sunstone/public/bower_components/foundation-datatables/features/lengthLinks/dataTables.lengthLinks.js new file mode 100644 index 0000000000..b7abc5e889 --- /dev/null +++ b/src/sunstone/public/bower_components/foundation-datatables/features/lengthLinks/dataTables.lengthLinks.js @@ -0,0 +1,90 @@ +/*! Page length control via links for DataTables + * 2014 SpryMedia Ltd - datatables.net/license + */ + +/** + * @summary LengthLinks + * @description Page length control via links for DataTables + * @version 1.1.0 + * @file dataTables.searchHighlight.js + * @author SpryMedia Ltd (www.sprymedia.co.uk) + * @contact www.sprymedia.co.uk/contact + * @copyright Copyright 2014 SpryMedia Ltd. + * + * License MIT - http://datatables.net/license/mit + * + * This feature plug-in for DataTables adds page length control links to the + * DataTable. The `dom` option can be used to insert the control using the `L` + * character option and it uses the `lengthMenu` options of DataTables to + * determine what to display. + * + * @example + * $('#myTable').DataTable( { + * dom: 'Lfrtip' + * } ); + * + * @example + * $('#myTable').DataTable( { + * lengthMenu: [ [10, 25, 50, -1], [10, 25, 50, "All"] ] + * dom: 'Lfrtip' + * } ); + */ + +(function(window, document, $, undefined) { + + +$.fn.dataTable.LengthLinks = function ( inst ) { + var api = new $.fn.dataTable.Api( inst ); + var settings = api.settings()[0]; + var container = $('
').addClass( settings.oClasses.sLength ); + var lastLength = -1; + + // API so the feature wrapper can return the node to insert + this.container = function () { + return container[0]; + }; + + // Listen for events to change the page length + container.on( 'click.dtll', 'a', function (e) { + e.preventDefault(); + api.page.len( $(this).data('length')*1 ).draw( false ); + } ); + + // Update on each draw + api.on( 'draw', function () { + // No point in updating - nothing has changed + if ( api.page.len() === lastLength ) { + return; + } + + var menu = settings.aLengthMenu; + var lang = menu.length===2 && $.isArray(menu[0]) ? menu[1] : menu; + var lens = menu.length===2 && $.isArray(menu[0]) ? menu[0] : menu; + + var out = $.map( lens, function (el, i) { + return el == api.page.len() ? + ''+lang[i]+'' : + ''+lang[i]+''; + } ); + + container.html( settings.oLanguage.sLengthMenu.replace( '_MENU_', out.join(' | ') ) ); + lastLength = api.page.len(); + } ); + + api.on( 'destroy', function () { + container.off( 'click.dtll', 'a' ); + } ); +}; + +// Subscribe the feature plug-in to DataTables, ready for use +$.fn.dataTable.ext.feature.push( { + "fnInit": function( settings ) { + var l = new $.fn.dataTable.LengthLinks( settings ); + return l.container(); + }, + "cFeature": "L", + "sFeature": "LengthLinks" +} ); + + +})(window, document, jQuery); diff --git a/src/sunstone/public/bower_components/foundation-datatables/features/lengthLinks/dataTables.lengthLinks.min.js b/src/sunstone/public/bower_components/foundation-datatables/features/lengthLinks/dataTables.lengthLinks.min.js new file mode 100644 index 0000000000..ff8001a68c --- /dev/null +++ b/src/sunstone/public/bower_components/foundation-datatables/features/lengthLinks/dataTables.lengthLinks.min.js @@ -0,0 +1,6 @@ +/*! + Page length control via links for DataTables + 2014 SpryMedia Ltd - datatables.net/license +*/ +(function(i,j,a){a.fn.dataTable.LengthLinks=function(d){var c=new a.fn.dataTable.Api(d),f=c.settings()[0],e=a("
").addClass(f.oClasses.sLength),h=-1;this.container=function(){return e[0]};e.on("click.dtll","a",function(b){b.preventDefault();c.page.len(1*a(this).data("length")).draw(!1)});c.on("draw",function(){if(c.page.len()!==h){var b=f.aLengthMenu,d=2===b.length&&a.isArray(b[0])?b[1]:b,g=2===b.length&&a.isArray(b[0])?b[0]:b,b=a.map(g,function(b,a){return b==c.page.len()?''+d[a]+"":''+d[a]+""});e.html(f.oLanguage.sLengthMenu.replace("_MENU_",b.join(" | ")));h=c.page.len()}});c.on("destroy",function(){e.off("click.dtll","a")})};a.fn.dataTable.ext.feature.push({fnInit:function(d){return(new a.fn.dataTable.LengthLinks(d)).container()},cFeature:"L",sFeature:"LengthLinks"})})(window,document,jQuery); diff --git a/src/sunstone/public/bower_components/foundation-datatables/features/searchHighlight/dataTables.searchHighlight.css b/src/sunstone/public/bower_components/foundation-datatables/features/searchHighlight/dataTables.searchHighlight.css new file mode 100644 index 0000000000..10a03e6330 --- /dev/null +++ b/src/sunstone/public/bower_components/foundation-datatables/features/searchHighlight/dataTables.searchHighlight.css @@ -0,0 +1,6 @@ + + +table.dataTable span.highlight { + background-color: #FFFF88; +} + diff --git a/src/sunstone/public/bower_components/foundation-datatables/features/searchHighlight/dataTables.searchHighlight.js b/src/sunstone/public/bower_components/foundation-datatables/features/searchHighlight/dataTables.searchHighlight.js new file mode 100644 index 0000000000..c075994281 --- /dev/null +++ b/src/sunstone/public/bower_components/foundation-datatables/features/searchHighlight/dataTables.searchHighlight.js @@ -0,0 +1,67 @@ +/*! SearchHighlight for DataTables v1.0.1 + * 2014 SpryMedia Ltd - datatables.net/license + */ + +/** + * @summary SearchHighlight + * @description Search term highlighter for DataTables + * @version 1.0.1 + * @file dataTables.searchHighlight.js + * @author SpryMedia Ltd (www.sprymedia.co.uk) + * @contact www.sprymedia.co.uk/contact + * @copyright Copyright 2014 SpryMedia Ltd. + * + * License MIT - http://datatables.net/license/mit + * + * This feature plug-in for DataTables will highlight search terms in the + * DataTable as they are entered into the main search input element, or via the + * `search()` API method. + * + * It depends upon the jQuery Highlight plug-in by Bartek Szopka: + * http://bartaz.github.io/sandbox.js/jquery.highlight.js + * + * Search highlighting in DataTables can be enabled by: + * + * * Adding the class `searchHighlight` to the HTML table + * * Setting the `searchHighlight` parameter in the DataTables initialisation to + * be true + * * Setting the `searchHighlight` parameter to be true in the DataTables + * defaults (thus causing all tables to have this feature) - i.e. + * `$.fn.dataTable.defaults.searchHighlight = true`. + * + * For more detailed information please see: + * http://datatables.net/blog/2014-10-22 + */ + +(function(window, document, $){ + + +// Listen for DataTables initialisations +$(document).on( 'init.dt.dth', function (e, settings, json) { + var table = new $.fn.dataTable.Api( settings ); + var body = $( table.table().body() ); + + if ( + $( table.table().node() ).hasClass( 'searchHighlight' ) || // table has class + settings.oInit.searchHighlight || // option specified + $.fn.dataTable.defaults.searchHighlight // default set + ) { + table + .on( 'draw.dt.dth column-visibility.dt.dth', function () { + // On each draw highlight search results, removing the old ones + body.unhighlight(); + + // Don't highlight the "not found" row + if ( table.rows( { filter: 'applied' } ).data().length ) { + body.highlight( table.search().split(' ') ); + } + } ) + .on( 'destroy', function () { + // Remove event handler + table.off( 'draw.dt.dth column-visibility.dt.dth' ); + } ); + } +} ); + + +})(window, document, jQuery); diff --git a/src/sunstone/public/bower_components/foundation-datatables/features/searchHighlight/dataTables.searchHighlight.min.js b/src/sunstone/public/bower_components/foundation-datatables/features/searchHighlight/dataTables.searchHighlight.min.js new file mode 100644 index 0000000000..945c24d603 --- /dev/null +++ b/src/sunstone/public/bower_components/foundation-datatables/features/searchHighlight/dataTables.searchHighlight.min.js @@ -0,0 +1,5 @@ +/*! + SearchHighlight for DataTables v1.0.0 + 2014 SpryMedia Ltd - datatables.net/license +*/ +(function(f,c,b){b(c).on("init.dt.dth",function(c,d){var a=new b.fn.dataTable.Api(d),e=b(a.table().body());if(b(a.table().node()).hasClass("searchHighlight")||d.oInit.searchHighlight||b.fn.dataTable.defaults.searchHighlight)a.on("draw.dt.dth column-visibility.dt.dth",function(){e.unhighlight();a.rows({filter:"applied"}).data().length&&e.highlight(a.search())}).on("destroy",function(){a.off("draw.dt.dth column-visibility.dt.dth")})})})(window,document,jQuery); diff --git a/src/sunstone/public/bower_components/foundation-datatables/filtering/type-based/accent-neutralise.js b/src/sunstone/public/bower_components/foundation-datatables/filtering/type-based/accent-neutralise.js index 20fe11fd45..05a83b58f8 100644 --- a/src/sunstone/public/bower_components/foundation-datatables/filtering/type-based/accent-neutralise.js +++ b/src/sunstone/public/bower_components/foundation-datatables/filtering/type-based/accent-neutralise.js @@ -9,7 +9,7 @@ * accented characters will no longer match. The second example below shows * how the function can be used to remove accents from the search input as well, * to mitigate this problem. - * + * * @summary Replace accented characters with unaccented counterparts * @name Accent neutralise * @author Allan Jardine @@ -39,6 +39,13 @@ jQuery.fn.DataTable.ext.type.search.string = function ( data ) { '' : typeof data === 'string' ? data + .replace( /έ/g, 'ε') + .replace( /ύ/g, 'υ') + .replace( /ό/g, 'ο') + .replace( /ώ/g, 'ω') + .replace( /ά/g, 'α') + .replace( /ί/g, 'ι') + .replace( /ή/g, 'η') .replace( /\n/g, ' ' ) .replace( /á/g, 'a' ) .replace( /é/g, 'e' ) @@ -51,6 +58,7 @@ jQuery.fn.DataTable.ext.type.search.string = function ( data ) { .replace( /è/g, 'e' ) .replace( /ï/g, 'i' ) .replace( /ü/g, 'u' ) - .replace( /ç/g, 'c' ) : + .replace( /ç/g, 'c' ) + .replace( /ì/g, 'i' ) : data; }; diff --git a/src/sunstone/public/bower_components/foundation-datatables/i18n/Polish.lang b/src/sunstone/public/bower_components/foundation-datatables/i18n/Polish.lang index bb01ee237e..09af71f99a 100644 --- a/src/sunstone/public/bower_components/foundation-datatables/i18n/Polish.lang +++ b/src/sunstone/public/bower_components/foundation-datatables/i18n/Polish.lang @@ -25,7 +25,7 @@ "sEmptyTable": "Brak danych", "sLoadingRecords": "Wczytywanie...", "oAria": { - "sSortAscending": ": aktywuj by posortować kolumnę rosnąco", - "sSortDescending": ": aktywuj by posortować kolumnę malejąco" + "sSortAscending": ": aktywuj, by posortować kolumnę rosnąco", + "sSortDescending": ": aktywuj, by posortować kolumnę malejąco" } } diff --git a/src/sunstone/public/bower_components/foundation-datatables/integration/bootstrap/2/dataTables.bootstrap.css b/src/sunstone/public/bower_components/foundation-datatables/integration/bootstrap/2/dataTables.bootstrap.css index bb011f5a03..1744bb98f2 100644 --- a/src/sunstone/public/bower_components/foundation-datatables/integration/bootstrap/2/dataTables.bootstrap.css +++ b/src/sunstone/public/bower_components/foundation-datatables/integration/bootstrap/2/dataTables.bootstrap.css @@ -1,5 +1,4 @@ div.dataTables_length label { - float: left; text-align: left; } @@ -7,8 +6,17 @@ div.dataTables_length select { width: 75px; } +div.dataTables_filter { + text-align: right; +} + div.dataTables_filter label { - float: right; + text-align: left; +} + +div.dataTables_filter input { + margin-left: 0.5em; + display: inline-block; } div.dataTables_info { @@ -16,10 +24,23 @@ div.dataTables_info { } div.dataTables_paginate { - float: right; + text-align: right; margin: 0; } +div.dataTables_paginate div.pagination { + margin: 0; +} + +@media screen and (max-width: 767px) { + div.dataTables_length, + div.dataTables_filter, + div.dataTables_info, + div.dataTables_paginate { + text-align: center; + } +} + table.table { clear: both; margin-bottom: 6px !important; diff --git a/src/sunstone/public/bower_components/foundation-datatables/integration/bootstrap/2/dataTables.bootstrap.js b/src/sunstone/public/bower_components/foundation-datatables/integration/bootstrap/2/dataTables.bootstrap.js index 7c552e0461..264763f9e5 100644 --- a/src/sunstone/public/bower_components/foundation-datatables/integration/bootstrap/2/dataTables.bootstrap.js +++ b/src/sunstone/public/bower_components/foundation-datatables/integration/bootstrap/2/dataTables.bootstrap.js @@ -1,121 +1,133 @@ -/* Set the defaults for DataTables initialisation */ -$.extend( true, $.fn.dataTable.defaults, { - "sDom": "<'row-fluid'<'span6'l><'span6'f>r>t<'row-fluid'<'span6'i><'span6'p>>", - "sPaginationType": "bootstrap", - "oLanguage": { - "sLengthMenu": "_MENU_ records per page" - } +/*! DataTables Bootstrap 2 integration + * ©2011-2014 SpryMedia Ltd - datatables.net/license + */ + +/** + * DataTables integration for Bootstrap 2. This requires Bootstrap 2 and + * DataTables 1.10 or newer. + * + * This file sets the defaults and adds options to DataTables to style its + * controls using Bootstrap. See http://datatables.net/manual/styling/bootstrap + * for further information. + */ +(function(window, document, $, DataTable, undefined){ + +$.extend( true, DataTable.defaults, { + "dom": + "<'row-fluid'<'span6'l><'span6'f>r>" + + "<'row-fluid'<'span12't>>" + + "<'row-fluid'<'span6'i><'span6'p>>", + renderer: 'bootstrap' } ); /* Default class modification */ -$.extend( $.fn.dataTableExt.oStdClasses, { - "sWrapper": "dataTables_wrapper form-inline" +$.extend( DataTable.ext.classes, { + sWrapper: "dataTables_wrapper form-inline dt-bootstrap" } ); -/* API method to get paging information */ -$.fn.dataTableExt.oApi.fnPagingInfo = function ( oSettings ) -{ - return { - "iStart": oSettings._iDisplayStart, - "iEnd": oSettings.fnDisplayEnd(), - "iLength": oSettings._iDisplayLength, - "iTotal": oSettings.fnRecordsTotal(), - "iFilteredTotal": oSettings.fnRecordsDisplay(), - "iPage": oSettings._iDisplayLength === -1 ? - 0 : Math.ceil( oSettings._iDisplayStart / oSettings._iDisplayLength ), - "iTotalPages": oSettings._iDisplayLength === -1 ? - 0 : Math.ceil( oSettings.fnRecordsDisplay() / oSettings._iDisplayLength ) - }; -}; +/* Bootstrap paging button renderer */ +DataTable.ext.renderer.pageButton.bootstrap = function ( settings, host, idx, buttons, page, pages ) { + var api = new DataTable.Api( settings ); + var classes = settings.oClasses; + var lang = settings.oLanguage.oPaginate; + var btnDisplay, btnClass; - -/* Bootstrap style pagination control */ -$.extend( $.fn.dataTableExt.oPagination, { - "bootstrap": { - "fnInit": function( oSettings, nPaging, fnDraw ) { - var oLang = oSettings.oLanguage.oPaginate; - var fnClickHandler = function ( e ) { - e.preventDefault(); - if ( oSettings.oApi._fnPageChange(oSettings, e.data.action) ) { - fnDraw( oSettings ); - } - }; - - $(nPaging).addClass('pagination').append( - '' - ); - var els = $('a', nPaging); - $(els[0]).bind( 'click.DT', { action: "previous" }, fnClickHandler ); - $(els[1]).bind( 'click.DT', { action: "next" }, fnClickHandler ); - }, - - "fnUpdate": function ( oSettings, fnDraw ) { - var iListLength = 5; - var oPaging = oSettings.oInstance.fnPagingInfo(); - var an = oSettings.aanFeatures.p; - var i, ien, j, sClass, iStart, iEnd, iHalf=Math.floor(iListLength/2); - - if ( oPaging.iTotalPages < iListLength) { - iStart = 1; - iEnd = oPaging.iTotalPages; + var attach = function( container, buttons ) { + var i, ien, node, button; + var clickHandler = function ( e ) { + e.preventDefault(); + if ( !$(e.currentTarget).hasClass('disabled') ) { + api.page( e.data.action ).draw( false ); } - else if ( oPaging.iPage <= iHalf ) { - iStart = 1; - iEnd = iListLength; - } else if ( oPaging.iPage >= (oPaging.iTotalPages-iHalf) ) { - iStart = oPaging.iTotalPages - iListLength + 1; - iEnd = oPaging.iTotalPages; - } else { - iStart = oPaging.iPage - iHalf + 1; - iEnd = iStart + iListLength - 1; + }; + + for ( i=0, ien=buttons.length ; i'+j+'') - .insertBefore( $('li:last', an[i])[0] ) - .bind('click', function (e) { - e.preventDefault(); - oSettings._iDisplayStart = (parseInt($('a', this).text(),10)-1) * oPaging.iLength; - fnDraw( oSettings ); - } ); + case 'first': + btnDisplay = lang.sFirst; + btnClass = button + (page > 0 ? + '' : ' disabled'); + break; + + case 'previous': + btnDisplay = lang.sPrevious; + btnClass = button + (page > 0 ? + '' : ' disabled'); + break; + + case 'next': + btnDisplay = lang.sNext; + btnClass = button + (page < pages-1 ? + '' : ' disabled'); + break; + + case 'last': + btnDisplay = lang.sLast; + btnClass = button + (page < pages-1 ? + '' : ' disabled'); + break; + + default: + btnDisplay = button + 1; + btnClass = page === button ? + 'active' : ''; + break; } - // Add / remove disabled classes from the static elements - if ( oPaging.iPage === 0 ) { - $('li:first', an[i]).addClass('disabled'); - } else { - $('li:first', an[i]).removeClass('disabled'); - } + if ( btnDisplay ) { + node = $('
  • ', { + 'class': classes.sPageButton+' '+btnClass, + 'aria-controls': settings.sTableId, + 'tabindex': settings.iTabIndex, + 'id': idx === 0 && typeof button === 'string' ? + settings.sTableId +'_'+ button : + null + } ) + .append( $('', { + 'href': '#' + } ) + .html( btnDisplay ) + ) + .appendTo( container ); - if ( oPaging.iPage === oPaging.iTotalPages-1 || oPaging.iTotalPages === 0 ) { - $('li:last', an[i]).addClass('disabled'); - } else { - $('li:last', an[i]).removeClass('disabled'); + settings.oApi._fnBindAction( + node, {action: button}, clickHandler + ); } } } - } -} ); + }; + + attach( + $(host).empty().html('').find('ul'), + buttons + ); +}; /* * TableTools Bootstrap compatibility * Required TableTools 2.1+ */ -if ( $.fn.DataTable.TableTools ) { +if ( DataTable.TableTools ) { // Set the classes that TableTools uses to something suitable for Bootstrap - $.extend( true, $.fn.DataTable.TableTools.classes, { + $.extend( true, DataTable.TableTools.classes, { "container": "DTTT btn-group", "buttons": { "normal": "btn", @@ -137,7 +149,7 @@ if ( $.fn.DataTable.TableTools ) { } ); // Have the collection use a bootstrap compatible dropdown - $.extend( true, $.fn.DataTable.TableTools.DEFAULTS.oTags, { + $.extend( true, DataTable.TableTools.DEFAULTS.oTags, { "collection": { "container": "ul", "button": "li", @@ -146,3 +158,5 @@ if ( $.fn.DataTable.TableTools ) { } ); } + +})(window, document, jQuery, jQuery.fn.dataTable); diff --git a/src/sunstone/public/bower_components/foundation-datatables/integration/bootstrap/2/dataTables.bootstrap.min.js b/src/sunstone/public/bower_components/foundation-datatables/integration/bootstrap/2/dataTables.bootstrap.min.js new file mode 100644 index 0000000000..4e5480d383 --- /dev/null +++ b/src/sunstone/public/bower_components/foundation-datatables/integration/bootstrap/2/dataTables.bootstrap.min.js @@ -0,0 +1,8 @@ +/*! + DataTables Bootstrap 2 integration + ©2011-2014 SpryMedia Ltd - datatables.net/license +*/ +(function(t,u,c,b){c.extend(!0,b.defaults,{dom:"<'row-fluid'<'span6'l><'span6'f>r><'row-fluid'<'span12't>><'row-fluid'<'span6'i><'span6'p>>",renderer:"bootstrap"});c.extend(b.ext.classes,{sWrapper:"dataTables_wrapper form-inline dt-bootstrap"});b.ext.renderer.pageButton.bootstrap=function(f,j,p,q,g,k){var r=new b.Api(f),s=f.oClasses,h=f.oLanguage.oPaginate,d,e,o=function(b,l){var i,m,n,a,j=function(a){a.preventDefault();c(a.currentTarget).hasClass("disabled")||r.page(a.data.action).draw(!1)};i=0; +for(m=l.length;i",{"class":s.sPageButton+" "+e,"aria-controls":f.sTableId,tabindex:f.iTabIndex,id:0===p&&"string"===typeof a? +f.sTableId+"_"+a:null}).append(c("",{href:"#"}).html(d)).appendTo(b),f.oApi._fnBindAction(n,{action:a},j))}};o(c(j).empty().html('').find("ul"),q)};b.TableTools&&(c.extend(!0,b.TableTools.classes,{container:"DTTT btn-group",buttons:{normal:"btn",disabled:"disabled"},collection:{container:"DTTT_dropdown dropdown-menu",buttons:{normal:"",disabled:"disabled"}},print:{info:"DTTT_print_info modal"},select:{row:"active"}}),c.extend(!0,b.TableTools.DEFAULTS.oTags,{collection:{container:"ul", +button:"li",liner:"a"}}))})(window,document,jQuery,jQuery.fn.dataTable); diff --git a/src/sunstone/public/bower_components/foundation-datatables/integration/bootstrap/2/index.html b/src/sunstone/public/bower_components/foundation-datatables/integration/bootstrap/2/index.html index bdd8048edc..4334d67d21 100644 --- a/src/sunstone/public/bower_components/foundation-datatables/integration/bootstrap/2/index.html +++ b/src/sunstone/public/bower_components/foundation-datatables/integration/bootstrap/2/index.html @@ -5,11 +5,11 @@ DataTables Bootstrap 2 example - + - - + + - + + "; }, footer: "\n\n" }; - template.header += "\n" + template.script_tag(path.resolve(__dirname, 'node_modules/chai/chai.js')); - template.header += "\n" + template.script_tag(path.resolve(__dirname, 'node_modules/mocha/mocha.js')); - template.header += "\n" + template.script_tag(path.resolve(__dirname, 'node_modules/sinon/pkg/sinon.js')); - template.header += "\n" + template.script_tag(path.resolve(__dirname, 'node_modules/sinon-chai/lib/sinon-chai.js')); - template.header += "\n" + template.script_tag(path.resolve(__dirname, 'node_modules/sinon-chai/lib/sinon-chai.js')); + template.header += "\n" + template.script_tag(get_path('node_modules/chai/chai.js')); + template.header += "\n" + template.script_tag(get_path('node_modules/mocha/mocha.js')); + template.header += "\n" + template.script_tag(get_path('node_modules/sinon/pkg/sinon.js')); + template.header += "\n" + template.script_tag(get_path('node_modules/sinon-chai/lib/sinon-chai.js')); + template.header += "\n" + template.script_tag(get_path('node_modules/sinon-chai/lib/sinon-chai.js')); template.header += "\n"; template.header = program.autoInject.reduce(function(acc, sn) { - return acc + "\n" + template.script_tag(path.resolve(process.cwd(), sn)); + return acc + "\n" + template.script_tag(get_path_cwd(sn)); }, template.header); file_paths = program.tests.map(function(jsn, ind) { var templ = template.header; templ += "\n"; - templ += template.script_tag(path.resolve(process.cwd(), jsn)); + templ += template.script_tag(get_path_cwd(jsn)); templ += template.footer; var tempfile = temp.openSync({ prefix: 'novnc-zombie-inject-', suffix: '-file_num-'+ind+'.html' }); @@ -117,15 +140,19 @@ if (program.outputHtml) { return; } + if (use_ansi) { + cursor + .bold() + .write(program.tests[path_ind]) + .reset() + .write("\n") + .write(Array(program.tests[path_ind].length+1).join('=')) + .write("\n\n"); + } + cursor - .bold() - .write(program.tests[path_ind]) - .reset() - .write("\n") - .write(Array(program.tests[path_ind].length+1).join('=')) - .write("\n\n") .write(data) - .write("\n"); + .write("\n\n"); }); }); } diff --git a/src/sunstone/public/bower_components/no-vnc/tests/test.base64.js b/src/sunstone/public/bower_components/no-vnc/tests/test.base64.js new file mode 100644 index 0000000000..b2646a0ffe --- /dev/null +++ b/src/sunstone/public/bower_components/no-vnc/tests/test.base64.js @@ -0,0 +1,33 @@ +// requires local modules: base64 +var assert = chai.assert; +var expect = chai.expect; + +describe('Base64 Tools', function() { + "use strict"; + + var BIN_ARR = new Array(256); + for (var i = 0; i < 256; i++) { + BIN_ARR[i] = i; + } + + var B64_STR = "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w=="; + + + describe('encode', function() { + it('should encode a binary string into Base64', function() { + var encoded = Base64.encode(BIN_ARR); + expect(encoded).to.equal(B64_STR); + }); + }); + + describe('decode', function() { + it('should decode a Base64 string into a normal string', function() { + var decoded = Base64.decode(B64_STR); + expect(decoded).to.deep.equal(BIN_ARR); + }); + + it('should throw an error if we have extra characters at the end of the string', function() { + expect(function () { Base64.decode(B64_STR+'abcdef'); }).to.throw(Error); + }); + }); +}); diff --git a/src/sunstone/public/bower_components/no-vnc/tests/test.display.js b/src/sunstone/public/bower_components/no-vnc/tests/test.display.js new file mode 100644 index 0000000000..25adfbeacc --- /dev/null +++ b/src/sunstone/public/bower_components/no-vnc/tests/test.display.js @@ -0,0 +1,353 @@ +// requires local modules: util, base64, display +// requires test modules: assertions +/* jshint expr: true */ +var expect = chai.expect; + +describe('Display/Canvas Helper', function () { + var checked_data = [ + 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255 + ]; + checked_data = new Uint8Array(checked_data); + + var basic_data = [0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0xff, 0xff, 0xff, 255]; + basic_data = new Uint8Array(basic_data); + + function make_image_canvas (input_data) { + var canvas = document.createElement('canvas'); + canvas.width = 4; + canvas.height = 4; + var ctx = canvas.getContext('2d'); + var data = ctx.createImageData(4, 4); + for (var i = 0; i < checked_data.length; i++) { data.data[i] = input_data[i]; } + ctx.putImageData(data, 0, 0); + return canvas; + } + + describe('checking for cursor uri support', function () { + beforeEach(function () { + this._old_change_cursor = Display.changeCursor; + }); + + it('should disable cursor URIs if there is no support', function () { + Display.changeCursor = function(target) { + target.style.cursor = undefined; + }; + var display = new Display({ target: document.createElement('canvas'), prefer_js: true, viewport: false }); + expect(display._cursor_uri).to.be.false; + }); + + it('should enable cursor URIs if there is support', function () { + Display.changeCursor = function(target) { + target.style.cursor = 'pointer'; + }; + var display = new Display({ target: document.createElement('canvas'), prefer_js: true, viewport: false }); + expect(display._cursor_uri).to.be.true; + }); + + it('respect the cursor_uri option if there is support', function () { + Display.changeCursor = function(target) { + target.style.cursor = 'pointer'; + }; + var display = new Display({ target: document.createElement('canvas'), prefer_js: true, viewport: false, cursor_uri: false }); + expect(display._cursor_uri).to.be.false; + }); + + afterEach(function () { + Display.changeCursor = this._old_change_cursor; + }); + }); + + describe('viewport handling', function () { + var display; + beforeEach(function () { + display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: true }); + display.resize(5, 5); + display.viewportChange(1, 1, 3, 3); + display.getCleanDirtyReset(); + }); + + it('should take viewport location into consideration when drawing images', function () { + display.resize(4, 4); + display.viewportChange(0, 0, 2, 2); + display.drawImage(make_image_canvas(basic_data), 1, 1); + + var expected = new Uint8Array(16); + var i; + for (i = 0; i < 8; i++) { expected[i] = basic_data[i]; } + for (i = 8; i < 16; i++) { expected[i] = 0; } + expect(display).to.have.displayed(expected); + }); + + it('should redraw the left side when shifted left', function () { + display.viewportChange(-1, 0, 3, 3); + var cdr = display.getCleanDirtyReset(); + expect(cdr.cleanBox).to.deep.equal({ x: 1, y: 1, w: 2, h: 3 }); + expect(cdr.dirtyBoxes).to.have.length(1); + expect(cdr.dirtyBoxes[0]).to.deep.equal({ x: 0, y: 1, w: 2, h: 3 }); + }); + + it('should redraw the right side when shifted right', function () { + display.viewportChange(1, 0, 3, 3); + var cdr = display.getCleanDirtyReset(); + expect(cdr.cleanBox).to.deep.equal({ x: 2, y: 1, w: 2, h: 3 }); + expect(cdr.dirtyBoxes).to.have.length(1); + expect(cdr.dirtyBoxes[0]).to.deep.equal({ x: 4, y: 1, w: 1, h: 3 }); + }); + + it('should redraw the top part when shifted up', function () { + display.viewportChange(0, -1, 3, 3); + var cdr = display.getCleanDirtyReset(); + expect(cdr.cleanBox).to.deep.equal({ x: 1, y: 1, w: 3, h: 2 }); + expect(cdr.dirtyBoxes).to.have.length(1); + expect(cdr.dirtyBoxes[0]).to.deep.equal({ x: 1, y: 0, w: 3, h: 1 }); + }); + + it('should redraw the bottom part when shifted down', function () { + display.viewportChange(0, 1, 3, 3); + var cdr = display.getCleanDirtyReset(); + expect(cdr.cleanBox).to.deep.equal({ x: 1, y: 2, w: 3, h: 2 }); + expect(cdr.dirtyBoxes).to.have.length(1); + expect(cdr.dirtyBoxes[0]).to.deep.equal({ x: 1, y: 4, w: 3, h: 1 }); + }); + + it('should reset the entire viewport to being clean after calculating the clean/dirty boxes', function () { + display.viewportChange(0, 1, 3, 3); + var cdr1 = display.getCleanDirtyReset(); + var cdr2 = display.getCleanDirtyReset(); + expect(cdr1).to.not.deep.equal(cdr2); + expect(cdr2.cleanBox).to.deep.equal({ x: 1, y: 2, w: 3, h: 3 }); + expect(cdr2.dirtyBoxes).to.be.empty; + }); + + it('should simply mark the whole display area as dirty if not using viewports', function () { + display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: false }); + display.resize(5, 5); + var cdr = display.getCleanDirtyReset(); + expect(cdr.cleanBox).to.deep.equal({ x: 0, y: 0, w: 0, h: 0 }); + expect(cdr.dirtyBoxes).to.have.length(1); + expect(cdr.dirtyBoxes[0]).to.deep.equal({ x: 0, y: 0, w: 5, h: 5 }); + }); + }); + + describe('resizing', function () { + var display; + beforeEach(function () { + display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: true }); + display.resize(4, 3); + }); + + it('should change the size of the logical canvas', function () { + display.resize(5, 7); + expect(display._fb_width).to.equal(5); + expect(display._fb_height).to.equal(7); + }); + + it('should update the viewport dimensions', function () { + sinon.spy(display, 'viewportChange'); + display.resize(2, 2); + expect(display.viewportChange).to.have.been.calledOnce; + }); + }); + + describe('drawing', function () { + + // TODO(directxman12): improve the tests for each of the drawing functions to cover more than just the + // basic cases + function drawing_tests (pref_js) { + var display; + beforeEach(function () { + display = new Display({ target: document.createElement('canvas'), prefer_js: pref_js }); + display.resize(4, 4); + }); + + it('should clear the screen on #clear without a logo set', function () { + display.fillRect(0, 0, 4, 4, [0x00, 0x00, 0xff]); + display._logo = null; + display.clear(); + display.resize(4, 4); + var empty = []; + for (var i = 0; i < 4 * display._fb_width * display._fb_height; i++) { empty[i] = 0; } + expect(display).to.have.displayed(new Uint8Array(empty)); + }); + + it('should draw the logo on #clear with a logo set', function (done) { + display._logo = { width: 4, height: 4, data: make_image_canvas(checked_data).toDataURL() }; + display._drawCtx._act_drawImg = display._drawCtx.drawImage; + display._drawCtx.drawImage = function (img, x, y) { + this._act_drawImg(img, x, y); + expect(display).to.have.displayed(checked_data); + done(); + }; + display.clear(); + expect(display._fb_width).to.equal(4); + expect(display._fb_height).to.equal(4); + }); + + it('should support filling a rectangle with particular color via #fillRect', function () { + display.fillRect(0, 0, 4, 4, [0, 0xff, 0]); + display.fillRect(0, 0, 2, 2, [0xff, 0, 0]); + display.fillRect(2, 2, 2, 2, [0xff, 0, 0]); + expect(display).to.have.displayed(checked_data); + }); + + it('should support copying an portion of the canvas via #copyImage', function () { + display.fillRect(0, 0, 4, 4, [0, 0xff, 0]); + display.fillRect(0, 0, 2, 2, [0xff, 0, 0x00]); + display.copyImage(0, 0, 2, 2, 2, 2); + expect(display).to.have.displayed(checked_data); + }); + + it('should support drawing tile data with a background color and sub tiles', function () { + display.startTile(0, 0, 4, 4, [0, 0xff, 0]); + display.subTile(0, 0, 2, 2, [0xff, 0, 0]); + display.subTile(2, 2, 2, 2, [0xff, 0, 0]); + display.finishTile(); + expect(display).to.have.displayed(checked_data); + }); + + it('should support drawing BGRX blit images with true color via #blitImage', function () { + var data = []; + for (var i = 0; i < 16; i++) { + data[i * 4] = checked_data[i * 4 + 2]; + data[i * 4 + 1] = checked_data[i * 4 + 1]; + data[i * 4 + 2] = checked_data[i * 4]; + data[i * 4 + 3] = checked_data[i * 4 + 3]; + } + display.blitImage(0, 0, 4, 4, data, 0); + expect(display).to.have.displayed(checked_data); + }); + + it('should support drawing RGB blit images with true color via #blitRgbImage', function () { + var data = []; + for (var i = 0; i < 16; i++) { + data[i * 3] = checked_data[i * 4]; + data[i * 3 + 1] = checked_data[i * 4 + 1]; + data[i * 3 + 2] = checked_data[i * 4 + 2]; + } + display.blitRgbImage(0, 0, 4, 4, data, 0); + expect(display).to.have.displayed(checked_data); + }); + + it('should support drawing blit images from a data URL via #blitStringImage', function (done) { + var img_url = make_image_canvas(checked_data).toDataURL(); + display._drawCtx._act_drawImg = display._drawCtx.drawImage; + display._drawCtx.drawImage = function (img, x, y) { + this._act_drawImg(img, x, y); + expect(display).to.have.displayed(checked_data); + done(); + }; + display.blitStringImage(img_url, 0, 0); + }); + + it('should support drawing solid colors with color maps', function () { + display._true_color = false; + display.set_colourMap({ 0: [0xff, 0, 0], 1: [0, 0xff, 0] }); + display.fillRect(0, 0, 4, 4, [1]); + display.fillRect(0, 0, 2, 2, [0]); + display.fillRect(2, 2, 2, 2, [0]); + expect(display).to.have.displayed(checked_data); + }); + + it('should support drawing blit images with color maps', function () { + display._true_color = false; + display.set_colourMap({ 1: [0xff, 0, 0], 0: [0, 0xff, 0] }); + var data = [1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1].map(function (elem) { return [elem]; }); + display.blitImage(0, 0, 4, 4, data, 0); + expect(display).to.have.displayed(checked_data); + }); + + it('should support drawing an image object via #drawImage', function () { + var img = make_image_canvas(checked_data); + display.drawImage(img, 0, 0); + expect(display).to.have.displayed(checked_data); + }); + } + + describe('(prefering native methods)', function () { drawing_tests.call(this, false); }); + describe('(prefering JavaScript)', function () { drawing_tests.call(this, true); }); + }); + + describe('the render queue processor', function () { + var display; + beforeEach(function () { + display = new Display({ target: document.createElement('canvas'), prefer_js: false }); + display.resize(4, 4); + sinon.spy(display, '_scan_renderQ'); + this.old_requestAnimFrame = window.requestAnimFrame; + window.requestAnimFrame = function (cb) { + this.next_frame_cb = cb; + }.bind(this); + this.next_frame = function () { this.next_frame_cb(); }; + }); + + afterEach(function () { + window.requestAnimFrame = this.old_requestAnimFrame; + }); + + it('should try to process an item when it is pushed on, if nothing else is on the queue', function () { + display.renderQ_push({ type: 'noop' }); // does nothing + expect(display._scan_renderQ).to.have.been.calledOnce; + }); + + it('should not try to process an item when it is pushed on if we are waiting for other items', function () { + display._renderQ.length = 2; + display.renderQ_push({ type: 'noop' }); + expect(display._scan_renderQ).to.not.have.been.called; + }); + + it('should wait until an image is loaded to attempt to draw it and the rest of the queue', function () { + var img = { complete: false }; + display._renderQ = [{ type: 'img', x: 3, y: 4, img: img }, + { type: 'fill', x: 1, y: 2, width: 3, height: 4, color: 5 }]; + display.drawImage = sinon.spy(); + display.fillRect = sinon.spy(); + + display._scan_renderQ(); + expect(display.drawImage).to.not.have.been.called; + expect(display.fillRect).to.not.have.been.called; + + display._renderQ[0].img.complete = true; + this.next_frame(); + expect(display.drawImage).to.have.been.calledOnce; + expect(display.fillRect).to.have.been.calledOnce; + }); + + it('should draw a blit image on type "blit"', function () { + display.blitImage = sinon.spy(); + display.renderQ_push({ type: 'blit', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9] }); + expect(display.blitImage).to.have.been.calledOnce; + expect(display.blitImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0); + }); + + it('should draw a blit RGB image on type "blitRgb"', function () { + display.blitRgbImage = sinon.spy(); + display.renderQ_push({ type: 'blitRgb', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9] }); + expect(display.blitRgbImage).to.have.been.calledOnce; + expect(display.blitRgbImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0); + }); + + it('should copy a region on type "copy"', function () { + display.copyImage = sinon.spy(); + display.renderQ_push({ type: 'copy', x: 3, y: 4, width: 5, height: 6, old_x: 7, old_y: 8 }); + expect(display.copyImage).to.have.been.calledOnce; + expect(display.copyImage).to.have.been.calledWith(7, 8, 3, 4, 5, 6); + }); + + it('should fill a rect with a given color on type "fill"', function () { + display.fillRect = sinon.spy(); + display.renderQ_push({ type: 'fill', x: 3, y: 4, width: 5, height: 6, color: [7, 8, 9]}); + expect(display.fillRect).to.have.been.calledOnce; + expect(display.fillRect).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9]); + }); + + it('should draw an image from an image object on type "img" (if complete)', function () { + display.drawImage = sinon.spy(); + display.renderQ_push({ type: 'img', x: 3, y: 4, img: { complete: true } }); + expect(display.drawImage).to.have.been.calledOnce; + expect(display.drawImage).to.have.been.calledWith({ complete: true }, 3, 4); + }); + }); +}); diff --git a/src/sunstone/public/bower_components/no-vnc/tests/test.helper.js b/src/sunstone/public/bower_components/no-vnc/tests/test.helper.js index d9e8e14437..98009d2abb 100644 --- a/src/sunstone/public/bower_components/no-vnc/tests/test.helper.js +++ b/src/sunstone/public/bower_components/no-vnc/tests/test.helper.js @@ -1,4 +1,6 @@ -var assert = chai.assert; +// requires local modules: keysym, keysymdef, keyboard + +var assert = chai.assert; var expect = chai.expect; describe('Helpers', function() { diff --git a/src/sunstone/public/bower_components/no-vnc/tests/test.keyboard.js b/src/sunstone/public/bower_components/no-vnc/tests/test.keyboard.js index 80d1fee4c1..2ac65af399 100644 --- a/src/sunstone/public/bower_components/no-vnc/tests/test.keyboard.js +++ b/src/sunstone/public/bower_components/no-vnc/tests/test.keyboard.js @@ -1,7 +1,8 @@ +// requires local modules: input, keyboard, keysymdef var assert = chai.assert; var expect = chai.expect; - +/* jshint newcap: false, expr: true */ describe('Key Event Pipeline Stages', function() { "use strict"; describe('Decode Keyboard Events', function() { @@ -50,7 +51,7 @@ describe('Key Event Pipeline Stages', function() { KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) { expect(evt).to.be.deep.equal({keyId: 0x41, type: 'keydown'}); done(); - }).keydown({keyCode: 0x41}) + }).keydown({keyCode: 0x41}); }); it('should forward keyup events with the right type', function(done) { KeyEventDecoder(kbdUtil.ModifierSync(), function(evt) { diff --git a/src/sunstone/public/bower_components/no-vnc/tests/test.rfb.js b/src/sunstone/public/bower_components/no-vnc/tests/test.rfb.js new file mode 100644 index 0000000000..1510f9e5e1 --- /dev/null +++ b/src/sunstone/public/bower_components/no-vnc/tests/test.rfb.js @@ -0,0 +1,1716 @@ +// requires local modules: util, base64, websock, rfb, keyboard, keysym, keysymdef, input, jsunzip, des, display +// requires test modules: fake.websocket, assertions +/* jshint expr: true */ +var assert = chai.assert; +var expect = chai.expect; + +function make_rfb (extra_opts) { + if (!extra_opts) { + extra_opts = {}; + } + + extra_opts.target = extra_opts.target || document.createElement('canvas'); + return new RFB(extra_opts); +} + +describe('Remote Frame Buffer Protocol Client', function() { + "use strict"; + before(FakeWebSocket.replace); + after(FakeWebSocket.restore); + + describe('Public API Basic Behavior', function () { + var client; + beforeEach(function () { + client = make_rfb(); + }); + + describe('#connect', function () { + beforeEach(function () { client._updateState = sinon.spy(); }); + + it('should set the current state to "connect"', function () { + client.connect('host', 8675); + expect(client._updateState).to.have.been.calledOnce; + expect(client._updateState).to.have.been.calledWith('connect'); + }); + + it('should fail if we are missing a host', function () { + sinon.spy(client, '_fail'); + client.connect(undefined, 8675); + expect(client._fail).to.have.been.calledOnce; + }); + + it('should fail if we are missing a port', function () { + sinon.spy(client, '_fail'); + client.connect('abc'); + expect(client._fail).to.have.been.calledOnce; + }); + + it('should not update the state if we are missing a host or port', function () { + sinon.spy(client, '_fail'); + client.connect('abc'); + expect(client._fail).to.have.been.calledOnce; + expect(client._updateState).to.have.been.calledOnce; + expect(client._updateState).to.have.been.calledWith('failed'); + }); + }); + + describe('#disconnect', function () { + beforeEach(function () { client._updateState = sinon.spy(); }); + + it('should set the current state to "disconnect"', function () { + client.disconnect(); + expect(client._updateState).to.have.been.calledOnce; + expect(client._updateState).to.have.been.calledWith('disconnect'); + }); + }); + + describe('#sendPassword', function () { + beforeEach(function () { this.clock = sinon.useFakeTimers(); }); + afterEach(function () { this.clock.restore(); }); + + it('should set the state to "Authentication"', function () { + client._rfb_state = "blah"; + client.sendPassword('pass'); + expect(client._rfb_state).to.equal('Authentication'); + }); + + it('should call init_msg "soon"', function () { + client._init_msg = sinon.spy(); + client.sendPassword('pass'); + this.clock.tick(5); + expect(client._init_msg).to.have.been.calledOnce; + }); + }); + + describe('#sendCtrlAlDel', function () { + beforeEach(function () { + client._sock = new Websock(); + client._sock.open('ws://', 'binary'); + client._sock._websocket._open(); + sinon.spy(client._sock, 'send'); + client._rfb_state = "normal"; + client._view_only = false; + }); + + it('should sent ctrl[down]-alt[down]-del[down] then del[up]-alt[up]-ctrl[up]', function () { + var expected = []; + expected = expected.concat(RFB.messages.keyEvent(0xFFE3, 1)); + expected = expected.concat(RFB.messages.keyEvent(0xFFE9, 1)); + expected = expected.concat(RFB.messages.keyEvent(0xFFFF, 1)); + expected = expected.concat(RFB.messages.keyEvent(0xFFFF, 0)); + expected = expected.concat(RFB.messages.keyEvent(0xFFE9, 0)); + expected = expected.concat(RFB.messages.keyEvent(0xFFE3, 0)); + + client.sendCtrlAltDel(); + expect(client._sock).to.have.sent(expected); + }); + + it('should not send the keys if we are not in a normal state', function () { + client._rfb_state = "broken"; + client.sendCtrlAltDel(); + expect(client._sock.send).to.not.have.been.called; + }); + + it('should not send the keys if we are set as view_only', function () { + client._view_only = true; + client.sendCtrlAltDel(); + expect(client._sock.send).to.not.have.been.called; + }); + }); + + describe('#sendKey', function () { + beforeEach(function () { + client._sock = new Websock(); + client._sock.open('ws://', 'binary'); + client._sock._websocket._open(); + sinon.spy(client._sock, 'send'); + client._rfb_state = "normal"; + client._view_only = false; + }); + + it('should send a single key with the given code and state (down = true)', function () { + var expected = RFB.messages.keyEvent(123, 1); + client.sendKey(123, true); + expect(client._sock).to.have.sent(expected); + }); + + it('should send both a down and up event if the state is not specified', function () { + var expected = RFB.messages.keyEvent(123, 1); + expected = expected.concat(RFB.messages.keyEvent(123, 0)); + client.sendKey(123); + expect(client._sock).to.have.sent(expected); + }); + + it('should not send the key if we are not in a normal state', function () { + client._rfb_state = "broken"; + client.sendKey(123); + expect(client._sock.send).to.not.have.been.called; + }); + + it('should not send the key if we are set as view_only', function () { + client._view_only = true; + client.sendKey(123); + expect(client._sock.send).to.not.have.been.called; + }); + }); + + describe('#clipboardPasteFrom', function () { + beforeEach(function () { + client._sock = new Websock(); + client._sock.open('ws://', 'binary'); + client._sock._websocket._open(); + sinon.spy(client._sock, 'send'); + client._rfb_state = "normal"; + client._view_only = false; + }); + + it('should send the given text in a paste event', function () { + var expected = RFB.messages.clientCutText('abc'); + client.clipboardPasteFrom('abc'); + expect(client._sock).to.have.sent(expected); + }); + + it('should not send the text if we are not in a normal state', function () { + client._rfb_state = "broken"; + client.clipboardPasteFrom('abc'); + expect(client._sock.send).to.not.have.been.called; + }); + }); + + describe("XVP operations", function () { + beforeEach(function () { + client._sock = new Websock(); + client._sock.open('ws://', 'binary'); + client._sock._websocket._open(); + sinon.spy(client._sock, 'send'); + client._rfb_state = "normal"; + client._view_only = false; + client._rfb_xvp_ver = 1; + }); + + it('should send the shutdown signal on #xvpShutdown', function () { + client.xvpShutdown(); + expect(client._sock).to.have.sent([0xFA, 0x00, 0x01, 0x02]); + }); + + it('should send the reboot signal on #xvpReboot', function () { + client.xvpReboot(); + expect(client._sock).to.have.sent([0xFA, 0x00, 0x01, 0x03]); + }); + + it('should send the reset signal on #xvpReset', function () { + client.xvpReset(); + expect(client._sock).to.have.sent([0xFA, 0x00, 0x01, 0x04]); + }); + + it('should support sending arbitrary XVP operations via #xvpOp', function () { + client.xvpOp(1, 7); + expect(client._sock).to.have.sent([0xFA, 0x00, 0x01, 0x07]); + }); + + it('should not send XVP operations with higher versions than we support', function () { + expect(client.xvpOp(2, 7)).to.be.false; + expect(client._sock.send).to.not.have.been.called; + }); + }); + }); + + describe('Misc Internals', function () { + describe('#_updateState', function () { + var client; + beforeEach(function () { + this.clock = sinon.useFakeTimers(); + client = make_rfb(); + }); + + afterEach(function () { + this.clock.restore(); + }); + + it('should clear the disconnect timer if the state is not disconnect', function () { + var spy = sinon.spy(); + client._disconnTimer = setTimeout(spy, 50); + client._updateState('normal'); + this.clock.tick(51); + expect(spy).to.not.have.been.called; + expect(client._disconnTimer).to.be.null; + }); + }); + }); + + describe('Page States', function () { + describe('loaded', function () { + var client; + beforeEach(function () { client = make_rfb(); }); + + it('should close any open WebSocket connection', function () { + sinon.spy(client._sock, 'close'); + client._updateState('loaded'); + expect(client._sock.close).to.have.been.calledOnce; + }); + }); + + describe('disconnected', function () { + var client; + beforeEach(function () { client = make_rfb(); }); + + it('should close any open WebSocket connection', function () { + sinon.spy(client._sock, 'close'); + client._updateState('disconnected'); + expect(client._sock.close).to.have.been.calledOnce; + }); + }); + + describe('connect', function () { + var client; + beforeEach(function () { client = make_rfb(); }); + + it('should reset the variable states', function () { + sinon.spy(client, '_init_vars'); + client._updateState('connect'); + expect(client._init_vars).to.have.been.calledOnce; + }); + + it('should actually connect to the websocket', function () { + sinon.spy(client._sock, 'open'); + client._updateState('connect'); + expect(client._sock.open).to.have.been.calledOnce; + }); + + it('should use wss:// to connect if encryption is enabled', function () { + sinon.spy(client._sock, 'open'); + client.set_encrypt(true); + client._updateState('connect'); + expect(client._sock.open.args[0][0]).to.contain('wss://'); + }); + + it('should use ws:// to connect if encryption is not enabled', function () { + sinon.spy(client._sock, 'open'); + client.set_encrypt(true); + client._updateState('connect'); + expect(client._sock.open.args[0][0]).to.contain('wss://'); + }); + + it('should use a uri with the host, port, and path specified to connect', function () { + sinon.spy(client._sock, 'open'); + client.set_encrypt(false); + client._rfb_host = 'HOST'; + client._rfb_port = 8675; + client._rfb_path = 'PATH'; + client._updateState('connect'); + expect(client._sock.open).to.have.been.calledWith('ws://HOST:8675/PATH'); + }); + + it('should attempt to close the websocket before we open an new one', function () { + sinon.spy(client._sock, 'close'); + client._updateState('connect'); + expect(client._sock.close).to.have.been.calledOnce; + }); + }); + + describe('disconnect', function () { + var client; + beforeEach(function () { + this.clock = sinon.useFakeTimers(); + client = make_rfb(); + client.connect('host', 8675); + }); + + afterEach(function () { + this.clock.restore(); + }); + + it('should fail if we do not call Websock.onclose within the disconnection timeout', function () { + client._sock._websocket.close = function () {}; // explicitly don't call onclose + client._updateState('disconnect'); + this.clock.tick(client.get_disconnectTimeout() * 1000); + expect(client._rfb_state).to.equal('failed'); + }); + + it('should not fail if Websock.onclose gets called within the disconnection timeout', function () { + client._updateState('disconnect'); + this.clock.tick(client.get_disconnectTimeout() * 500); + client._sock._websocket.close(); + this.clock.tick(client.get_disconnectTimeout() * 500 + 1); + expect(client._rfb_state).to.equal('disconnected'); + }); + + it('should close the WebSocket connection', function () { + sinon.spy(client._sock, 'close'); + client._updateState('disconnect'); + expect(client._sock.close).to.have.been.calledTwice; // once on loaded, once on disconnect + }); + }); + + describe('failed', function () { + var client; + beforeEach(function () { + this.clock = sinon.useFakeTimers(); + client = make_rfb(); + client.connect('host', 8675); + }); + + afterEach(function () { + this.clock.restore(); + }); + + it('should close the WebSocket connection', function () { + sinon.spy(client._sock, 'close'); + client._updateState('failed'); + expect(client._sock.close).to.have.been.called; + }); + + it('should transition to disconnected but stay in failed state', function () { + client.set_onUpdateState(sinon.spy()); + client._updateState('failed'); + this.clock.tick(50); + expect(client._rfb_state).to.equal('failed'); + + var onUpdateState = client.get_onUpdateState(); + expect(onUpdateState).to.have.been.called; + // it should be specifically the last call + expect(onUpdateState.args[onUpdateState.args.length - 1][1]).to.equal('disconnected'); + expect(onUpdateState.args[onUpdateState.args.length - 1][2]).to.equal('failed'); + }); + + }); + + describe('fatal', function () { + var client; + beforeEach(function () { client = make_rfb(); }); + + it('should close any open WebSocket connection', function () { + sinon.spy(client._sock, 'close'); + client._updateState('fatal'); + expect(client._sock.close).to.have.been.calledOnce; + }); + }); + + // NB(directxman12): Normal does *nothing* in updateState + }); + + describe('Protocol Initialization States', function () { + describe('ProtocolVersion', function () { + beforeEach(function () { + this.clock = sinon.useFakeTimers(); + }); + + afterEach(function () { + this.clock.restore(); + }); + + function send_ver (ver, client) { + var arr = new Uint8Array(12); + for (var i = 0; i < ver.length; i++) { + arr[i+4] = ver.charCodeAt(i); + } + arr[0] = 'R'; arr[1] = 'F'; arr[2] = 'B'; arr[3] = ' '; + arr[11] = '\n'; + client._sock._websocket._receive_data(arr); + } + + describe('version parsing', function () { + var client; + beforeEach(function () { + client = make_rfb(); + client.connect('host', 8675); + client._sock._websocket._open(); + }); + + it('should interpret version 000.000 as a repeater', function () { + client._repeaterID = '\x01\x02\x03\x04\x05'; + send_ver('000.000', client); + expect(client._rfb_version).to.equal(0); + + var sent_data = client._sock._websocket._get_sent_data(); + expect(sent_data.slice(0, 5)).to.deep.equal([1, 2, 3, 4, 5]); + }); + + it('should interpret version 003.003 as version 3.3', function () { + send_ver('003.003', client); + expect(client._rfb_version).to.equal(3.3); + }); + + it('should interpret version 003.006 as version 3.3', function () { + send_ver('003.006', client); + expect(client._rfb_version).to.equal(3.3); + }); + + it('should interpret version 003.889 as version 3.3', function () { + send_ver('003.889', client); + expect(client._rfb_version).to.equal(3.3); + }); + + it('should interpret version 003.007 as version 3.7', function () { + send_ver('003.007', client); + expect(client._rfb_version).to.equal(3.7); + }); + + it('should interpret version 003.008 as version 3.8', function () { + send_ver('003.008', client); + expect(client._rfb_version).to.equal(3.8); + }); + + it('should interpret version 004.000 as version 3.8', function () { + send_ver('004.000', client); + expect(client._rfb_version).to.equal(3.8); + }); + + it('should interpret version 004.001 as version 3.8', function () { + send_ver('004.001', client); + expect(client._rfb_version).to.equal(3.8); + }); + + it('should fail on an invalid version', function () { + send_ver('002.000', client); + expect(client._rfb_state).to.equal('failed'); + }); + }); + + var client; + beforeEach(function () { + client = make_rfb(); + client.connect('host', 8675); + client._sock._websocket._open(); + }); + + it('should handle two step repeater negotiation', function () { + client._repeaterID = '\x01\x02\x03\x04\x05'; + + send_ver('000.000', client); + expect(client._rfb_version).to.equal(0); + var sent_data = client._sock._websocket._get_sent_data(); + expect(sent_data.slice(0, 5)).to.deep.equal([1, 2, 3, 4, 5]); + expect(sent_data).to.have.length(250); + + send_ver('003.008', client); + expect(client._rfb_version).to.equal(3.8); + }); + + it('should initialize the flush interval', function () { + client._sock.flush = sinon.spy(); + send_ver('003.008', client); + this.clock.tick(100); + expect(client._sock.flush).to.have.been.calledThrice; + }); + + it('should send back the interpreted version', function () { + send_ver('004.000', client); + + var expected_str = 'RFB 003.008\n'; + var expected = []; + for (var i = 0; i < expected_str.length; i++) { + expected[i] = expected_str.charCodeAt(i); + } + + expect(client._sock).to.have.sent(expected); + }); + + it('should transition to the Security state on successful negotiation', function () { + send_ver('003.008', client); + expect(client._rfb_state).to.equal('Security'); + }); + }); + + describe('Security', function () { + var client; + + beforeEach(function () { + client = make_rfb(); + client.connect('host', 8675); + client._sock._websocket._open(); + client._rfb_state = 'Security'; + }); + + it('should simply receive the auth scheme when for versions < 3.7', function () { + client._rfb_version = 3.6; + var auth_scheme_raw = [1, 2, 3, 4]; + var auth_scheme = (auth_scheme_raw[0] << 24) + (auth_scheme_raw[1] << 16) + + (auth_scheme_raw[2] << 8) + auth_scheme_raw[3]; + client._sock._websocket._receive_data(auth_scheme_raw); + expect(client._rfb_auth_scheme).to.equal(auth_scheme); + }); + + it('should choose for the most prefered scheme possible for versions >= 3.7', function () { + client._rfb_version = 3.7; + var auth_schemes = [2, 1, 2]; + client._sock._websocket._receive_data(auth_schemes); + expect(client._rfb_auth_scheme).to.equal(2); + expect(client._sock).to.have.sent([2]); + }); + + it('should fail if there are no supported schemes for versions >= 3.7', function () { + client._rfb_version = 3.7; + var auth_schemes = [1, 32]; + client._sock._websocket._receive_data(auth_schemes); + expect(client._rfb_state).to.equal('failed'); + }); + + it('should fail with the appropriate message if no types are sent for versions >= 3.7', function () { + client._rfb_version = 3.7; + var failure_data = [0, 0, 0, 0, 6, 119, 104, 111, 111, 112, 115]; + sinon.spy(client, '_fail'); + client._sock._websocket._receive_data(failure_data); + + expect(client._fail).to.have.been.calledTwice; + expect(client._fail).to.have.been.calledWith('Security failure: whoops'); + }); + + it('should transition to the Authentication state and continue on successful negotiation', function () { + client._rfb_version = 3.7; + var auth_schemes = [1, 1]; + client._negotiate_authentication = sinon.spy(); + client._sock._websocket._receive_data(auth_schemes); + expect(client._rfb_state).to.equal('Authentication'); + expect(client._negotiate_authentication).to.have.been.calledOnce; + }); + }); + + describe('Authentication', function () { + var client; + + beforeEach(function () { + client = make_rfb(); + client.connect('host', 8675); + client._sock._websocket._open(); + client._rfb_state = 'Security'; + }); + + function send_security(type, cl) { + cl._sock._websocket._receive_data(new Uint8Array([1, type])); + } + + it('should fail on auth scheme 0 (pre 3.7) with the given message', function () { + client._rfb_version = 3.6; + var err_msg = "Whoopsies"; + var data = [0, 0, 0, 0]; + var err_len = err_msg.length; + data.push32(err_len); + for (var i = 0; i < err_len; i++) { + data.push(err_msg.charCodeAt(i)); + } + + sinon.spy(client, '_fail'); + client._sock._websocket._receive_data(new Uint8Array(data)); + expect(client._rfb_state).to.equal('failed'); + expect(client._fail).to.have.been.calledWith('Auth failure: Whoopsies'); + }); + + it('should transition straight to SecurityResult on "no auth" (1) for versions >= 3.8', function () { + client._rfb_version = 3.8; + send_security(1, client); + expect(client._rfb_state).to.equal('SecurityResult'); + }); + + it('should transition straight to ClientInitialisation on "no auth" for versions < 3.8', function () { + client._rfb_version = 3.7; + sinon.spy(client, '_updateState'); + send_security(1, client); + expect(client._updateState).to.have.been.calledWith('ClientInitialisation'); + expect(client._rfb_state).to.equal('ServerInitialisation'); + }); + + it('should fail on an unknown auth scheme', function () { + client._rfb_version = 3.8; + send_security(57, client); + expect(client._rfb_state).to.equal('failed'); + }); + + describe('VNC Authentication (type 2) Handler', function () { + var client; + + beforeEach(function () { + client = make_rfb(); + client.connect('host', 8675); + client._sock._websocket._open(); + client._rfb_state = 'Security'; + client._rfb_version = 3.8; + }); + + it('should transition to the "password" state if missing a password', function () { + send_security(2, client); + expect(client._rfb_state).to.equal('password'); + }); + + it('should encrypt the password with DES and then send it back', function () { + client._rfb_password = 'passwd'; + send_security(2, client); + client._sock._websocket._get_sent_data(); // skip the choice of auth reply + + var challenge = []; + for (var i = 0; i < 16; i++) { challenge[i] = i; } + client._sock._websocket._receive_data(new Uint8Array(challenge)); + + var des_pass = RFB.genDES('passwd', challenge); + expect(client._sock).to.have.sent(des_pass); + }); + + it('should transition to SecurityResult immediately after sending the password', function () { + client._rfb_password = 'passwd'; + send_security(2, client); + + var challenge = []; + for (var i = 0; i < 16; i++) { challenge[i] = i; } + client._sock._websocket._receive_data(new Uint8Array(challenge)); + + expect(client._rfb_state).to.equal('SecurityResult'); + }); + }); + + describe('XVP Authentication (type 22) Handler', function () { + var client; + + beforeEach(function () { + client = make_rfb(); + client.connect('host', 8675); + client._sock._websocket._open(); + client._rfb_state = 'Security'; + client._rfb_version = 3.8; + }); + + it('should fall through to standard VNC authentication upon completion', function () { + client.set_xvp_password_sep('#'); + client._rfb_password = 'user#target#password'; + client._negotiate_std_vnc_auth = sinon.spy(); + send_security(22, client); + expect(client._negotiate_std_vnc_auth).to.have.been.calledOnce; + }); + + it('should transition to the "password" state if the passwords is missing', function() { + send_security(22, client); + expect(client._rfb_state).to.equal('password'); + }); + + it('should transition to the "password" state if the passwords is improperly formatted', function() { + client._rfb_password = 'user@target'; + send_security(22, client); + expect(client._rfb_state).to.equal('password'); + }); + + it('should split the password, send the first two parts, and pass on the last part', function () { + client.set_xvp_password_sep('#'); + client._rfb_password = 'user#target#password'; + client._negotiate_std_vnc_auth = sinon.spy(); + + send_security(22, client); + + expect(client._rfb_password).to.equal('password'); + + var expected = [22, 4, 6]; // auth selection, len user, len target + for (var i = 0; i < 10; i++) { expected[i+3] = 'usertarget'.charCodeAt(i); } + + expect(client._sock).to.have.sent(expected); + }); + }); + + describe('TightVNC Authentication (type 16) Handler', function () { + var client; + + beforeEach(function () { + client = make_rfb(); + client.connect('host', 8675); + client._sock._websocket._open(); + client._rfb_state = 'Security'; + client._rfb_version = 3.8; + send_security(16, client); + client._sock._websocket._get_sent_data(); // skip the security reply + }); + + function send_num_str_pairs(pairs, client) { + var pairs_len = pairs.length; + var data = []; + data.push32(pairs_len); + + for (var i = 0; i < pairs_len; i++) { + data.push32(pairs[i][0]); + var j; + for (j = 0; j < 4; j++) { + data.push(pairs[i][1].charCodeAt(j)); + } + for (j = 0; j < 8; j++) { + data.push(pairs[i][2].charCodeAt(j)); + } + } + + client._sock._websocket._receive_data(new Uint8Array(data)); + } + + it('should skip tunnel negotiation if no tunnels are requested', function () { + client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0])); + expect(client._rfb_tightvnc).to.be.true; + }); + + it('should fail if no supported tunnels are listed', function () { + send_num_str_pairs([[123, 'OTHR', 'SOMETHNG']], client); + expect(client._rfb_state).to.equal('failed'); + }); + + it('should choose the notunnel tunnel type', function () { + send_num_str_pairs([[0, 'TGHT', 'NOTUNNEL'], [123, 'OTHR', 'SOMETHNG']], client); + expect(client._sock).to.have.sent([0, 0, 0, 0]); + }); + + it('should continue to sub-auth negotiation after tunnel negotiation', function () { + send_num_str_pairs([[0, 'TGHT', 'NOTUNNEL']], client); + client._sock._websocket._get_sent_data(); // skip the tunnel choice here + send_num_str_pairs([[1, 'STDV', 'NOAUTH__']], client); + expect(client._sock).to.have.sent([0, 0, 0, 1]); + expect(client._rfb_state).to.equal('SecurityResult'); + }); + + /*it('should attempt to use VNC auth over no auth when possible', function () { + client._rfb_tightvnc = true; + client._negotiate_std_vnc_auth = sinon.spy(); + send_num_str_pairs([[1, 'STDV', 'NOAUTH__'], [2, 'STDV', 'VNCAUTH_']], client); + expect(client._sock).to.have.sent([0, 0, 0, 1]); + expect(client._negotiate_std_vnc_auth).to.have.been.calledOnce; + expect(client._rfb_auth_scheme).to.equal(2); + });*/ // while this would make sense, the original code doesn't actually do this + + it('should accept the "no auth" auth type and transition to SecurityResult', function () { + client._rfb_tightvnc = true; + send_num_str_pairs([[1, 'STDV', 'NOAUTH__']], client); + expect(client._sock).to.have.sent([0, 0, 0, 1]); + expect(client._rfb_state).to.equal('SecurityResult'); + }); + + it('should accept VNC authentication and transition to that', function () { + client._rfb_tightvnc = true; + client._negotiate_std_vnc_auth = sinon.spy(); + send_num_str_pairs([[2, 'STDV', 'VNCAUTH__']], client); + expect(client._sock).to.have.sent([0, 0, 0, 2]); + expect(client._negotiate_std_vnc_auth).to.have.been.calledOnce; + expect(client._rfb_auth_scheme).to.equal(2); + }); + + it('should fail if there are no supported auth types', function () { + client._rfb_tightvnc = true; + send_num_str_pairs([[23, 'stdv', 'badval__']], client); + expect(client._rfb_state).to.equal('failed'); + }); + }); + }); + + describe('SecurityResult', function () { + var client; + + beforeEach(function () { + client = make_rfb(); + client.connect('host', 8675); + client._sock._websocket._open(); + client._rfb_state = 'SecurityResult'; + }); + + it('should fall through to ClientInitialisation on a response code of 0', function () { + client._updateState = sinon.spy(); + client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0])); + expect(client._updateState).to.have.been.calledOnce; + expect(client._updateState).to.have.been.calledWith('ClientInitialisation'); + }); + + it('should fail on an error code of 1 with the given message for versions >= 3.8', function () { + client._rfb_version = 3.8; + sinon.spy(client, '_fail'); + var failure_data = [0, 0, 0, 1, 0, 0, 0, 6, 119, 104, 111, 111, 112, 115]; + client._sock._websocket._receive_data(new Uint8Array(failure_data)); + expect(client._rfb_state).to.equal('failed'); + expect(client._fail).to.have.been.calledWith('whoops'); + }); + + it('should fail on an error code of 1 with a standard message for version < 3.8', function () { + client._rfb_version = 3.7; + client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 1])); + expect(client._rfb_state).to.equal('failed'); + }); + }); + + describe('ClientInitialisation', function () { + var client; + + beforeEach(function () { + client = make_rfb(); + client.connect('host', 8675); + client._sock._websocket._open(); + client._rfb_state = 'SecurityResult'; + }); + + it('should transition to the ServerInitialisation state', function () { + client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0])); + expect(client._rfb_state).to.equal('ServerInitialisation'); + }); + + it('should send 1 if we are in shared mode', function () { + client.set_shared(true); + client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0])); + expect(client._sock).to.have.sent([1]); + }); + + it('should send 0 if we are not in shared mode', function () { + client.set_shared(false); + client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0])); + expect(client._sock).to.have.sent([0]); + }); + }); + + describe('ServerInitialisation', function () { + var client; + + beforeEach(function () { + client = make_rfb(); + client.connect('host', 8675); + client._sock._websocket._open(); + client._rfb_state = 'ServerInitialisation'; + }); + + function send_server_init(opts, client) { + var full_opts = { width: 10, height: 12, bpp: 24, depth: 24, big_endian: 0, + true_color: 1, red_max: 255, green_max: 255, blue_max: 255, + red_shift: 16, green_shift: 8, blue_shift: 0, name: 'a name' }; + for (var opt in opts) { + full_opts[opt] = opts[opt]; + } + var data = []; + + data.push16(full_opts.width); + data.push16(full_opts.height); + + data.push(full_opts.bpp); + data.push(full_opts.depth); + data.push(full_opts.big_endian); + data.push(full_opts.true_color); + + data.push16(full_opts.red_max); + data.push16(full_opts.green_max); + data.push16(full_opts.blue_max); + data.push8(full_opts.red_shift); + data.push8(full_opts.green_shift); + data.push8(full_opts.blue_shift); + + // padding + data.push8(0); + data.push8(0); + data.push8(0); + + client._sock._websocket._receive_data(new Uint8Array(data)); + + var name_data = []; + name_data.push32(full_opts.name.length); + for (var i = 0; i < full_opts.name.length; i++) { + name_data.push(full_opts.name.charCodeAt(i)); + } + client._sock._websocket._receive_data(new Uint8Array(name_data)); + } + + it('should set the framebuffer width and height', function () { + send_server_init({ width: 32, height: 84 }, client); + expect(client._fb_width).to.equal(32); + expect(client._fb_height).to.equal(84); + }); + + // NB(sross): we just warn, not fail, for endian-ness and shifts, so we don't test them + + it('should set the framebuffer name and call the callback', function () { + client.set_onDesktopName(sinon.spy()); + send_server_init({ name: 'some name' }, client); + + var spy = client.get_onDesktopName(); + expect(client._fb_name).to.equal('some name'); + expect(spy).to.have.been.calledOnce; + expect(spy.args[0][1]).to.equal('some name'); + }); + + it('should handle the extended init message of the tight encoding', function () { + // NB(sross): we don't actually do anything with it, so just test that we can + // read it w/o throwing an error + client._rfb_tightvnc = true; + send_server_init({}, client); + + var tight_data = []; + tight_data.push16(1); + tight_data.push16(2); + tight_data.push16(3); + tight_data.push16(0); + for (var i = 0; i < 16 + 32 + 48; i++) { + tight_data.push(i); + } + client._sock._websocket._receive_data(tight_data); + + expect(client._rfb_state).to.equal('normal'); + }); + + it('should set the true color mode on the display to the configuration variable', function () { + client.set_true_color(false); + sinon.spy(client._display, 'set_true_color'); + send_server_init({ true_color: 1 }, client); + expect(client._display.set_true_color).to.have.been.calledOnce; + expect(client._display.set_true_color).to.have.been.calledWith(false); + }); + + it('should call the resize callback and resize the display', function () { + client.set_onFBResize(sinon.spy()); + sinon.spy(client._display, 'resize'); + send_server_init({ width: 27, height: 32 }, client); + + var spy = client.get_onFBResize(); + expect(client._display.resize).to.have.been.calledOnce; + expect(client._display.resize).to.have.been.calledWith(27, 32); + expect(spy).to.have.been.calledOnce; + expect(spy.args[0][1]).to.equal(27); + expect(spy.args[0][2]).to.equal(32); + }); + + it('should grab the mouse and keyboard', function () { + sinon.spy(client._keyboard, 'grab'); + sinon.spy(client._mouse, 'grab'); + send_server_init({}, client); + expect(client._keyboard.grab).to.have.been.calledOnce; + expect(client._mouse.grab).to.have.been.calledOnce; + }); + + it('should set the BPP and depth to 4 and 3 respectively if in true color mode', function () { + client.set_true_color(true); + send_server_init({}, client); + expect(client._fb_Bpp).to.equal(4); + expect(client._fb_depth).to.equal(3); + }); + + it('should set the BPP and depth to 1 and 1 respectively if not in true color mode', function () { + client.set_true_color(false); + send_server_init({}, client); + expect(client._fb_Bpp).to.equal(1); + expect(client._fb_depth).to.equal(1); + }); + + // TODO(directxman12): test the various options in this configuration matrix + it('should reply with the pixel format, client encodings, and initial update request', function () { + client.set_true_color(true); + client.set_local_cursor(false); + var expected = RFB.messages.pixelFormat(4, 3, true); + expected = expected.concat(RFB.messages.clientEncodings(client._encodings, false, true)); + var expected_cdr = { cleanBox: { x: 0, y: 0, w: 0, h: 0 }, + dirtyBoxes: [ { x: 0, y: 0, w: 27, h: 32 } ] }; + expected = expected.concat(RFB.messages.fbUpdateRequests(expected_cdr, 27, 32)); + + send_server_init({ width: 27, height: 32 }, client); + expect(client._sock).to.have.sent(expected); + }); + + it('should check for sending mouse events', function () { + // be lazy with our checking so we don't have to check through the whole sent buffer + sinon.spy(client, '_checkEvents'); + send_server_init({}, client); + expect(client._checkEvents).to.have.been.calledOnce; + }); + + it('should transition to the "normal" state', function () { + send_server_init({}, client); + expect(client._rfb_state).to.equal('normal'); + }); + }); + }); + + describe('Protocol Message Processing After Completing Initialization', function () { + var client; + + beforeEach(function () { + client = make_rfb(); + client.connect('host', 8675); + client._sock._websocket._open(); + client._rfb_state = 'normal'; + client._fb_name = 'some device'; + client._fb_width = 640; + client._fb_height = 20; + }); + + describe('Framebuffer Update Handling', function () { + var client; + + beforeEach(function () { + client = make_rfb(); + client.connect('host', 8675); + client._sock._websocket._open(); + client._rfb_state = 'normal'; + client._fb_name = 'some device'; + client._fb_width = 640; + client._fb_height = 20; + }); + + var target_data_arr = [ + 0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, + 0x00, 0xff, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, + 0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255, + 0xee, 0x00, 0xff, 255, 0x00, 0xee, 0xff, 255, 0xaa, 0xee, 0xff, 255, 0xab, 0xee, 0xff, 255 + ]; + var target_data; + + var target_data_check_arr = [ + 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, + 0x00, 0xff, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255 + ]; + var target_data_check; + + before(function () { + // NB(directxman12): PhantomJS 1.x doesn't implement Uint8ClampedArray + target_data = new Uint8Array(target_data_arr); + target_data_check = new Uint8Array(target_data_check_arr); + }); + + function send_fbu_msg (rect_info, rect_data, client, rect_cnt) { + var data = []; + + if (!rect_cnt || rect_cnt > -1) { + // header + data.push(0); // msg type + data.push(0); // padding + data.push16(rect_cnt || rect_data.length); + } + + for (var i = 0; i < rect_data.length; i++) { + if (rect_info[i]) { + data.push16(rect_info[i].x); + data.push16(rect_info[i].y); + data.push16(rect_info[i].width); + data.push16(rect_info[i].height); + data.push32(rect_info[i].encoding); + } + data = data.concat(rect_data[i]); + } + + client._sock._websocket._receive_data(new Uint8Array(data)); + } + + it('should send an update request if there is sufficient data', function () { + var expected_cdr = { cleanBox: { x: 0, y: 0, w: 0, h: 0 }, + dirtyBoxes: [ { x: 0, y: 0, w: 240, h: 20 } ] }; + var expected_msg = RFB.messages.fbUpdateRequests(expected_cdr, 240, 20); + + client._framebufferUpdate = function () { return true; }; + client._sock._websocket._receive_data(new Uint8Array([0])); + + expect(client._sock).to.have.sent(expected_msg); + }); + + it('should not send an update request if we need more data', function () { + client._sock._websocket._receive_data(new Uint8Array([0])); + expect(client._sock._websocket._get_sent_data()).to.have.length(0); + }); + + it('should resume receiving an update if we previously did not have enough data', function () { + var expected_cdr = { cleanBox: { x: 0, y: 0, w: 0, h: 0 }, + dirtyBoxes: [ { x: 0, y: 0, w: 240, h: 20 } ] }; + var expected_msg = RFB.messages.fbUpdateRequests(expected_cdr, 240, 20); + + // just enough to set FBU.rects + client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 3])); + expect(client._sock._websocket._get_sent_data()).to.have.length(0); + + client._framebufferUpdate = function () { return true; }; // we magically have enough data + // 247 should *not* be used as the message type here + client._sock._websocket._receive_data(new Uint8Array([247])); + expect(client._sock).to.have.sent(expected_msg); + }); + + it('should parse out information from a header before any actual data comes in', function () { + client.set_onFBUReceive(sinon.spy()); + var rect_info = { x: 8, y: 11, width: 27, height: 32, encoding: 0x02, encodingName: 'RRE' }; + send_fbu_msg([rect_info], [[]], client); + + var spy = client.get_onFBUReceive(); + expect(spy).to.have.been.calledOnce; + expect(spy).to.have.been.calledWith(sinon.match.any, rect_info); + }); + + it('should fire onFBUComplete when the update is complete', function () { + client.set_onFBUComplete(sinon.spy()); + var rect_info = { x: 8, y: 11, width: 27, height: 32, encoding: -224, encodingName: 'last_rect' }; + send_fbu_msg([rect_info], [[]], client); // last_rect + + var spy = client.get_onFBUComplete(); + expect(spy).to.have.been.calledOnce; + expect(spy).to.have.been.calledWith(sinon.match.any, rect_info); + }); + + it('should not fire onFBUComplete if we have not finished processing the update', function () { + client.set_onFBUComplete(sinon.spy()); + var rect_info = { x: 8, y: 11, width: 27, height: 32, encoding: 0x00, encodingName: 'RAW' }; + send_fbu_msg([rect_info], [[]], client); + expect(client.get_onFBUComplete()).to.not.have.been.called; + }); + + it('should call the appropriate encoding handler', function () { + client._encHandlers[0x02] = sinon.spy(); + var rect_info = { x: 8, y: 11, width: 27, height: 32, encoding: 0x02 }; + send_fbu_msg([rect_info], [[]], client); + expect(client._encHandlers[0x02]).to.have.been.calledOnce; + }); + + it('should fail on an unsupported encoding', function () { + client.set_onFBUReceive(sinon.spy()); + var rect_info = { x: 8, y: 11, width: 27, height: 32, encoding: 234 }; + send_fbu_msg([rect_info], [[]], client); + expect(client._rfb_state).to.equal('failed'); + }); + + it('should be able to pause and resume receiving rects if not enought data', function () { + // seed some initial data to copy + client._fb_width = 4; + client._fb_height = 4; + client._display.resize(4, 4); + var initial_data = client._display._drawCtx.createImageData(4, 2); + var initial_data_arr = target_data_check_arr.slice(0, 32); + for (var i = 0; i < 32; i++) { initial_data.data[i] = initial_data_arr[i]; } + client._display._drawCtx.putImageData(initial_data, 0, 0); + + var info = [{ x: 0, y: 2, width: 2, height: 2, encoding: 0x01}, + { x: 2, y: 2, width: 2, height: 2, encoding: 0x01}]; + // data says [{ old_x: 0, old_y: 0 }, { old_x: 0, old_y: 0 }] + var rects = [[0, 2, 0, 0], [0, 0, 0, 0]]; + send_fbu_msg([info[0]], [rects[0]], client, 2); + send_fbu_msg([info[1]], [rects[1]], client, -1); + expect(client._display).to.have.displayed(target_data_check); + }); + + describe('Message Encoding Handlers', function () { + var client; + + beforeEach(function () { + client = make_rfb(); + client.connect('host', 8675); + client._sock._websocket._open(); + client._rfb_state = 'normal'; + client._fb_name = 'some device'; + // a really small frame + client._fb_width = 4; + client._fb_height = 4; + client._display._fb_width = 4; + client._display._fb_height = 4; + client._display._viewportLoc.w = 4; + client._display._viewportLoc.h = 4; + client._fb_Bpp = 4; + }); + + it('should handle the RAW encoding', function () { + var info = [{ x: 0, y: 0, width: 2, height: 2, encoding: 0x00 }, + { x: 2, y: 0, width: 2, height: 2, encoding: 0x00 }, + { x: 0, y: 2, width: 4, height: 1, encoding: 0x00 }, + { x: 0, y: 3, width: 4, height: 1, encoding: 0x00 }]; + // data is in bgrx + var rects = [ + [0x00, 0x00, 0xff, 0, 0x00, 0xff, 0x00, 0, 0x00, 0xff, 0x00, 0, 0x00, 0x00, 0xff, 0], + [0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0, 0xff, 0x00, 0x00, 0], + [0xff, 0x00, 0xee, 0, 0xff, 0xee, 0x00, 0, 0xff, 0xee, 0xaa, 0, 0xff, 0xee, 0xab, 0], + [0xff, 0x00, 0xee, 0, 0xff, 0xee, 0x00, 0, 0xff, 0xee, 0xaa, 0, 0xff, 0xee, 0xab, 0]]; + send_fbu_msg(info, rects, client); + expect(client._display).to.have.displayed(target_data); + }); + + it('should handle the COPYRECT encoding', function () { + // seed some initial data to copy + var initial_data = client._display._drawCtx.createImageData(4, 2); + var initial_data_arr = target_data_check_arr.slice(0, 32); + for (var i = 0; i < 32; i++) { initial_data.data[i] = initial_data_arr[i]; } + client._display._drawCtx.putImageData(initial_data, 0, 0); + + var info = [{ x: 0, y: 2, width: 2, height: 2, encoding: 0x01}, + { x: 2, y: 2, width: 2, height: 2, encoding: 0x01}]; + // data says [{ old_x: 0, old_y: 0 }, { old_x: 0, old_y: 0 }] + var rects = [[0, 2, 0, 0], [0, 0, 0, 0]]; + send_fbu_msg(info, rects, client); + expect(client._display).to.have.displayed(target_data_check); + }); + + // TODO(directxman12): for encodings with subrects, test resuming on partial send? + // TODO(directxman12): test rre_chunk_sz (related to above about subrects)? + + it('should handle the RRE encoding', function () { + var info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x02 }]; + var rect = []; + rect.push32(2); // 2 subrects + rect.push32(0xff00ff); // becomes 00ff00ff --> #00FF00 bg color + rect.push(0xff); // becomes ff0000ff --> #0000FF color + rect.push(0x00); + rect.push(0x00); + rect.push(0xff); + rect.push16(0); // x: 0 + rect.push16(0); // y: 0 + rect.push16(2); // width: 2 + rect.push16(2); // height: 2 + rect.push(0xff); // becomes ff0000ff --> #0000FF color + rect.push(0x00); + rect.push(0x00); + rect.push(0xff); + rect.push16(2); // x: 2 + rect.push16(2); // y: 2 + rect.push16(2); // width: 2 + rect.push16(2); // height: 2 + + send_fbu_msg(info, [rect], client); + expect(client._display).to.have.displayed(target_data_check); + }); + + describe('the HEXTILE encoding handler', function () { + var client; + beforeEach(function () { + client = make_rfb(); + client.connect('host', 8675); + client._sock._websocket._open(); + client._rfb_state = 'normal'; + client._fb_name = 'some device'; + // a really small frame + client._fb_width = 4; + client._fb_height = 4; + client._display._fb_width = 4; + client._display._fb_height = 4; + client._display._viewportLoc.w = 4; + client._display._viewportLoc.h = 4; + client._fb_Bpp = 4; + }); + + it('should handle a tile with fg, bg specified, normal subrects', function () { + var info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }]; + var rect = []; + rect.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects + rect.push32(0xff00ff); // becomes 00ff00ff --> #00FF00 bg color + rect.push(0xff); // becomes ff0000ff --> #0000FF fg color + rect.push(0x00); + rect.push(0x00); + rect.push(0xff); + rect.push(2); // 2 subrects + rect.push(0); // x: 0, y: 0 + rect.push(1 | (1 << 4)); // width: 2, height: 2 + rect.push(2 | (2 << 4)); // x: 2, y: 2 + rect.push(1 | (1 << 4)); // width: 2, height: 2 + send_fbu_msg(info, [rect], client); + expect(client._display).to.have.displayed(target_data_check); + }); + + it('should handle a raw tile', function () { + var info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }]; + var rect = []; + rect.push(0x01); // raw + for (var i = 0; i < target_data.length; i += 4) { + rect.push(target_data[i + 2]); + rect.push(target_data[i + 1]); + rect.push(target_data[i]); + rect.push(target_data[i + 3]); + } + send_fbu_msg(info, [rect], client); + expect(client._display).to.have.displayed(target_data); + }); + + it('should handle a tile with only bg specified (solid bg)', function () { + var info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }]; + var rect = []; + rect.push(0x02); + rect.push32(0xff00ff); // becomes 00ff00ff --> #00FF00 bg color + send_fbu_msg(info, [rect], client); + + var expected = []; + for (var i = 0; i < 16; i++) { expected.push32(0xff00ff); } + expect(client._display).to.have.displayed(new Uint8Array(expected)); + }); + + it('should handle a tile with only bg specified and an empty frame afterwards', function () { + // set the width so we can have two tiles + client._fb_width = 8; + client._display._fb_width = 8; + client._display._viewportLoc.w = 8; + + var info = [{ x: 0, y: 0, width: 8, height: 4, encoding: 0x05 }]; + + var rect = []; + + // send a bg frame + rect.push(0x02); + rect.push32(0xff00ff); // becomes 00ff00ff --> #00FF00 bg color + + // send an empty frame + rect.push(0x00); + + send_fbu_msg(info, [rect], client); + + var expected = []; + var i; + for (i = 0; i < 16; i++) { expected.push32(0xff00ff); } // rect 1: solid + for (i = 0; i < 16; i++) { expected.push32(0xff00ff); } // rect 2: same bkground color + expect(client._display).to.have.displayed(new Uint8Array(expected)); + }); + + it('should handle a tile with bg and coloured subrects', function () { + var info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }]; + var rect = []; + rect.push(0x02 | 0x08 | 0x10); // bg spec, anysubrects, colouredsubrects + rect.push32(0xff00ff); // becomes 00ff00ff --> #00FF00 bg color + rect.push(2); // 2 subrects + rect.push(0xff); // becomes ff0000ff --> #0000FF fg color + rect.push(0x00); + rect.push(0x00); + rect.push(0xff); + rect.push(0); // x: 0, y: 0 + rect.push(1 | (1 << 4)); // width: 2, height: 2 + rect.push(0xff); // becomes ff0000ff --> #0000FF fg color + rect.push(0x00); + rect.push(0x00); + rect.push(0xff); + rect.push(2 | (2 << 4)); // x: 2, y: 2 + rect.push(1 | (1 << 4)); // width: 2, height: 2 + send_fbu_msg(info, [rect], client); + expect(client._display).to.have.displayed(target_data_check); + }); + + it('should carry over fg and bg colors from the previous tile if not specified', function () { + client._fb_width = 4; + client._fb_height = 17; + client._display.resize(4, 17); + + var info = [{ x: 0, y: 0, width: 4, height: 17, encoding: 0x05}]; + var rect = []; + rect.push(0x02 | 0x04 | 0x08); // bg spec, fg spec, anysubrects + rect.push32(0xff00ff); // becomes 00ff00ff --> #00FF00 bg color + rect.push(0xff); // becomes ff0000ff --> #0000FF fg color + rect.push(0x00); + rect.push(0x00); + rect.push(0xff); + rect.push(8); // 8 subrects + var i; + for (i = 0; i < 4; i++) { + rect.push((0 << 4) | (i * 4)); // x: 0, y: i*4 + rect.push(1 | (1 << 4)); // width: 2, height: 2 + rect.push((2 << 4) | (i * 4 + 2)); // x: 2, y: i * 4 + 2 + rect.push(1 | (1 << 4)); // width: 2, height: 2 + } + rect.push(0x08); // anysubrects + rect.push(1); // 1 subrect + rect.push(0); // x: 0, y: 0 + rect.push(1 | (1 << 4)); // width: 2, height: 2 + send_fbu_msg(info, [rect], client); + + var expected = []; + for (i = 0; i < 4; i++) { expected = expected.concat(target_data_check_arr); } + expected = expected.concat(target_data_check_arr.slice(0, 16)); + expect(client._display).to.have.displayed(new Uint8Array(expected)); + }); + + it('should fail on an invalid subencoding', function () { + var info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }]; + var rects = [[45]]; // an invalid subencoding + send_fbu_msg(info, rects, client); + expect(client._rfb_state).to.equal('failed'); + }); + }); + + it.skip('should handle the TIGHT encoding', function () { + // TODO(directxman12): test this + }); + + it.skip('should handle the TIGHT_PNG encoding', function () { + // TODO(directxman12): test this + }); + + it('should handle the DesktopSize pseduo-encoding', function () { + client.set_onFBResize(sinon.spy()); + sinon.spy(client._display, 'resize'); + send_fbu_msg([{ x: 0, y: 0, width: 20, height: 50, encoding: -223 }], [[]], client); + + var spy = client.get_onFBResize(); + expect(spy).to.have.been.calledOnce; + expect(spy).to.have.been.calledWith(sinon.match.any, 20, 50); + + expect(client._fb_width).to.equal(20); + expect(client._fb_height).to.equal(50); + + expect(client._display.resize).to.have.been.calledOnce; + expect(client._display.resize).to.have.been.calledWith(20, 50); + }); + + it.skip('should handle the Cursor pseudo-encoding', function () { + // TODO(directxman12): test + }); + + it('should handle the last_rect pseudo-encoding', function () { + client.set_onFBUReceive(sinon.spy()); + send_fbu_msg([{ x: 0, y: 0, width: 0, height: 0, encoding: -224}], [[]], client, 100); + expect(client._FBU.rects).to.equal(0); + expect(client.get_onFBUReceive()).to.have.been.calledOnce; + }); + }); + }); + + it('should set the colour map on the display on SetColourMapEntries', function () { + var expected_cm = []; + var data = [1, 0, 0, 1, 0, 4]; + var i; + for (i = 0; i < 4; i++) { + expected_cm[i + 1] = [i * 10, i * 10 + 1, i * 10 + 2]; + data.push16(expected_cm[i + 1][2] << 8); + data.push16(expected_cm[i + 1][1] << 8); + data.push16(expected_cm[i + 1][0] << 8); + } + + client._sock._websocket._receive_data(new Uint8Array(data)); + expect(client._display.get_colourMap()).to.deep.equal(expected_cm); + }); + + describe('XVP Message Handling', function () { + beforeEach(function () { + client = make_rfb(); + client.connect('host', 8675); + client._sock._websocket._open(); + client._rfb_state = 'normal'; + client._fb_name = 'some device'; + client._fb_width = 27; + client._fb_height = 32; + }); + + it('should call updateState with a message on XVP_FAIL, but keep the same state', function () { + client._updateState = sinon.spy(); + client._sock._websocket._receive_data(new Uint8Array([250, 0, 10, 0])); + expect(client._updateState).to.have.been.calledOnce; + expect(client._updateState).to.have.been.calledWith('normal', 'Operation Failed'); + }); + + it('should set the XVP version and fire the callback with the version on XVP_INIT', function () { + client.set_onXvpInit(sinon.spy()); + client._sock._websocket._receive_data(new Uint8Array([250, 0, 10, 1])); + expect(client._rfb_xvp_ver).to.equal(10); + expect(client.get_onXvpInit()).to.have.been.calledOnce; + expect(client.get_onXvpInit()).to.have.been.calledWith(10); + }); + + it('should fail on unknown XVP message types', function () { + client._sock._websocket._receive_data(new Uint8Array([250, 0, 10, 237])); + expect(client._rfb_state).to.equal('failed'); + }); + }); + + it('should fire the clipboard callback with the retrieved text on ServerCutText', function () { + var expected_str = 'cheese!'; + var data = [3, 0, 0, 0]; + data.push32(expected_str.length); + for (var i = 0; i < expected_str.length; i++) { data.push(expected_str.charCodeAt(i)); } + client.set_onClipboard(sinon.spy()); + + client._sock._websocket._receive_data(new Uint8Array(data)); + var spy = client.get_onClipboard(); + expect(spy).to.have.been.calledOnce; + expect(spy.args[0][1]).to.equal(expected_str); + }); + + it('should fire the bell callback on Bell', function () { + client.set_onBell(sinon.spy()); + client._sock._websocket._receive_data(new Uint8Array([2])); + expect(client.get_onBell()).to.have.been.calledOnce; + }); + + it('should fail on an unknown message type', function () { + client._sock._websocket._receive_data(new Uint8Array([87])); + expect(client._rfb_state).to.equal('failed'); + }); + }); + + describe('Asynchronous Events', function () { + describe('Mouse event handlers', function () { + var client; + beforeEach(function () { + client = make_rfb(); + client._sock.send = sinon.spy(); + client._rfb_state = 'normal'; + }); + + it('should not send button messages in view-only mode', function () { + client._view_only = true; + client._mouse._onMouseButton(0, 0, 1, 0x001); + expect(client._sock.send).to.not.have.been.called; + }); + + it('should not send movement messages in view-only mode', function () { + client._view_only = true; + client._mouse._onMouseMove(0, 0); + expect(client._sock.send).to.not.have.been.called; + }); + + it('should send a pointer event on mouse button presses', function () { + client._mouse._onMouseButton(10, 12, 1, 0x001); + expect(client._sock.send).to.have.been.calledOnce; + var pointer_msg = RFB.messages.pointerEvent(10, 12, 0x001); + expect(client._sock.send).to.have.been.calledWith(pointer_msg); + }); + + it('should send a mask of 1 on mousedown', function () { + client._mouse._onMouseButton(10, 12, 1, 0x001); + expect(client._sock.send).to.have.been.calledOnce; + var pointer_msg = RFB.messages.pointerEvent(10, 12, 0x001); + expect(client._sock.send).to.have.been.calledWith(pointer_msg); + }); + + it('should send a mask of 0 on mouseup', function () { + client._mouse_buttonMask = 0x001; + client._mouse._onMouseButton(10, 12, 0, 0x001); + expect(client._sock.send).to.have.been.calledOnce; + var pointer_msg = RFB.messages.pointerEvent(10, 12, 0x000); + expect(client._sock.send).to.have.been.calledWith(pointer_msg); + }); + + it('should send a pointer event on mouse movement', function () { + client._mouse._onMouseMove(10, 12); + expect(client._sock.send).to.have.been.calledOnce; + var pointer_msg = RFB.messages.pointerEvent(10, 12, 0); + expect(client._sock.send).to.have.been.calledWith(pointer_msg); + }); + + it('should set the button mask so that future mouse movements use it', function () { + client._mouse._onMouseButton(10, 12, 1, 0x010); + client._sock.send = sinon.spy(); + client._mouse._onMouseMove(13, 9); + expect(client._sock.send).to.have.been.calledOnce; + var pointer_msg = RFB.messages.pointerEvent(13, 9, 0x010); + expect(client._sock.send).to.have.been.calledWith(pointer_msg); + }); + + // NB(directxman12): we don't need to test not sending messages in + // non-normal modes, since we haven't grabbed input + // yet (grabbing input should be checked in the lifecycle tests). + + it('should not send movement messages when viewport dragging', function () { + client._viewportDragging = true; + client._display.viewportChange = sinon.spy(); + client._mouse._onMouseMove(13, 9); + expect(client._sock.send).to.not.have.been.called; + }); + + it('should not send button messages when initiating viewport dragging', function () { + client._viewportDrag = true; + client._mouse._onMouseButton(13, 9, 0x001); + expect(client._sock.send).to.not.have.been.called; + }); + + it('should be initiate viewport dragging on a button down event, if enabled', function () { + client._viewportDrag = true; + client._mouse._onMouseButton(13, 9, 0x001); + expect(client._viewportDragging).to.be.true; + expect(client._viewportDragPos).to.deep.equal({ x: 13, y: 9 }); + }); + + it('should terminate viewport dragging on a button up event, if enabled', function () { + client._viewportDrag = true; + client._viewportDragging = true; + client._mouse._onMouseButton(13, 9, 0x000); + expect(client._viewportDragging).to.be.false; + }); + + it('if enabled, viewportDragging should occur on mouse movement while a button is down', function () { + client._viewportDrag = true; + client._viewportDragging = true; + client._viewportDragPos = { x: 13, y: 9 }; + client._display.viewportChange = sinon.spy(); + + client._mouse._onMouseMove(10, 4); + + expect(client._viewportDragging).to.be.true; + expect(client._viewportDragPos).to.deep.equal({ x: 10, y: 4 }); + expect(client._display.viewportChange).to.have.been.calledOnce; + expect(client._display.viewportChange).to.have.been.calledWith(3, 5); + }); + }); + + describe('Keyboard Event Handlers', function () { + var client; + beforeEach(function () { + client = make_rfb(); + client._sock.send = sinon.spy(); + }); + + it('should send a key message on a key press', function () { + client._keyboard._onKeyPress(1234, 1); + expect(client._sock.send).to.have.been.calledOnce; + var key_msg = RFB.messages.keyEvent(1234, 1); + expect(client._sock.send).to.have.been.calledWith(key_msg); + }); + + it('should not send messages in view-only mode', function () { + client._view_only = true; + client._keyboard._onKeyPress(1234, 1); + expect(client._sock.send).to.not.have.been.called; + }); + }); + + describe('WebSocket event handlers', function () { + var client; + beforeEach(function () { + client = make_rfb(); + this.clock = sinon.useFakeTimers(); + }); + + afterEach(function () { this.clock.restore(); }); + + // message events + it ('should do nothing if we receive an empty message and have nothing in the queue', function () { + client.connect('host', 8675); + client._rfb_state = 'normal'; + client._normal_msg = sinon.spy(); + client._sock._websocket._receive_data(Base64.encode([])); + expect(client._normal_msg).to.not.have.been.called; + }); + + it('should handle a message in the normal state as a normal message', function () { + client.connect('host', 8675); + client._rfb_state = 'normal'; + client._normal_msg = sinon.spy(); + client._sock._websocket._receive_data(Base64.encode([1, 2, 3])); + expect(client._normal_msg).to.have.been.calledOnce; + }); + + it('should handle a message in any non-disconnected/failed state like an init message', function () { + client.connect('host', 8675); + client._rfb_state = 'ProtocolVersion'; + client._init_msg = sinon.spy(); + client._sock._websocket._receive_data(Base64.encode([1, 2, 3])); + expect(client._init_msg).to.have.been.calledOnce; + }); + + it('should split up the handling of muplitle normal messages across 10ms intervals', function () { + client.connect('host', 8675); + client._sock._websocket._open(); + client._rfb_state = 'normal'; + client.set_onBell(sinon.spy()); + client._sock._websocket._receive_data(new Uint8Array([0x02, 0x02])); + expect(client.get_onBell()).to.have.been.calledOnce; + this.clock.tick(20); + expect(client.get_onBell()).to.have.been.calledTwice; + }); + + // open events + it('should update the state to ProtocolVersion on open (if the state is "connect")', function () { + client.connect('host', 8675); + client._sock._websocket._open(); + expect(client._rfb_state).to.equal('ProtocolVersion'); + }); + + it('should fail if we are not currently ready to connect and we get an "open" event', function () { + client.connect('host', 8675); + client._rfb_state = 'some_other_state'; + client._sock._websocket._open(); + expect(client._rfb_state).to.equal('failed'); + }); + + // close events + it('should transition to "disconnected" from "disconnect" on a close event', function () { + client.connect('host', 8675); + client._rfb_state = 'disconnect'; + client._sock._websocket.close(); + expect(client._rfb_state).to.equal('disconnected'); + }); + + it('should transition to failed if we get a close event from any non-"disconnection" state', function () { + client.connect('host', 8675); + client._rfb_state = 'normal'; + client._sock._websocket.close(); + expect(client._rfb_state).to.equal('failed'); + }); + + // error events do nothing + }); + }); +}); diff --git a/src/sunstone/public/bower_components/no-vnc/tests/test.util.js b/src/sunstone/public/bower_components/no-vnc/tests/test.util.js new file mode 100644 index 0000000000..7d6524a3ea --- /dev/null +++ b/src/sunstone/public/bower_components/no-vnc/tests/test.util.js @@ -0,0 +1,105 @@ +// requires local modules: util +/* jshint expr: true */ + +var assert = chai.assert; +var expect = chai.expect; + +describe('Utils', function() { + "use strict"; + + describe('Array instance methods', function () { + describe('push8', function () { + it('should push a byte on to the array', function () { + var arr = [1]; + arr.push8(128); + expect(arr).to.deep.equal([1, 128]); + }); + + it('should only use the least significant byte of any number passed in', function () { + var arr = [1]; + arr.push8(0xABCD); + expect(arr).to.deep.equal([1, 0xCD]); + }); + }); + + describe('push16', function () { + it('should push two bytes on to the array', function () { + var arr = [1]; + arr.push16(0xABCD); + expect(arr).to.deep.equal([1, 0xAB, 0xCD]); + }); + + it('should only use the two least significant bytes of any number passed in', function () { + var arr = [1]; + arr.push16(0xABCDEF); + expect(arr).to.deep.equal([1, 0xCD, 0xEF]); + }); + }); + + describe('push32', function () { + it('should push four bytes on to the array', function () { + var arr = [1]; + arr.push32(0xABCDEF12); + expect(arr).to.deep.equal([1, 0xAB, 0xCD, 0xEF, 0x12]); + }); + + it('should only use the four least significant bytes of any number passed in', function () { + var arr = [1]; + arr.push32(0xABCDEF1234); + expect(arr).to.deep.equal([1, 0xCD, 0xEF, 0x12, 0x34]); + }); + }); + }); + + describe('logging functions', function () { + beforeEach(function () { + sinon.spy(console, 'log'); + sinon.spy(console, 'warn'); + sinon.spy(console, 'error'); + }); + + afterEach(function () { + console.log.restore(); + console.warn.restore(); + console.error.restore(); + }); + + it('should use noop for levels lower than the min level', function () { + Util.init_logging('warn'); + Util.Debug('hi'); + Util.Info('hello'); + expect(console.log).to.not.have.been.called; + }); + + it('should use console.log for Debug and Info', function () { + Util.init_logging('debug'); + Util.Debug('dbg'); + Util.Info('inf'); + expect(console.log).to.have.been.calledWith('dbg'); + expect(console.log).to.have.been.calledWith('inf'); + }); + + it('should use console.warn for Warn', function () { + Util.init_logging('warn'); + Util.Warn('wrn'); + expect(console.warn).to.have.been.called; + expect(console.warn).to.have.been.calledWith('wrn'); + }); + + it('should use console.error for Error', function () { + Util.init_logging('error'); + Util.Error('err'); + expect(console.error).to.have.been.called; + expect(console.error).to.have.been.calledWith('err'); + }); + }); + + // TODO(directxman12): test the conf_default and conf_defaults methods + // TODO(directxman12): test decodeUTF8 + // TODO(directxman12): test the event methods (addEvent, removeEvent, stopEvent) + // TODO(directxman12): figure out a good way to test getPosition and getEventPosition + // TODO(directxman12): figure out how to test the browser detection functions properly + // (we can't really test them against the browsers, except for Gecko + // via PhantomJS, the default test driver) + // TODO(directxman12): figure out how to test Util.Flash +}); diff --git a/src/sunstone/public/bower_components/no-vnc/tests/test.websock.js b/src/sunstone/public/bower_components/no-vnc/tests/test.websock.js new file mode 100644 index 0000000000..7d242d3e15 --- /dev/null +++ b/src/sunstone/public/bower_components/no-vnc/tests/test.websock.js @@ -0,0 +1,480 @@ +// requires local modules: websock, base64, util +// requires test modules: fake.websocket +/* jshint expr: true */ +var assert = chai.assert; +var expect = chai.expect; + +describe('Websock', function() { + "use strict"; + + describe('Queue methods', function () { + var sock; + var RQ_TEMPLATE = [0, 1, 2, 3, 4, 5, 6, 7]; + + beforeEach(function () { + sock = new Websock(); + for (var i = RQ_TEMPLATE.length - 1; i >= 0; i--) { + sock.rQunshift8(RQ_TEMPLATE[i]); + } + }); + describe('rQlen', function () { + it('should return the length of the receive queue', function () { + sock.set_rQi(0); + + expect(sock.rQlen()).to.equal(RQ_TEMPLATE.length); + }); + + it("should return the proper length if we read some from the receive queue", function () { + sock.set_rQi(1); + + expect(sock.rQlen()).to.equal(RQ_TEMPLATE.length - 1); + }); + }); + + describe('rQpeek8', function () { + it('should peek at the next byte without poping it off the queue', function () { + var bef_len = sock.rQlen(); + var peek = sock.rQpeek8(); + expect(sock.rQpeek8()).to.equal(peek); + expect(sock.rQlen()).to.equal(bef_len); + }); + }); + + describe('rQshift8', function () { + it('should pop a single byte from the receive queue', function () { + var peek = sock.rQpeek8(); + var bef_len = sock.rQlen(); + expect(sock.rQshift8()).to.equal(peek); + expect(sock.rQlen()).to.equal(bef_len - 1); + }); + }); + + describe('rQunshift8', function () { + it('should place a byte at the front of the queue', function () { + sock.rQunshift8(255); + expect(sock.rQpeek8()).to.equal(255); + expect(sock.rQlen()).to.equal(RQ_TEMPLATE.length + 1); + }); + }); + + describe('rQshift16', function () { + it('should pop two bytes from the receive queue and return a single number', function () { + var bef_len = sock.rQlen(); + var expected = (RQ_TEMPLATE[0] << 8) + RQ_TEMPLATE[1]; + expect(sock.rQshift16()).to.equal(expected); + expect(sock.rQlen()).to.equal(bef_len - 2); + }); + }); + + describe('rQshift32', function () { + it('should pop four bytes from the receive queue and return a single number', function () { + var bef_len = sock.rQlen(); + var expected = (RQ_TEMPLATE[0] << 24) + + (RQ_TEMPLATE[1] << 16) + + (RQ_TEMPLATE[2] << 8) + + RQ_TEMPLATE[3]; + expect(sock.rQshift32()).to.equal(expected); + expect(sock.rQlen()).to.equal(bef_len - 4); + }); + }); + + describe('rQshiftStr', function () { + it('should shift the given number of bytes off of the receive queue and return a string', function () { + var bef_len = sock.rQlen(); + var bef_rQi = sock.get_rQi(); + var shifted = sock.rQshiftStr(3); + expect(shifted).to.be.a('string'); + expect(shifted).to.equal(String.fromCharCode.apply(null, RQ_TEMPLATE.slice(bef_rQi, bef_rQi + 3))); + expect(sock.rQlen()).to.equal(bef_len - 3); + }); + + it('should shift the entire rest of the queue off if no length is given', function () { + sock.rQshiftStr(); + expect(sock.rQlen()).to.equal(0); + }); + }); + + describe('rQshiftBytes', function () { + it('should shift the given number of bytes of the receive queue and return an array', function () { + var bef_len = sock.rQlen(); + var bef_rQi = sock.get_rQi(); + var shifted = sock.rQshiftBytes(3); + expect(shifted).to.be.an.instanceof(Array); + expect(shifted).to.deep.equal(RQ_TEMPLATE.slice(bef_rQi, bef_rQi + 3)); + expect(sock.rQlen()).to.equal(bef_len - 3); + }); + + it('should shift the entire rest of the queue off if no length is given', function () { + sock.rQshiftBytes(); + expect(sock.rQlen()).to.equal(0); + }); + }); + + describe('rQslice', function () { + beforeEach(function () { + sock.set_rQi(0); + }); + + it('should not modify the receive queue', function () { + var bef_len = sock.rQlen(); + sock.rQslice(0, 2); + expect(sock.rQlen()).to.equal(bef_len); + }); + + it('should return an array containing the given slice of the receive queue', function () { + var sl = sock.rQslice(0, 2); + expect(sl).to.be.an.instanceof(Array); + expect(sl).to.deep.equal(RQ_TEMPLATE.slice(0, 2)); + }); + + it('should use the rest of the receive queue if no end is given', function () { + var sl = sock.rQslice(1); + expect(sl).to.have.length(RQ_TEMPLATE.length - 1); + expect(sl).to.deep.equal(RQ_TEMPLATE.slice(1)); + }); + + it('should take the current rQi in to account', function () { + sock.set_rQi(1); + expect(sock.rQslice(0, 2)).to.deep.equal(RQ_TEMPLATE.slice(1, 3)); + }); + }); + + describe('rQwait', function () { + beforeEach(function () { + sock.set_rQi(0); + }); + + it('should return true if there are not enough bytes in the receive queue', function () { + expect(sock.rQwait('hi', RQ_TEMPLATE.length + 1)).to.be.true; + }); + + it('should return false if there are enough bytes in the receive queue', function () { + expect(sock.rQwait('hi', RQ_TEMPLATE.length)).to.be.false; + }); + + it('should return true and reduce rQi by "goback" if there are not enough bytes', function () { + sock.set_rQi(5); + expect(sock.rQwait('hi', RQ_TEMPLATE.length, 4)).to.be.true; + expect(sock.get_rQi()).to.equal(1); + }); + + it('should raise an error if we try to go back more than possible', function () { + sock.set_rQi(5); + expect(function () { sock.rQwait('hi', RQ_TEMPLATE.length, 6); }).to.throw(Error); + }); + + it('should not reduce rQi if there are enough bytes', function () { + sock.set_rQi(5); + sock.rQwait('hi', 1, 6); + expect(sock.get_rQi()).to.equal(5); + }); + }); + + describe('flush', function () { + beforeEach(function () { + sock._websocket = { + send: sinon.spy() + }; + }); + + it('should actually send on the websocket if the websocket does not have too much buffered', function () { + sock.maxBufferedAmount = 10; + sock._websocket.bufferedAmount = 8; + sock._sQ = [1, 2, 3]; + var encoded = sock._encode_message(); + + sock.flush(); + expect(sock._websocket.send).to.have.been.calledOnce; + expect(sock._websocket.send).to.have.been.calledWith(encoded); + }); + + it('should return true if the websocket did not have too much buffered', function () { + sock.maxBufferedAmount = 10; + sock._websocket.bufferedAmount = 8; + + expect(sock.flush()).to.be.true; + }); + + it('should not call send if we do not have anything queued up', function () { + sock._sQ = []; + sock.maxBufferedAmount = 10; + sock._websocket.bufferedAmount = 8; + + sock.flush(); + + expect(sock._websocket.send).not.to.have.been.called; + }); + + it('should not send and return false if the websocket has too much buffered', function () { + sock.maxBufferedAmount = 10; + sock._websocket.bufferedAmount = 12; + + expect(sock.flush()).to.be.false; + expect(sock._websocket.send).to.not.have.been.called; + }); + }); + + describe('send', function () { + beforeEach(function () { + sock.flush = sinon.spy(); + }); + + it('should add to the send queue', function () { + sock.send([1, 2, 3]); + var sq = sock.get_sQ(); + expect(sock.get_sQ().slice(sq.length - 3)).to.deep.equal([1, 2, 3]); + }); + + it('should call flush', function () { + sock.send([1, 2, 3]); + expect(sock.flush).to.have.been.calledOnce; + }); + }); + + describe('send_string', function () { + beforeEach(function () { + sock.send = sinon.spy(); + }); + + it('should call send after converting the string to an array', function () { + sock.send_string("\x01\x02\x03"); + expect(sock.send).to.have.been.calledWith([1, 2, 3]); + }); + }); + }); + + describe('lifecycle methods', function () { + var old_WS; + before(function () { + old_WS = WebSocket; + }); + + var sock; + beforeEach(function () { + sock = new Websock(); + WebSocket = sinon.spy(); + WebSocket.OPEN = old_WS.OPEN; + WebSocket.CONNECTING = old_WS.CONNECTING; + WebSocket.CLOSING = old_WS.CLOSING; + WebSocket.CLOSED = old_WS.CLOSED; + }); + + describe('opening', function () { + it('should pick the correct protocols if none are given' , function () { + + }); + + it('should open the actual websocket', function () { + sock.open('ws://localhost:8675', 'base64'); + expect(WebSocket).to.have.been.calledWith('ws://localhost:8675', 'base64'); + }); + + it('should fail if we try to use binary but do not support it', function () { + expect(function () { sock.open('ws:///', 'binary'); }).to.throw(Error); + }); + + it('should fail if we specified an array with only binary and we do not support it', function () { + expect(function () { sock.open('ws:///', ['binary']); }).to.throw(Error); + }); + + it('should skip binary if we have multiple options for encoding and do not support binary', function () { + sock.open('ws:///', ['binary', 'base64']); + expect(WebSocket).to.have.been.calledWith('ws:///', ['base64']); + }); + // it('should initialize the event handlers')? + }); + + describe('closing', function () { + beforeEach(function () { + sock.open('ws://'); + sock._websocket.close = sinon.spy(); + }); + + it('should close the actual websocket if it is open', function () { + sock._websocket.readyState = WebSocket.OPEN; + sock.close(); + expect(sock._websocket.close).to.have.been.calledOnce; + }); + + it('should close the actual websocket if it is connecting', function () { + sock._websocket.readyState = WebSocket.CONNECTING; + sock.close(); + expect(sock._websocket.close).to.have.been.calledOnce; + }); + + it('should not try to close the actual websocket if closing', function () { + sock._websocket.readyState = WebSocket.CLOSING; + sock.close(); + expect(sock._websocket.close).not.to.have.been.called; + }); + + it('should not try to close the actual websocket if closed', function () { + sock._websocket.readyState = WebSocket.CLOSED; + sock.close(); + expect(sock._websocket.close).not.to.have.been.called; + }); + + it('should reset onmessage to not call _recv_message', function () { + sinon.spy(sock, '_recv_message'); + sock.close(); + sock._websocket.onmessage(null); + try { + expect(sock._recv_message).not.to.have.been.called; + } finally { + sock._recv_message.restore(); + } + }); + }); + + describe('event handlers', function () { + beforeEach(function () { + sock._recv_message = sinon.spy(); + sock.on('open', sinon.spy()); + sock.on('close', sinon.spy()); + sock.on('error', sinon.spy()); + sock.open('ws://'); + }); + + it('should call _recv_message on a message', function () { + sock._websocket.onmessage(null); + expect(sock._recv_message).to.have.been.calledOnce; + }); + + it('should copy the mode over upon opening', function () { + sock._websocket.protocol = 'cheese'; + sock._websocket.onopen(); + expect(sock._mode).to.equal('cheese'); + }); + + it('should assume base64 if no protocol was available on opening', function () { + sock._websocket.protocol = null; + sock._websocket.onopen(); + expect(sock._mode).to.equal('base64'); + }); + + it('should call the open event handler on opening', function () { + sock._websocket.onopen(); + expect(sock._eventHandlers.open).to.have.been.calledOnce; + }); + + it('should call the close event handler on closing', function () { + sock._websocket.onclose(); + expect(sock._eventHandlers.close).to.have.been.calledOnce; + }); + + it('should call the error event handler on error', function () { + sock._websocket.onerror(); + expect(sock._eventHandlers.error).to.have.been.calledOnce; + }); + }); + + after(function () { + WebSocket = old_WS; + }); + }); + + describe('WebSocket Receiving', function () { + var sock; + beforeEach(function () { + sock = new Websock(); + }); + + it('should support decoding base64 string data to add it to the receive queue', function () { + var msg = { data: Base64.encode([1, 2, 3]) }; + sock._mode = 'base64'; + sock._recv_message(msg); + expect(sock.rQshiftStr(3)).to.equal('\x01\x02\x03'); + }); + + it('should support adding binary Uint8Array data to the receive queue', function () { + var msg = { data: new Uint8Array([1, 2, 3]) }; + sock._mode = 'binary'; + sock._recv_message(msg); + expect(sock.rQshiftStr(3)).to.equal('\x01\x02\x03'); + }); + + it('should call the message event handler if present', function () { + sock._eventHandlers.message = sinon.spy(); + var msg = { data: Base64.encode([1, 2, 3]) }; + sock._mode = 'base64'; + sock._recv_message(msg); + expect(sock._eventHandlers.message).to.have.been.calledOnce; + }); + + it('should not call the message event handler if there is nothing in the receive queue', function () { + sock._eventHandlers.message = sinon.spy(); + var msg = { data: Base64.encode([]) }; + sock._mode = 'base64'; + sock._recv_message(msg); + expect(sock._eventHandlers.message).not.to.have.been.called; + }); + + it('should compact the receive queue', function () { + // NB(sross): while this is an internal implementation detail, it's important to + // test, otherwise the receive queue could become very large very quickly + sock._rQ = [0, 1, 2, 3, 4, 5]; + sock.set_rQi(6); + sock._rQmax = 3; + var msg = { data: Base64.encode([1, 2, 3]) }; + sock._mode = 'base64'; + sock._recv_message(msg); + expect(sock._rQ.length).to.equal(3); + expect(sock.get_rQi()).to.equal(0); + }); + + it('should call the error event handler on an exception', function () { + sock._eventHandlers.error = sinon.spy(); + sock._eventHandlers.message = sinon.stub().throws(); + var msg = { data: Base64.encode([1, 2, 3]) }; + sock._mode = 'base64'; + sock._recv_message(msg); + expect(sock._eventHandlers.error).to.have.been.calledOnce; + }); + }); + + describe('Data encoding', function () { + before(function () { FakeWebSocket.replace(); }); + after(function () { FakeWebSocket.restore(); }); + + describe('as binary data', function () { + var sock; + beforeEach(function () { + sock = new Websock(); + sock.open('ws://', 'binary'); + sock._websocket._open(); + }); + + it('should convert the send queue into an ArrayBuffer', function () { + sock._sQ = [1, 2, 3]; + var res = sock._encode_message(); // An ArrayBuffer + expect(new Uint8Array(res)).to.deep.equal(new Uint8Array(res)); + }); + + it('should properly pass the encoded data off to the actual WebSocket', function () { + sock.send([1, 2, 3]); + expect(sock._websocket._get_sent_data()).to.deep.equal([1, 2, 3]); + }); + }); + + describe('as Base64 data', function () { + var sock; + beforeEach(function () { + sock = new Websock(); + sock.open('ws://', 'base64'); + sock._websocket._open(); + }); + + it('should convert the send queue into a Base64-encoded string', function () { + sock._sQ = [1, 2, 3]; + expect(sock._encode_message()).to.equal(Base64.encode([1, 2, 3])); + }); + + it('should properly pass the encoded data off to the actual WebSocket', function () { + sock.send([1, 2, 3]); + expect(sock._websocket._get_sent_data()).to.deep.equal([1, 2, 3]); + }); + + }); + + }); +}); diff --git a/src/sunstone/public/bower_components/no-vnc/tests/vnc_perf.html b/src/sunstone/public/bower_components/no-vnc/tests/vnc_perf.html index 18aba3557a..c439e95545 100644 --- a/src/sunstone/public/bower_components/no-vnc/tests/vnc_perf.html +++ b/src/sunstone/public/bower_components/no-vnc/tests/vnc_perf.html @@ -202,7 +202,7 @@ dbgmsg(" " + enc + ": " + VNC_frame_data_multi[enc].length); } rfb = new RFB({'target': $D('VNC_canvas'), - 'updateState': updateState}); + 'onUpdateState': updateState}); rfb.testMode(send_array, VNC_frame_encoding); } diff --git a/src/sunstone/public/bower_components/no-vnc/tests/vnc_playback.html b/src/sunstone/public/bower_components/no-vnc/tests/vnc_playback.html index 9d7f31f59c..b5faf93cce 100644 --- a/src/sunstone/public/bower_components/no-vnc/tests/vnc_playback.html +++ b/src/sunstone/public/bower_components/no-vnc/tests/vnc_playback.html @@ -131,7 +131,7 @@ if (fname) { message("VNC_frame_data.length: " + VNC_frame_data.length); rfb = new RFB({'target': $D('VNC_canvas'), - 'updateState': updateState}); + 'onUpdateState': updateState}); } } diff --git a/src/sunstone/public/bower_components/no-vnc/vnc_auto.html b/src/sunstone/public/bower_components/no-vnc/vnc_auto.html index 53b8220cfb..ff376fec16 100644 --- a/src/sunstone/public/bower_components/no-vnc/vnc_auto.html +++ b/src/sunstone/public/bower_components/no-vnc/vnc_auto.html @@ -198,7 +198,7 @@ 'local_cursor': WebUtil.getQueryVar('cursor', true), 'shared': WebUtil.getQueryVar('shared', true), 'view_only': WebUtil.getQueryVar('view_only', false), - 'updateState': updateState, + 'onUpdateState': updateState, 'onXvpInit': xvpInit, 'onPasswordRequired': passwordRequired}); rfb.connect(host, port, password, path); diff --git a/src/sunstone/public/bower_components/resumablejs/.bower.json b/src/sunstone/public/bower_components/resumablejs/.bower.json index 329eb3d778..2451629441 100644 --- a/src/sunstone/public/bower_components/resumablejs/.bower.json +++ b/src/sunstone/public/bower_components/resumablejs/.bower.json @@ -11,14 +11,13 @@ "Large files" ], "homepage": "https://github.com/23/resumable.js", - "_release": "703fc629fe", + "_release": "681185ad58", "_resolution": { "type": "branch", "branch": "master", - "commit": "703fc629feb01e8fe58f8faab62ceb2f24aaa1e6" + "commit": "681185ad5806f183b169e34b3614eb3dfcfc0ca0" }, "_source": "git://github.com/23/resumable.js.git", "_target": "*", - "_originalSource": "resumablejs", - "_direct": true + "_originalSource": "resumablejs" } \ No newline at end of file diff --git a/src/sunstone/public/bower_components/resumablejs/resumable.js b/src/sunstone/public/bower_components/resumablejs/resumable.js index 5e9f342483..06d86c60dd 100644 --- a/src/sunstone/public/bower_components/resumablejs/resumable.js +++ b/src/sunstone/public/bower_components/resumablejs/resumable.js @@ -50,7 +50,7 @@ generateUniqueIdentifier:null, maxChunkRetries:undefined, chunkRetryInterval:undefined, - permanentErrors:[404, 415, 500, 501], + permanentErrors:[400, 404, 415, 500, 501], maxFiles:undefined, withCredentials:false, xhrTimeout:0, @@ -345,7 +345,7 @@ if(c.status()=='error') error = true; ret += c.progress(true); // get chunk progress relative to entire file }); - ret = (error ? 1 : (ret>0.999 ? 1 : ret)); + ret = (error ? 1 : (ret>0.99999 ? 1 : ret)); ret = Math.max($._prevProgress, ret); // We don't want to lose percentages when an upload is paused $._prevProgress = ret; return(ret); @@ -710,7 +710,7 @@ // When new files are added, simply append them to the overall list input.addEventListener('change', function(e){ appendFilesFromFileList(e.target.files,e); - //e.target.value = ''; + e.target.value = ''; }, false); }); }; diff --git a/src/sunstone/public/js/opennebula.js b/src/sunstone/public/js/opennebula.js index 8add411150..9ec40960f9 100644 --- a/src/sunstone/public/js/opennebula.js +++ b/src/sunstone/public/js/opennebula.js @@ -639,6 +639,31 @@ var OpenNebula = { } }); }, + + "showback": function(params, resource, path){ + var callback = params.success; + var callback_error = params.error; + var data = params.data; + + var method = "showback"; + var request = OpenNebula.Helper.request(resource,method, data); + + var url = path ? path : resource.toLowerCase() + "/showback"; + + $.ajax({ + url: url, + type: "GET", + data: data, + dataType: "json", + success: function(response){ + return callback ? callback(request, response) : null; + }, + error: function(response){ + return callback_error ? + callback_error(request, OpenNebula.Error(response)) : null; + } + }); + } }, "Auth": { @@ -1045,6 +1070,9 @@ var OpenNebula = { "accounting": function(params){ OpenNebula.Action.accounting(params,OpenNebula.VM.resource); }, + "showback": function(params){ + OpenNebula.Action.showback(params,OpenNebula.VM.resource); + } }, "Group": { @@ -1842,7 +1870,7 @@ var OpenNebula = { return params.success ? params.success(request, response) : null; }, error: function(response){ - return params.error ? params.error(request, OpenNebula.Error(res)) : null; + return params.error ? params.error(request, OpenNebula.Error(response)) : null; } }); } diff --git a/src/sunstone/public/js/plugins/config-tab.js b/src/sunstone/public/js/plugins/config-tab.js index bb6e08ba9a..9aa8e549d2 100644 --- a/src/sunstone/public/js/plugins/config-tab.js +++ b/src/sunstone/public/js/plugins/config-tab.js @@ -31,6 +31,7 @@ Config = { var enabled = config['view']['enabled_tabs'][tab_name]; return enabled; }, + "isTabActionEnabled": function(tab_name, action_name, panel_name){ var enabled; if (panel_name) { @@ -51,6 +52,14 @@ Config = { } }, + "isFeatureEnabled": function(feature_name){ + if (config['view']['features'] && config['view']['features'][feature_name]) { + return true; + } else { + return false; + } + }, + "tabTableColumns": function(tab_name){ var columns = config['view']['tabs'][tab_name]['table_columns']; @@ -361,15 +370,6 @@ function setupConfigDialog() { }); } -function tr(str){ - var tmp = locale[str]; - if ( tmp == null || tmp == "" ) { - //console.debug("Missing translation: "+str); - tmp = str; - } - return tmp; -}; - function updateUserConfigInfo(request,user_json) { var info = user_json.USER; diff --git a/src/sunstone/public/js/plugins/groups-tab.js b/src/sunstone/public/js/plugins/groups-tab.js index 3ccc78ae79..a53f8aae65 100644 --- a/src/sunstone/public/js/plugins/groups-tab.js +++ b/src/sunstone/public/js/plugins/groups-tab.js @@ -737,10 +737,26 @@ function updateGroupInfo(request,group){ content: '
    ' }; + Sunstone.updateInfoPanelTab("group_info_panel","group_info_tab",info_tab); Sunstone.updateInfoPanelTab("group_info_panel","group_quotas_tab",quotas_tab); Sunstone.updateInfoPanelTab("group_info_panel","group_providers_tab",providers_tab); - Sunstone.updateInfoPanelTab("group_info_panel","group_accouning_tab",accounting_tab); + Sunstone.updateInfoPanelTab("group_info_panel","group_accounting_tab",accounting_tab); + + if (Config.isFeatureEnabled("showback")) { + var showback_tab = { + title: tr("Showback"), + icon: "fa-money", + content: '
    ' + }; + + Sunstone.updateInfoPanelTab("group_info_panel","group_showback_tab",showback_tab); + + showbackGraphs( + $("#group_showback","#group_info_panel"), + { fixed_group: info.ID }); + } + Sunstone.popUpInfoPanel("group_info_panel", 'groups-tab'); $("#add_rp_button", $("#group_info_panel")).click(function(){ diff --git a/src/sunstone/public/js/plugins/provision-tab.js b/src/sunstone/public/js/plugins/provision-tab.js index db8ee44732..d6c969da45 100644 --- a/src/sunstone/public/js/plugins/provision-tab.js +++ b/src/sunstone/public/js/plugins/provision-tab.js @@ -825,9 +825,10 @@ var provision_user_info = '
  • '+ '
  • '+ '
  • ') @@ -5146,6 +5249,21 @@ function setup_provision_user_info(context) { '') }) + if (Config.isFeatureEnabled("showback")) { + context.on("click", ".provision_vdc_user_info_show_showback", function(){ + $(".provision_vdc_info_container", context).html(""); + + showbackGraphs( + $(".provision_vdc_info_container", context), + { fixed_user: $(".provision_info_vdc_user", context).attr("opennebula_id")}); + + $(".provision_vdc_info_container", context).prepend( + '

    '+ + $(".provision_info_vdc_user", context).attr("uname") + ' ' + tr("Showback")+ + '

    ') + }) + }; + context.on("click", ".provision_vdc_user_delete_confirm_button", function(){ $(".provision_vdc_user_confirm_action", context).html( '
    '+ @@ -5188,7 +5306,7 @@ function setup_provision_user_info(context) { '×'+ '
    '); - context.on("click", "#provision_vdc_user_change_password_button", function(){ + context.on("click", ".provision_vdc_user_change_password_button", function(){ var button = $(this); button.attr("disabled", "disabled"); var user_id = $(".provision_info_vdc_user", context).attr("opennebula_id"); @@ -6294,26 +6412,17 @@ $(document).ready(function(){ var template_id = role.vm_template; var role_html_id = "#provision_create_flow_role_"+index; - generate_cardinality_selector( - $(".provision_cardinality_selector", context), - role); - OpenNebula.Template.show({ data : { id: template_id }, success: function(request,template_json){ var role_context = $(role_html_id) - //var template_nic = template_json.VMTEMPLATE.TEMPLATE.NIC - //var nics = [] - //if ($.isArray(template_nic)) - // nics = template_nic - //else if (!$.isEmptyObject(template_nic)) - // nics = [template_nic] -// - //generate_provision_instance_type_accordion( - // $(".provision_capacity_selector", role_context), - // template_json.VMTEMPLATE.TEMPLATE); + + generate_cardinality_selector( + $(".provision_cardinality_selector", context), + role, + template_json); if (template_json.VMTEMPLATE.TEMPLATE.USER_INPUTS) { generate_custom_attrs( diff --git a/src/sunstone/public/js/plugins/templates-tab.js b/src/sunstone/public/js/plugins/templates-tab.js index d191e5a014..c73c515b8a 100644 --- a/src/sunstone/public/js/plugins/templates-tab.js +++ b/src/sunstone/public/js/plugins/templates-tab.js @@ -14,6 +14,987 @@ /* limitations under the License. */ /* -------------------------------------------------------------------------- */ +var $template_create_wizard_str = "" + +if (Config.isTemplateCreationTabEnabled('general')){ + $template_create_wizard_str += '
    '+ + generate_capacity_tab_content() + + '
    '; +} + +if (Config.isTemplateCreationTabEnabled('storage')){ + $template_create_wizard_str += + '
    '+ + '
    '+ + '
    '+tr("Add another disk")+'
    '+ + '
    '+ + '
    '+ + '
    '+ + '
    '; +} + +if (Config.isTemplateCreationTabEnabled('network')){ + $template_create_wizard_str += + '
    '+ + '
    '+ + '
    '+tr("Add another nic")+'
    '+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + ''+ + ''+ + '
    '+ + '
    '+ + '
    '; +} +if (Config.isTemplateCreationTabEnabled('os_booting')){ + $template_create_wizard_str += +'
    '+ +'
    '+ +'
    '+ + '
    '+tr("Boot")+'
    '+ + '
    '+tr("Kernel")+'
    '+ + '
    '+tr("Ramdisk")+'
    '+ + '
    '+tr("Features")+'
    '+ +'
    '+ +'
    '+ + '
    '+ + '
    '+ + '
    '+ + ''+ + ''+ + '
    '+ + '
    '+ + ''+ + ''+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + ''+ + ''+ + '
    '+ + '
    '+ + ''+ + ''+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + ''+ + ''+ + '
    '+ + '
    '+ + ''+ + ''+ + '
    '+ + '
    '+ + ''+ + ''+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + ''+ + ''+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + ''+ + ''+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + ''+ + ''+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + '
    ' + + '' + + '
    ' + + '
    '+ + ''+ + '
    '+ + '
    '+ + '
    '+ + '
    ' + + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + '
    '+tr("ID")+''+tr("Owner")+''+tr("Group")+''+tr("Name")+''+tr("Datastore")+''+tr("Size")+''+tr("Type")+''+tr("Registration time")+''+tr("Persistent")+''+tr("Status")+''+tr("#VMS")+''+tr("Target")+'
    '+ + '
    '+ + '
    '+ + '
    '+ + '
    ' + + ''+tr("Please select a Kernel from the list")+''+ + ''+ + ''+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + ''+ + ''+ + '
    '+ + '
    '+ + '
    '+ + ''+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + '"+ + '"+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + '
    ' + + '' + + '
    ' + + '
    '+ + ''+ + '
    '+ + '
    '+ + '
    '+ + '
    ' + + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + '
    '+tr("ID")+''+tr("Owner")+''+tr("Group")+''+tr("Name")+''+tr("Datastore")+''+tr("Size")+''+tr("Type")+''+tr("Registration time")+''+tr("Persistent")+''+tr("Status")+''+tr("#VMS")+''+tr("Target")+'
    '+ + '
    '+ + '
    '+ + '
    '+ + '
    ' + + ''+tr("Please select a Ramdisk from the list")+''+ + ''+ + ''+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + ''+ + ''+ + '
    '+ + '
    '+ + '
    '+ + ''+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + ''+ + ''+ + '
    '+ + '
    '+ + ''+ + ''+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + ''+ + ''+ + '
    '+ + '
    '+ + ''+ + ''+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + ''+ + ''+ + '
    '+ + '
    '+ + ''+ + ''+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + ''+ + ''+ + '
    '+ + '
    '+ + '
    '+ +'
    '+ +'
    '+ +'
    '; + +} + +if (Config.isTemplateCreationTabEnabled('input_output')){ + $template_create_wizard_str += +'
    '+ + '
    '+ + '
    '+ + '
    '+ + ''+tr("Graphics")+''+ + '
    '+ + '
    '+ + ''+ + ''+ + ''+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + ''+ + '
    '+ + ''+ + ''+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + ''+ + ''+ + '
    '+ + '
    '+ + ''+ + ''+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + ''+ + ''+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + ''+tr("Inputs")+''+ + '
    '+ + '
    '+ + ''+ + '
    '+ + '
    '+ + ''+ + '
    '+ + '
    '+ + ''+tr("Add")+''+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + ''+ + '
    '+tr("TYPE")+''+tr("BUS")+'
    '+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ +'
    '; +} + +if (Config.isTemplateCreationTabEnabled('context')){ + $template_create_wizard_str += +'
    '+ + '
    '+ + '
    '+tr("Network & SSH")+'
    '+ + '
    '+tr("Files")+'
    '+ + '
    '+tr("User Inputs")+'
    '+ + '
    '+tr("Custom vars")+'
    '+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + ''+ + ''+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + ''+ + '