Hello community, here is the log from the commit of package velum for openSUSE:Factory checked in at 2018-02-22 15:03:31 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/velum (Old) and /work/SRC/openSUSE:Factory/.velum.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "velum" Thu Feb 22 15:03:31 2018 rev:7 rq:578943 version:3.0.0+dev+git_r650_4eb2d26dfbef4dd92b3b4685f1704ed8787d5732 Changes: -------- --- /work/SRC/openSUSE:Factory/velum/velum.changes 2018-02-18 11:42:51.856523989 +0100 +++ /work/SRC/openSUSE:Factory/.velum.new/velum.changes 2018-02-22 15:03:34.463568486 +0100 @@ -1,0 +2,9 @@ +Thu Feb 22 09:58:32 UTC 2018 - containers-bugow...@suse.de + +- Commit 4eb2d26 by James Mason jma...@suse.com + Add user interface for public cloud bootstrapping + + new file: app/assets/stylesheets/pages/instance_type.scss + + +------------------------------------------------------------------- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ velum.spec ++++++ --- /var/tmp/diff_new_pack.RjePRA/_old 2018-02-22 15:03:35.919516113 +0100 +++ /var/tmp/diff_new_pack.RjePRA/_new 2018-02-22 15:03:35.923515970 +0100 @@ -23,7 +23,7 @@ # Version: 1.0.0 # %%define branch 1.0.0 -Version: 3.0.0+dev+git_r646_67806291a2f2903835f76b154b3e1b4811873011 +Version: 3.0.0+dev+git_r650_4eb2d26dfbef4dd92b3b4685f1704ed8787d5732 Release: 0 %define branch master Summary: Dashboard for CaasP @@ -96,7 +96,7 @@ %description velum is the dashboard for CaasP to manage and deploy kubernetes clusters on top of MicroOS -This package has been built with commit 67806291a2f2903835f76b154b3e1b4811873011 from branch master on date Thu, 15 Feb 2018 15:53:13 +0000 +This package has been built with commit 4eb2d26dfbef4dd92b3b4685f1704ed8787d5732 from branch master on date Thu, 22 Feb 2018 09:57:53 +0000 %prep %setup -q -n velum-%{branch} ++++++ master.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/app/assets/javascripts/application.js new/velum-master/app/assets/javascripts/application.js --- old/velum-master/app/assets/javascripts/application.js 2018-02-15 16:53:11.000000000 +0100 +++ new/velum-master/app/assets/javascripts/application.js 2018-02-22 10:57:04.000000000 +0100 @@ -13,6 +13,7 @@ //= require jquery //= require jquery_ujs //= require bootstrap.min +//= require bootstrap-slider.min // //= require_tree ./dashboard //= require_tree ./setup @@ -21,4 +22,4 @@ $('body').on('click', '[disabled]', function(e) { e.preventDefault(); -}); \ No newline at end of file +}); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/app/assets/javascripts/cloud/bootstrap.js new/velum-master/app/assets/javascripts/cloud/bootstrap.js --- old/velum-master/app/assets/javascripts/cloud/bootstrap.js 1970-01-01 01:00:00.000000000 +0100 +++ new/velum-master/app/assets/javascripts/cloud/bootstrap.js 2018-02-22 10:57:04.000000000 +0100 @@ -0,0 +1,65 @@ +$(function() { + // https://stackoverflow.com/questions/10420352/converting-file-size-in-bytes-to-human-readable-string + // https://creativecommons.org/licenses/by-sa/4.0/ + function humanFileSize(bytes, si) { + var thresh = si ? 1000 : 1024; + if(Math.abs(bytes) < thresh) { + return bytes + ' B'; + } + var units = si + ? ['kB','MB','GB','TB','PB','EB','ZB','YB'] + : ['KiB','MiB','GiB','TiB','PiB','EiB','ZiB','YiB']; + var u = -1; + do { + bytes /= thresh; + ++u; + } while(Math.abs(bytes) >= thresh && u < units.length - 1); + return bytes.toFixed(1)+' '+units[u]; + } + + function calcClusterVcpus() { + vcpusPerVm = $('.instance-type-description .vcpu-count').data('vcpus'); + vmCount = clusterSize.getValue(); + $('#cluster-cpu-count').html(vcpusPerVm * vmCount); + } + + function calcClusterRam() { + bytesPerVm = $('.instance-type-description .ram-size').data('bytes'); + siUnits = $('.instance-type-description .ram-size').data('si'); + vmCount = clusterSize.getValue(); + totalBytes = bytesPerVm * vmCount; + $('#cluster-ram-size').attr('data-bytes', totalBytes); + $('#cluster-ram-size').html(humanFileSize(totalBytes, siUnits)) + } + + var updateClusterSize = function() { + calcClusterVcpus(); + calcClusterRam(); + } + + var clusterSize = $('#cloud_cluster_instance_count').slider() + .on('slide change', updateClusterSize).data('slider'); + + $('input[name="cloud_cluster[instance_type]"]').click(function() { + definition = $(this).siblings('.definition').html(); + $('.instance-type-description').html(definition); + ramSize = $('.instance-type-description .ram-size') + ramSize.html( + humanFileSize(ramSize.data('bytes'), ramSize.data('si')) + ) + + if (this.id === 'cloud_cluster_instance_type_custom') { + $('.cluster-cpu-count,.cluster-ram-size').hide(); + $('input#cloud_cluster_instance_type_custom[type="text"]'). + show().focus(); + } else { + $('input#cloud_cluster_instance_type_custom[type="text"]'). + val("").hide(); + updateClusterSize(); + $('.cluster-cpu-count,.cluster-ram-size').show(); + } + }); + + // kick things off + $('input[name="cloud_cluster[instance_type]"][checked="checked"]').click(); +}); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/app/assets/stylesheets/application.scss new/velum-master/app/assets/stylesheets/application.scss --- old/velum-master/app/assets/stylesheets/application.scss 2018-02-15 16:53:11.000000000 +0100 +++ new/velum-master/app/assets/stylesheets/application.scss 2018-02-22 10:57:04.000000000 +0100 @@ -6,6 +6,7 @@ @import 'bootstrap-sprockets'; @import 'velum_bootstrap-variables'; @import 'bootstrap'; +@import 'bootstrap-slider.min'; @import 'components/**/*'; @import 'velum_general'; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/app/assets/stylesheets/pages/instance_type.scss new/velum-master/app/assets/stylesheets/pages/instance_type.scss --- old/velum-master/app/assets/stylesheets/pages/instance_type.scss 1970-01-01 01:00:00.000000000 +0100 +++ new/velum-master/app/assets/stylesheets/pages/instance_type.scss 2018-02-22 10:57:04.000000000 +0100 @@ -0,0 +1,106 @@ +.instance-type-selector { + background-color: $panel-default-heading-bg; + + .instance-types { + border-right: 1px solid $table-border-color; + } + + label.instance-type { + + input[type="radio"] { + display: none; + + &:checked + .instance-type-box { + background-color: $brand-success; + color: $table-bg; + box-shadow: none; + } + } + + .instance-type-box { + display: inline-block; + overflow: hidden; + width: 148px; + height: 148px; + + border: 1px solid $table-border-color; + border-radius: 5px; + box-shadow: 1px 1px 6px 1px $table-border-color; + + background: $table-bg; + color: $brand-success; + + margin: 10px; + padding: 10px; + + font-size: 14pt; + text-align: center; + font-weight: normal; + word-wrap: break-word; + + &.double { + width: 316px; + } + } + + .definition { + display: none; + } + } + + .instance-type-description { + h1, h2, h3, h4, h5, h6 { + color: #3d4042; + } + } +} + +.cluster-size-selector { + background-color: $panel-default-heading-bg; + min-height: 113px; + + .slider { + &.slider-horizontal { + width: 100% !important; + } + + .slider-handle { + background: $brand-success; + border: 1px solid $brand-success; + box-shadow: none; + } + + .slider-track { + background: $table-bg; + border: 1px solid $table-border-color; + } + + .slider-track-low, .slider-track-high { + background: $table-bg; + } + + .slider-selection { + background: $brand-success; //#337ab7; + } + + .slider-tick { + background: $table-bg; + border: 1px solid $table-border-color; + box-shadow: none; + + &.in-selection { + background: $brand-success; //#337ab7; + border: 1px solid $brand-success; //#337ab7; + } + } + } + + label { + display: block; + + &.h3 { + margin-top: 0; + margin-bottom: 0; + } + } +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/app/controllers/internal_api/v1/pillars_controller.rb new/velum-master/app/controllers/internal_api/v1/pillars_controller.rb --- old/velum-master/app/controllers/internal_api/v1/pillars_controller.rb 2018-02-15 16:53:11.000000000 +0100 +++ new/velum-master/app/controllers/internal_api/v1/pillars_controller.rb 2018-02-22 10:57:04.000000000 +0100 @@ -1,14 +1,18 @@ # Serve the pillar information class InternalApi::V1::PillarsController < InternalApiController def show - ok content: pillar_contents.merge(registry_contents) + ok content: pillar_contents.merge( + registry_contents + ).merge( + cloud_framework_contents + ) end private def pillar_contents pillar_struct = {}.tap do |h| - Pillar.all_pillars.each do |k, v| + Pillar.simple_pillars.each do |k, v| h[v] = Pillar.value(pillar: k.to_sym) unless Pillar.value(pillar: k.to_sym).nil? end end @@ -40,4 +44,34 @@ end { registries: (registries + registry_mirrors) } end + + def cloud_framework_contents + case Pillar.value(pillar: :cloud_framework) + when "ec2" + ec2_cloud_contents + else + {} + end + end + + def ec2_cloud_contents + { + cloud: { + framework: "ec2", + profiles: { + cluster_node: { + size: Pillar.value(pillar: :cloud_worker_type), + network_interfaces: [ + { + DeviceIndex: 0, + AssociatePublicIpAddress: false, + SubnetId: Pillar.value(pillar: :cloud_worker_subnet), + SecurityGroupId: Pillar.value(pillar: :cloud_worker_security_group) + } + ] + } + } + } + } + end end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/app/controllers/setup_controller.rb new/velum-master/app/controllers/setup_controller.rb --- old/velum-master/app/controllers/setup_controller.rb 2018-02-15 16:53:11.000000000 +0100 +++ new/velum-master/app/controllers/setup_controller.rb 2018-02-22 10:57:04.000000000 +0100 @@ -1,4 +1,5 @@ require "velum/salt" +require "velum/instance_type" # SetupController is responsible for everything related to the bootstrapping # process: @@ -52,6 +53,38 @@ def worker_bootstrap @controller_node = Pillar.value pillar: :dashboard + + return unless (cloud = Pillar.value(pillar: :cloud_framework)) + + @instance_types = Velum::InstanceType.for(cloud) + @cloud_cluster = CloudCluster.new(cloud_framework: cloud) + case cloud + when "ec2" + @cloud_cluster.instance_type = Pillar.value( + pillar: :cloud_worker_type + ) || @instance_types.first.key + @cloud_cluster.subnet_id = Pillar.value( + pillar: :cloud_worker_subnet + ) || "subnet-" + @cloud_cluster.security_group_id = Pillar.value( + pillar: :cloud_worker_security_group + ) || "sg-" + end + render "worker_bootstrap_#{cloud}".to_sym + end + + def build_cloud_cluster + @cloud_cluster = CloudCluster.new(cloud_cluster_params) + + if @cloud_cluster.save + Velum::Salt.build_cloud_cluster(@cloud_cluster.instance_count) + redirect_to setup_discovery_path, + notice: "Starting to build #{@cloud_cluster}..." + else + flash.keep + redirect_to setup_worker_bootstrap_path, + flash: { error: @cloud_cluster.errors.full_messages.to_sentence } + end end def set_roles @@ -135,6 +168,21 @@ [parameters] end + def cloud_cluster_params + cloud_cluster = params.require(:cloud_cluster).permit( + :instance_type, + :instance_type_custom, + :instance_count, + :vnet_id, + :subnet_id, + :security_group_id, + :publishsettings, + :media_link + ) + cloud_cluster["cloud_framework"] = Pillar.value(pillar: :cloud_framework) + cloud_cluster + end + def update_nodes_params params.require(:roles) end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/app/models/cloud_cluster.rb new/velum-master/app/models/cloud_cluster.rb --- old/velum-master/app/models/cloud_cluster.rb 1970-01-01 01:00:00.000000000 +0100 +++ new/velum-master/app/models/cloud_cluster.rb 2018-02-22 10:57:04.000000000 +0100 @@ -0,0 +1,55 @@ +# frozen_string_literal: true +require "velum/salt" +# CloudCluster represents user-configured attributes of a cloud deployment. +class CloudCluster + include ActiveModel::Model + attr_accessor :cloud_framework, + :instance_count, :instance_type, :instance_type_custom, + :subnet_id, :security_group_id # EC2 + + def initialize(*args) + super + if @instance_type.blank? || @instance_type == "CUSTOM" + @instance_type = instance_type_custom + end + @instance_count = @instance_count.to_i + end + + def to_s + parts = ["a cluster of #{@instance_count} #{@instance_type} instances"] + parts.push("in the #{@subnet_id} subnet") if @subnet_id + parts.push("in the #{@security_group_id} security group") if @security_group_id + case @cloud_framework + when "ec2" + parts.push("in EC2") + end + parts.join(" ") + end + + def save! + case @cloud_framework + when "ec2" + persist_to_pillar!(:cloud_worker_type, @instance_type) + persist_to_pillar!(:cloud_worker_subnet, @subnet_id) + persist_to_pillar!(:cloud_worker_security_group, @security_group_id) + Velum::Salt.call(action: "saltutil.refresh_pillar") + end + end + + def save + save! + return true + rescue ActiveRecord::ActiveRecordError, + Velum::SaltApi::SaltConnectionException => e + errors[:base] << e.message + return false + end + + private + + def persist_to_pillar!(pillar_key, value) + pillar = Pillar.find_or_initialize_by(pillar: Pillar.all_pillars[pillar_key]) + pillar.value = value + pillar.save! + end +end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/app/models/pillar.rb new/velum-master/app/models/pillar.rb --- old/velum-master/app/models/pillar.rb 2018-02-15 16:53:11.000000000 +0100 +++ new/velum-master/app/models/pillar.rb 2018-02-22 10:57:04.000000000 +0100 @@ -13,6 +13,10 @@ end def all_pillars + simple_pillars.merge(cloud_worker_pillars) + end + + def simple_pillars { dashboard: "dashboard", dashboard_external_fqdn: "dashboard_external_fqdn", @@ -47,6 +51,19 @@ } end + # rubocop:disable Layout/AlignHash + def cloud_worker_pillars + { + cloud_worker_type: + "cloud:profiles:cluster_node:size", + cloud_worker_subnet: + "cloud:profiles:cluster_node:network_interfaces:SubnetId", + cloud_worker_security_group: + "cloud:profiles:cluster_node:network_interfaces:SecurityGroupId" + } + end + # rubocop:enable Layout/AlignHash + # Apply the given pillars into the database. It returns an array with the # encountered errors. def apply(pillars, required_pillars: [], unprotected_pillars: []) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/app/views/setup/_cluster_size_panel.html.slim new/velum-master/app/views/setup/_cluster_size_panel.html.slim --- old/velum-master/app/views/setup/_cluster_size_panel.html.slim 1970-01-01 01:00:00.000000000 +0100 +++ new/velum-master/app/views/setup/_cluster_size_panel.html.slim 2018-02-22 10:57:04.000000000 +0100 @@ -0,0 +1,16 @@ +.panel.panel-default + .panel-heading + h3.panel-title Cluster size + .panel-body.cluster-size-selector + .col-md-8 + label for="cloud_cluster_instance_count" Number of instances + = form.text_field :instance_count, data: {slider_min: 3, slider_value: 5, slider_max: 50, slider_scale: "logarithmic", slider_tooltip: "always", slider_tooltip_position: "bottom", slider_ticks: [3, 5, 10, 20, 50], slider_ticks_snap_bounds: 0.5}, class: 'h3' + .col-md-2.text-right.cluster-cpu-count + label Total vCPUs + label.h3#cluster-cpu-count = 0 + .col-md-2.text-right.cluster-ram-size + label Total RAM + label.h3#cluster-ram-size data-bytes=0 = 0 + .panel-footer + h4 Tip + p At least three nodes are required for a reliable cluster. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/app/views/setup/_instance_type_panel.html.slim new/velum-master/app/views/setup/_instance_type_panel.html.slim --- old/velum-master/app/views/setup/_instance_type_panel.html.slim 1970-01-01 01:00:00.000000000 +0100 +++ new/velum-master/app/views/setup/_instance_type_panel.html.slim 2018-02-22 10:57:04.000000000 +0100 @@ -0,0 +1,56 @@ +.panel.panel-default + .panel-heading + h3.panel-title Instance Type + .panel-body.instance-type-selector + .row + .col-md-8.instance-types + .form-group + - @instance_types.each do |instance_type| + label.instance-type + = form.radio_button :instance_type, instance_type.key + .instance-type-box + = instance_type.category.name + br + small = instance_type.key + .definition + h2 = instance_type.category.name + h3 = instance_type.key + p + = instance_type.category.description + p + - instance_type.category.features.each do |feature| + ' + span.label.label-default = feature + + dl + dt vCPUs + dd.vcpu-count(data-vcpus="#{instance_type.vcpu_count}") + = pluralize(instance_type.vcpu_count, 'core') + dt RAM + dd.ram-size(data-bytes="#{instance_type.ram_bytes}" + data-si="#{instance_type.ram_si_units}") + - instance_type.details.each do |key, value| + dt + = key + dd + = value + label.instance-type + = form.radio_button :instance_type, 'CUSTOM' + .instance-type-box.double + ' Other types… + br + br + small = form.text_field :instance_type_custom, class: "form-control", style: "display: none;" + .definition + p + ' You may specify a preferred instance type with a minimum of + ' are 2 vCPUs and 8GB of RAM; 4 vCPUs are recommended. + + + .col-md-4.instance-type-description + .panel-footer + h4 Tip + p + ' Not sure which type of instance to use? Check the + = link_to "Instance Types", list_url + ' list. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/app/views/setup/worker_bootstrap_ec2.html.slim new/velum-master/app/views/setup/worker_bootstrap_ec2.html.slim --- old/velum-master/app/views/setup/worker_bootstrap_ec2.html.slim 1970-01-01 01:00:00.000000000 +0100 +++ new/velum-master/app/views/setup/worker_bootstrap_ec2.html.slim 2018-02-22 10:57:04.000000000 +0100 @@ -0,0 +1,38 @@ += content_for :body_class, "worker_bootstrap" + +h1 + ' Bootstrap your CaaS Platform + small in Amazon Web Services' Elastic Compute Cloud + + += form_for @cloud_cluster, url: setup_build_cloud_cluster_path do |form| + = form.hidden_field :cloud_framework + p + ' In order to complete the installation, it is necessary to bootstrap a few + ' additional nodes, those will be the Kubernetes Master and Workers. + + = render "instance_type_panel", + form: form, + list_url: "https://aws.amazon.com/ec2/instance-types/" + + = render "cluster_size_panel", form: form + + .panel.panel-default + .panel-heading + h3.panel-title Networking + .panel-body + .col-md-4 + .form-group + label for="cloud_cluster_subnet_id" Subnet ID + = form.text_field :subnet_id, class: "form-control" + .col-md-4 + .form-group + label for="cloud_cluster_security_group_id" Security Group ID + = form.text_field :security_group_id, class: "form-control" + + .clearfix.text-right.steps-container + = link_to "Back", setup_path, class: "btn btn-danger" + = form.submit "Next", class: "btn btn-primary" + +- content_for :page_javascript do + = javascript_include_tag 'cloud/bootstrap', defer: true diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/config/initializers/assets.rb new/velum-master/config/initializers/assets.rb --- old/velum-master/config/initializers/assets.rb 2018-02-15 16:53:11.000000000 +0100 +++ new/velum-master/config/initializers/assets.rb 2018-02-22 10:57:04.000000000 +0100 @@ -9,4 +9,7 @@ # Precompile additional assets. # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. # Rails.application.config.assets.precompile += %w( search.js ) -Rails.application.config.assets.precompile += ["authentication.css"] +Rails.application.config.assets.precompile += [ + "authentication.css", + "cloud/bootstrap.js" +] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/config/routes.rb new/velum-master/config/routes.rb --- old/velum-master/config/routes.rb 2018-02-15 16:53:11.000000000 +0100 +++ new/velum-master/config/routes.rb 2018-02-22 10:57:04.000000000 +0100 @@ -37,7 +37,8 @@ namespace :setup do get "/", action: :welcome match "/", action: :configure, via: [:put, :patch] - get :"worker-bootstrap" + get "worker-bootstrap" + post :build_cloud_cluster get :discovery post :discovery, action: :set_roles get :bootstrap diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/lib/velum/salt.rb new/velum-master/lib/velum/salt.rb --- old/velum-master/lib/velum/salt.rb 2018-02-15 16:53:11.000000000 +0100 +++ new/velum-master/lib/velum/salt.rb 2018-02-22 10:57:04.000000000 +0100 @@ -1,4 +1,5 @@ require "velum/salt_api" +require "securerandom" module Velum # This class allows to interact with global salt actions @@ -34,6 +35,23 @@ [needed["return"], failed["return"]] end + # Trigger salt-cloud to construct a cluster + def self.build_cloud_cluster(count) + instance_names = (1..count).collect { "caasp-node-" + SecureRandom.hex(4) } + instance_names.collect do |instance_name| + perform_request( + endpoint: "/", + method: "post", + data: { + client: "local_async", + tgt: "admin", + fun: "cloud.profile", + arg: ["cluster_node", instance_name] + } + ) + end + end + # Returns the minions as discovered by salt. def self.minions res = perform_request(endpoint: "/minions", method: "get") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/spec/controllers/internal_api/v1/pillars_controller_spec.rb new/velum-master/spec/controllers/internal_api/v1/pillars_controller_spec.rb --- old/velum-master/spec/controllers/internal_api/v1/pillars_controller_spec.rb 2018-02-15 16:53:11.000000000 +0100 +++ new/velum-master/spec/controllers/internal_api/v1/pillars_controller_spec.rb 2018-02-22 10:57:04.000000000 +0100 @@ -74,4 +74,56 @@ expect(json).to match expected_registries_response end end + + context "when in EC2 framework" do + let(:custom_instance_type) { "custom-instance-type" } + let(:subnet_id) { "subnet-9d4a7b6c" } + let(:security_group_id) { "sg-903004f8" } + + let(:expected_response) do + { + registries: [], + cloud: { + framework: "ec2", + profiles: { + cluster_node: { + size: custom_instance_type, + network_interfaces: [ + { + DeviceIndex: 0, + AssociatePublicIpAddress: false, + SubnetId: subnet_id, + SecurityGroupId: security_group_id + } + ] + } + } + } + } + end + + before do + create(:ec2_pillar) + create( + :pillar, + pillar: "cloud:profiles:cluster_node:size", + value: custom_instance_type + ) + create( + :pillar, + pillar: "cloud:profiles:cluster_node:network_interfaces:SubnetId", + value: subnet_id + ) + create( + :pillar, + pillar: "cloud:profiles:cluster_node:network_interfaces:SecurityGroupId", + value: security_group_id + ) + end + + it "has remote registries and respective mirrors" do + get :show + expect(json).to eq(expected_response) + end + end end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/spec/controllers/setup_controller_spec.rb new/velum-master/spec/controllers/setup_controller_spec.rb --- old/velum-master/spec/controllers/setup_controller_spec.rb 2018-02-15 16:53:11.000000000 +0100 +++ new/velum-master/spec/controllers/setup_controller_spec.rb 2018-02-22 10:57:04.000000000 +0100 @@ -88,6 +88,104 @@ get :worker_bootstrap expect(assigns(:controller_node)).to eq("localhost") end + + context "when in EC2 framework" do + before do + create(:ec2_pillar) + get :worker_bootstrap + end + + it "assigns @instance_sizes" do + expect(assigns(:instance_types)).to all(be_a(Velum::InstanceType)) + end + + it "renders EC2 view" do + expect(response).to render_template(:worker_bootstrap_ec2) + end + end + end + + describe "POST /setup/worker-boostrap via HTML in EC2" do + let(:instance_type) { "t2.xlarge" } + let(:instance_count) { 5 } + let(:subnet_id) { "subnet-9d4a7b6c" } + let(:security_group_id) { "sg-903004f8" } + let(:cloud_cluster_params) do + { + instance_type: instance_type, + instance_count: instance_count, + subnet_id: subnet_id, + security_group_id: security_group_id + } + end + + before do + sign_in user + create(:ec2_pillar) + Pillar.create pillar: "dashboard", value: "localhost" + + allow(Velum::Salt).to receive(:build_cloud_cluster) + end + + context "when saving succeeds" do + before do + ensure_pillar_refresh do + post :build_cloud_cluster, cloud_cluster: cloud_cluster_params + end + end + + it "always uses the framework pillar" do + expect(assigns(:cloud_cluster).cloud_framework).to eq("ec2") + end + + it "assigns the instance type" do + expect(assigns(:cloud_cluster).instance_type).to eq(instance_type) + end + + it "assigns the quantity of workers" do + expect(assigns(:cloud_cluster).instance_count).to eq(instance_count) + end + + it "assigns the EC2 subnet ID" do + expect(assigns(:cloud_cluster).subnet_id).to eq(subnet_id) + end + + it "assigns the EC2 security group ID" do + expect(assigns(:cloud_cluster).security_group_id).to eq(security_group_id) + end + + it "calls salt-cloud" do + expect(Velum::Salt).to have_received(:build_cloud_cluster).with(instance_count).once + end + + it "uses a flash to provide confirmation" do + expect(flash[:notice]).to be_present + end + end + + context "when saving fails" do + let(:error_message) { "Nope!" } + let(:mock_cloud_cluster) do + mock = CloudCluster.new(cloud_cluster_params) + allow(mock).to receive(:save!).and_raise( + ActiveRecord::ActiveRecordError.new(error_message) + ) + mock + end + + before do + allow(CloudCluster).to receive(:new).and_return(mock_cloud_cluster) + post :build_cloud_cluster, cloud_cluster: cloud_cluster_params + end + + it "redirects back to bootstrap" do + expect(controller).to redirect_to(:setup_worker_bootstrap) + end + + it "uses a flash to show error messages" do + expect(flash[:error]).to be_present + end + end end describe "POST /setup/discovery via HTML" do diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/spec/factories/pillar_factory.rb new/velum-master/spec/factories/pillar_factory.rb --- old/velum-master/spec/factories/pillar_factory.rb 2018-02-15 16:53:11.000000000 +0100 +++ new/velum-master/spec/factories/pillar_factory.rb 2018-02-22 10:57:04.000000000 +0100 @@ -7,4 +7,8 @@ pillar { Pillar.all_pillars[:apiserver] } value "myapiserver.example.com" end + factory :ec2_pillar, parent: :pillar do + pillar { Pillar.all_pillars[:cloud_framework] } + value "ec2" + end end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/spec/features/bootstrap_in_ec2_feature_spec.rb new/velum-master/spec/features/bootstrap_in_ec2_feature_spec.rb --- old/velum-master/spec/features/bootstrap_in_ec2_feature_spec.rb 1970-01-01 01:00:00.000000000 +0100 +++ new/velum-master/spec/features/bootstrap_in_ec2_feature_spec.rb 2018-02-22 10:57:04.000000000 +0100 @@ -0,0 +1,70 @@ +require "rails_helper" +require "velum/instance_type" + +describe "Feature: Bootstrap a cluster in EC2" do + let(:user) { create(:user) } + let(:instance_types) { Velum::InstanceType.for("ec2") } + let(:custom_instance_type) { OpenStruct.new(key: "CUSTOM") } + + before do + login_as user, scope: :user + create(:ec2_pillar) + setup_stubbed_update_status! + visit setup_worker_bootstrap_path + end + + it "refers to EC2 in the heading" do + expect(page).to have_css("h1", text: "Elastic Compute Cloud") + end + + it "allows selection of an instance type" do + instance_types.each do |instance_type| + expect(page).to have_css(instance_type_radio_finder(instance_type)) + end + end + + it "displays the category of the selected instance type", js: true do + instance_types.each do |instance_type| + click_instance_type_radio(instance_type) + expect(page).to have_text(:visible, instance_type.category.name) + expect(page).to have_text(:visible, instance_type.category.description) + end + end + + it "hides the custom instance type box by default", js: true do + expect(page).not_to have_css("input#cloud_cluster_instance_type_custom[type='text']") + end + + it "shows the textbox when choosing a custom instance type", js: true do + click_instance_type_radio(custom_instance_type) + expect(page).to have_css("input#cloud_cluster_instance_type_custom[type='text']") + end + + context "when sizing the cluster" do + let(:cluster_size) do + page.find("#cloud_cluster_instance_count", visible: :any)["data-slider-value"].to_i + end + + it "calculates the total cluster vCPU count", js: true do + instance_types.each do |instance_type| + total = instance_type.vcpu_count * cluster_size + click_instance_type_radio(instance_type) + expect(page).to have_css("#cluster-cpu-count", text: total) + end + end + + it "calculates the total cluster RAM size", js: true do + instance_types.each do |instance_type| + total = instance_type.ram_bytes * cluster_size + click_instance_type_radio(instance_type) + expect(page.find("#cluster-ram-size")["data-bytes"].to_i).to eq(total) + end + end + + it "hides calculations for custom types", js: true do + click_instance_type_radio(custom_instance_type) + expect(page).not_to have_css("#cluster-cpu-count") + expect(page).not_to have_css("#cluster-ram-count") + end + end +end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/spec/lib/velum/salt_spec.rb new/velum-master/spec/lib/velum/salt_spec.rb --- old/velum-master/spec/lib/velum/salt_spec.rb 2018-02-15 16:53:11.000000000 +0100 +++ new/velum-master/spec/lib/velum/salt_spec.rb 2018-02-22 10:57:04.000000000 +0100 @@ -76,4 +76,16 @@ end end end + + describe "#build_cloud_cluster" do + let(:count) { rand(49) + 1 } # 1..50 + + it "calls cloud.profile salt function the specified number of times" do + VCR.use_cassette("salt/cloud_profile", record: :none, allow_playback_repeats: true) do + responses = described_class.build_cloud_cluster(count) + expect(responses.length).to eq(count) + expect(responses).to all(be_a(Net::HTTPOK)) + end + end + end end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/spec/models/cloud_cluster_spec.rb new/velum-master/spec/models/cloud_cluster_spec.rb --- old/velum-master/spec/models/cloud_cluster_spec.rb 1970-01-01 01:00:00.000000000 +0100 +++ new/velum-master/spec/models/cloud_cluster_spec.rb 2018-02-22 10:57:04.000000000 +0100 @@ -0,0 +1,116 @@ +require "rails_helper" +require "velum/salt_api" + +describe CloudCluster do + let(:custom_instance_type) { "custom-instance-type" } + let(:subnet_id) { "subnet-9d4a7b6c" } + let(:security_group_id) { "sg-903004f8" } + let(:instance_count) { 5 } + + it "can implicitly represent a custom instance type" do + cluster = described_class.new(instance_type_custom: custom_instance_type) + expect(cluster.instance_type).to be(custom_instance_type) + end + + it "can explicity represent a custom instance type" do + cluster = described_class.new( + instance_type: "CUSTOM", + instance_type_custom: custom_instance_type + ) + expect(cluster.instance_type).to be(custom_instance_type) + end + + context "when represented as a string" do + let(:cluster) do + described_class.new( + instance_type: custom_instance_type, + instance_count: instance_count, + subnet_id: subnet_id, + security_group_id: security_group_id + ) + end + + it "counts out the instances" do + substring = "a cluster of #{instance_count} #{custom_instance_type} instances" + expect(cluster.to_s).to match(substring) + end + + it "describes the subnet" do + substring = "in the #{subnet_id} subnet" + expect(cluster.to_s).to match(substring) + end + + it "describes the security group" do + substring = "in the #{security_group_id} security group" + expect(cluster.to_s).to match(substring) + end + end + + context "when saving, behave like ActiveRecord#save" do + let(:cluster) { described_class.new } + let(:handled_exceptions) do + [ + ActiveRecord::ActiveRecordError.new("Didn't work!"), + Velum::SaltApi::SaltConnectionException.new("You're bad at this.") + ] + end + + it "returns true" do + allow(cluster).to receive(:save!) + expect(cluster.save).to be(true) + end + + it "returns false when there is an exception" do + handled_exceptions.each do |exception| + allow(cluster).to receive(:save!).and_raise(exception) + expect(cluster.save).to be(false) + end + end + + it "captures downstream messages to the errors collection" do + handled_exceptions.each do |exception| + allow(cluster).to receive(:save!).and_raise(exception) + cluster.save + expect(cluster.errors[:base]).to include(exception.message) + end + end + end + + context "when framework is EC2" do + let(:framework) { "ec2" } + let(:cluster) do + described_class.new( + cloud_framework: framework, + instance_type: custom_instance_type, + subnet_id: subnet_id, + security_group_id: security_group_id + ) + end + + it "stores instance type as :cloud_worker_type Pillar and refreshes" do + ensure_pillar_refresh do + expect(cluster.save).to be(true) + end + expect(Pillar.value(pillar: :cloud_worker_type)).to eq(custom_instance_type) + end + + it "stores subnet ID as :cloud_worker_subnet Pillar and refreshes" do + ensure_pillar_refresh do + expect(cluster.save).to be(true) + end + expect(Pillar.value(pillar: :cloud_worker_subnet)).to eq(subnet_id) + end + + it "stores security group ID as :cloud_worker_security_group Pillar and refreshes" do + ensure_pillar_refresh do + expect(cluster.save).to be(true) + end + expect(Pillar.value(pillar: :cloud_worker_security_group)).to eq(security_group_id) + end + + it "describes the framework in string representation" do + substring = "in EC2" + expect(cluster.to_s).to match(substring) + end + end +end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/spec/support/helpers.rb new/velum-master/spec/support/helpers.rb --- old/velum-master/spec/support/helpers.rb 2018-02-15 16:53:11.000000000 +0100 +++ new/velum-master/spec/support/helpers.rb 2018-02-22 10:57:04.000000000 +0100 @@ -23,4 +23,16 @@ end end +def click_instance_type_radio(instance_type) + page.execute_script('$("' + instance_type_radio_finder(instance_type) + '").click()') +end + +def instance_type_radio_finder(instance_type) + [ + "input[type='radio']", + "[name='cloud_cluster[instance_type]']", + "[value='#{instance_type.key}']" + ].join +end + RSpec.configure { |config| config.include Helpers, type: :feature } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/spec/support/utils.rb new/velum-master/spec/support/utils.rb --- old/velum-master/spec/support/utils.rb 2018-02-15 16:53:11.000000000 +0100 +++ new/velum-master/spec/support/utils.rb 2018-02-22 10:57:04.000000000 +0100 @@ -19,6 +19,17 @@ def setup_undone Pillar.delete_all end + + def ensure_pillar_refresh + VCR.use_cassette( + "salt/refresh_pillar", + allow_unused_http_interactions: false, + allow_playback_repeats: true, + record: :none + ) do + yield + end + end end RSpec.configure { |config| config.include Utils } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/spec/vcr_cassettes/salt/cloud_profile.yml new/velum-master/spec/vcr_cassettes/salt/cloud_profile.yml --- old/velum-master/spec/vcr_cassettes/salt/cloud_profile.yml 1970-01-01 01:00:00.000000000 +0100 +++ new/velum-master/spec/vcr_cassettes/salt/cloud_profile.yml 2018-02-22 10:57:04.000000000 +0100 @@ -0,0 +1,107 @@ +--- +http_interactions: +- request: + method: post + uri: https://127.0.0.1:8000/login + body: + encoding: UTF-8 + string: '{"username":"saltapi","password":"l+ZtDm9lG1DPdt/QyFfABgWtCN/IKwnmGTK8nCt++PiOVG9Y2NccIrozchvz7RtxREIZe5CshcO0","eauth":"pam"}' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - application/json; charset=utf-8 + User-Agent: + - Ruby + Host: + - 127.0.0.1:8000 + Content-Type: + - application/json; charset=utf-8 + response: + status: + code: 200 + message: OK + headers: + Content-Length: + - '217' + Access-Control-Expose-Headers: + - GET, POST + Vary: + - Accept-Encoding + Server: + - CherryPy/3.6.0 + Allow: + - GET, HEAD, POST + Access-Control-Allow-Credentials: + - 'true' + Date: + - Mon, 05 Feb 2018 20:57:34 GMT + Access-Control-Allow-Origin: + - "*" + X-Auth-Token: + - 507fef45a35e7038c9a1cdb754acfc7539aac4a2 + Content-Type: + - application/json + Set-Cookie: + - session_id=507fef45a35e7038c9a1cdb754acfc7539aac4a2; expires=Tue, 06 Feb 2018 + 06:57:34 GMT; Path=/ + body: + encoding: UTF-8 + string: '{"return": [{"perms": [".*", "@wheel", "@runner", "@jobs", "@events"], + "start": 1517864254.835655, "token": "507fef45a35e7038c9a1cdb754acfc7539aac4a2", + "expire": 1517907454.835655, "user": "saltapi", "eauth": "pam"}]}' + http_version: + recorded_at: Mon, 05 Feb 2018 20:57:34 GMT +- request: + method: post + uri: https://127.0.0.1:8000/ + body: + encoding: UTF-8 + string: '{"client":"local_async","tgt":"admin","fun":"cloud.profile","arg":["cluster_node","caasp-node-d43efb76"]}' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - application/json; charset=utf-8 + User-Agent: + - Ruby + Host: + - 127.0.0.1:8000 + Content-Type: + - application/json; charset=utf-8 + X-Auth-Token: + - 507fef45a35e7038c9a1cdb754acfc7539aac4a2 + response: + status: + code: 200 + message: OK + headers: + Content-Length: + - '67' + Access-Control-Expose-Headers: + - GET, POST + Cache-Control: + - private + Vary: + - Accept-Encoding + Server: + - CherryPy/3.6.0 + Allow: + - GET, HEAD, POST + Access-Control-Allow-Credentials: + - 'true' + Date: + - Mon, 05 Feb 2018 20:57:34 GMT + Access-Control-Allow-Origin: + - "*" + Content-Type: + - application/json + Set-Cookie: + - session_id=507fef45a35e7038c9a1cdb754acfc7539aac4a2; expires=Tue, 06 Feb 2018 + 06:57:34 GMT; Path=/ + body: + encoding: UTF-8 + string: '{"return": [{"jid": "20180205205734878026", "minions": ["admin"]}]}' + http_version: + recorded_at: Mon, 05 Feb 2018 20:57:34 GMT +recorded_with: VCR 3.0.3 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/spec/vcr_cassettes/salt/refresh_pillar.yml new/velum-master/spec/vcr_cassettes/salt/refresh_pillar.yml --- old/velum-master/spec/vcr_cassettes/salt/refresh_pillar.yml 1970-01-01 01:00:00.000000000 +0100 +++ new/velum-master/spec/vcr_cassettes/salt/refresh_pillar.yml 2018-02-22 10:57:04.000000000 +0100 @@ -0,0 +1,107 @@ +--- +http_interactions: +- request: + method: post + uri: https://127.0.0.1:8000/login + body: + encoding: UTF-8 + string: '{"username":"saltapi","password":"x3NTblSaFzs2+SjtkW8zkUXL65L5AWbTPScpuzYkcDjmBMgvzixpuPTxDERT7Y1JEjqkMo7KG0+r","eauth":"pam"}' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - application/json; charset=utf-8 + User-Agent: + - Ruby + Host: + - 127.0.0.1:8000 + Content-Type: + - application/json; charset=utf-8 + response: + status: + code: 200 + message: OK + headers: + Content-Length: + - '216' + Access-Control-Expose-Headers: + - GET, POST + Vary: + - Accept-Encoding + Server: + - CherryPy/3.6.0 + Allow: + - GET, HEAD, POST + Access-Control-Allow-Credentials: + - 'true' + Date: + - Sat, 27 Jan 2018 00:12:03 GMT + Access-Control-Allow-Origin: + - "*" + X-Auth-Token: + - cd96f194d3bb5a73ea6f5acd427abe4ff677cb27 + Content-Type: + - application/json + Set-Cookie: + - session_id=cd96f194d3bb5a73ea6f5acd427abe4ff677cb27; expires=Sat, 27 Jan 2018 + 10:12:03 GMT; Path=/ + body: + encoding: UTF-8 + string: '{"return": [{"perms": [".*", "@wheel", "@runner", "@jobs", "@events"], + "start": 1517011923.448009, "token": "cd96f194d3bb5a73ea6f5acd427abe4ff677cb27", + "expire": 1517055123.44801, "user": "saltapi", "eauth": "pam"}]}' + http_version: + recorded_at: Sat, 27 Jan 2018 00:12:03 GMT +- request: + method: post + uri: https://127.0.0.1:8000/ + body: + encoding: UTF-8 + string: '{"tgt":"*","fun":"saltutil.refresh_pillar","expr_form":"glob","client":"local"}' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - application/json; charset=utf-8 + User-Agent: + - Ruby + Host: + - 127.0.0.1:8000 + Content-Type: + - application/json; charset=utf-8 + X-Auth-Token: + - cd96f194d3bb5a73ea6f5acd427abe4ff677cb27 + response: + status: + code: 200 + message: OK + headers: + Content-Length: + - '41' + Access-Control-Expose-Headers: + - GET, POST + Cache-Control: + - private + Vary: + - Accept-Encoding + Server: + - CherryPy/3.6.0 + Allow: + - GET, HEAD, POST + Access-Control-Allow-Credentials: + - 'true' + Date: + - Sat, 27 Jan 2018 00:12:03 GMT + Access-Control-Allow-Origin: + - "*" + Content-Type: + - application/json + Set-Cookie: + - session_id=cd96f194d3bb5a73ea6f5acd427abe4ff677cb27; expires=Sat, 27 Jan 2018 + 10:12:03 GMT; Path=/ + body: + encoding: UTF-8 + string: '{"return": [{"admin": true, "ca": true}]}' + http_version: + recorded_at: Sat, 27 Jan 2018 00:12:04 GMT +recorded_with: VCR 3.0.3 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/vendor/assets/javascript/bootstrap-slider.min.js new/velum-master/vendor/assets/javascript/bootstrap-slider.min.js --- old/velum-master/vendor/assets/javascript/bootstrap-slider.min.js 1970-01-01 01:00:00.000000000 +0100 +++ new/velum-master/vendor/assets/javascript/bootstrap-slider.min.js 2018-02-22 10:57:04.000000000 +0100 @@ -0,0 +1,5 @@ +/*! ======================================================= + VERSION 9.9.0 +========================================================= */ +"use strict";var _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(a){return typeof a}:function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a},windowIsDefined="object"===("undefined"==typeof window?"undefined":_typeof(window));!function(a){if("function"==typeof define&&define.amd)define(["jquery"],a);else if("object"===("undefined"==typeof module?"undefined":_typeof(module))&&module.exports){var b;try{b=require("jquery")}catch(c){b=null}module.exports=a(b)}else window&&(window.Slider=a(window.jQuery))}(function(a){var b="slider",c="bootstrapSlider";windowIsDefined&&!window.console&&(window.console={}),windowIsDefined&&!window.console.log&&(window.console.log=function(){}),windowIsDefined&&!window.console.warn&&(window.console.warn=function(){});var d;return function(a){function b(){}function c(a){function c(b){b.prototype.option||(b.prototype.option=function(b){a.isPlainObject(b)&&(this.options=a.extend(!0,this.options,b))})}function e(b,c){a.fn[b]=function(e){if("string"==typeof e){for(var g=d.call(arguments,1),h=0,i=this.length;i>h;h++){var j=this[h],k=a.data(j,b);if(k)if(a.isFunction(k[e])&&"_"!==e.charAt(0)){var l=k[e].apply(k,g);if(void 0!==l&&l!==k)return l}else f("no such method '"+e+"' for "+b+" instance");else f("cannot call methods on "+b+" prior to initialization; attempted to call '"+e+"'")}return this}var m=this.map(function(){var d=a.data(this,b);return d?(d.option(e),d._init()):(d=new c(this,e),a.data(this,b,d)),a(this)});return!m||m.length>1?m:m[0]}}if(a){var f="undefined"==typeof console?b:function(a){console.error(a)};return a.bridget=function(a,b){c(b),e(a,b)},a.bridget}}var d=Array.prototype.slice;c(a)}(a),function(a){function e(b,c){function d(a,b){var c="data-slider-"+b.replace(/_/g,"-"),d=a.getAttribute(c);try{return JSON.parse(d)}catch(e){return d}}this._state={value:null,enabled:null,offset:null,size:null,percentage:null,inDrag:!1,over:!1},this.ticksCallbackMap={},this.handleCallbackMap={},"string"==typeof b?this.element=document.querySelector(b):b instanceof HTMLElement&&(this.element=b),c=c?c:{};for(var e=Object.keys(this.defaultOptions),f=0;f<e.length;f++){var h=e[f],i=c[h];i="undefined"!=typeof i?i:d(this.element,h),i=null!==i?i:this.defaultOptions[h],this.options||(this.options={}),this.options[h]=i}"auto"===this.options.rtl&&(this.options.rtl="rtl"===window.getComputedStyle(this.element).direction),"vertical"!==this.options.orientation||"top"!==this.options.tooltip_position&&"bottom"!==this.options.tooltip_position?"horizontal"!==this.options.orientation||"left"!==this.options.tooltip_position&&"right"!==this.options.tooltip_position||(this.options.tooltip_position="top"):this.options.rtl?this.options.tooltip_position="left":this.options.tooltip_position="right";var j,k,l,m,n,o=this.element.style.width,p=!1,q=this.element.parentNode;if(this.sliderElem)p=!0;else{this.sliderElem=document.createElement("div"),this.sliderElem.className="slider";var r=document.createElement("div");r.className="slider-track",k=document.createElement("div"),k.className="slider-track-low",j=document.createElement("div"),j.className="slider-selection",l=document.createElement("div"),l.className="slider-track-high",m=document.createElement("div"),m.className="slider-handle min-slider-handle",m.setAttribute("role","slider"),m.setAttribute("aria-valuemin",this.options.min),m.setAttribute("aria-valuemax",this.options.max),n=document.createElement("div"),n.className="slider-handle max-slider-handle",n.setAttribute("role","slider"),n.setAttribute("aria-valuemin",this.options.min),n.setAttribute("aria-valuemax",this.options.max),r.appendChild(k),r.appendChild(j),r.appendChild(l),this.rangeHighlightElements=[];var s=this.options.rangeHighlights;if(Array.isArray(s)&&s.length>0)for(var t=0;t<s.length;t++){var u=document.createElement("div"),v=s[t]["class"]||"";u.className="slider-rangeHighlight slider-selection "+v,this.rangeHighlightElements.push(u),r.appendChild(u)}var w=Array.isArray(this.options.labelledby);if(w&&this.options.labelledby[0]&&m.setAttribute("aria-labelledby",this.options.labelledby[0]),w&&this.options.labelledby[1]&&n.setAttribute("aria-labelledby",this.options.labelledby[1]),!w&&this.options.labelledby&&(m.setAttribute("aria-labelledby",this.options.labelledby),n.setAttribute("aria-labelledby",this.options.labelledby)),this.ticks=[],Array.isArray(this.options.ticks)&&this.options.ticks.length>0){for(this.ticksContainer=document.createElement("div"),this.ticksContainer.className="slider-tick-container",f=0;f<this.options.ticks.length;f++){var x=document.createElement("div");if(x.className="slider-tick",this.options.ticks_tooltip){var y=this._addTickListener(),z=y.addMouseEnter(this,x,f),A=y.addMouseLeave(this,x);this.ticksCallbackMap[f]={mouseEnter:z,mouseLeave:A}}this.ticks.push(x),this.ticksContainer.appendChild(x)}j.className+=" tick-slider-selection"}if(this.tickLabels=[],Array.isArray(this.options.ticks_labels)&&this.options.ticks_labels.length>0)for(this.tickLabelContainer=document.createElement("div"),this.tickLabelContainer.className="slider-tick-label-container",f=0;f<this.options.ticks_labels.length;f++){var B=document.createElement("div"),C=0===this.options.ticks_positions.length,D=this.options.reversed&&C?this.options.ticks_labels.length-(f+1):f;B.className="slider-tick-label",B.innerHTML=this.options.ticks_labels[D],this.tickLabels.push(B),this.tickLabelContainer.appendChild(B)}var E=function(a){var b=document.createElement("div");b.className="tooltip-arrow";var c=document.createElement("div");c.className="tooltip-inner",a.appendChild(b),a.appendChild(c)},F=document.createElement("div");F.className="tooltip tooltip-main",F.setAttribute("role","presentation"),E(F);var G=document.createElement("div");G.className="tooltip tooltip-min",G.setAttribute("role","presentation"),E(G);var H=document.createElement("div");H.className="tooltip tooltip-max",H.setAttribute("role","presentation"),E(H),this.sliderElem.appendChild(r),this.sliderElem.appendChild(F),this.sliderElem.appendChild(G),this.sliderElem.appendChild(H),this.tickLabelContainer&&this.sliderElem.appendChild(this.tickLabelContainer),this.ticksContainer&&this.sliderElem.appendChild(this.ticksContainer),this.sliderElem.appendChild(m),this.sliderElem.appendChild(n),q.insertBefore(this.sliderElem,this.element),this.element.style.display="none"}if(a&&(this.$element=a(this.element),this.$sliderElem=a(this.sliderElem)),this.eventToCallbackMap={},this.sliderElem.id=this.options.id,this.touchCapable="ontouchstart"in window||window.DocumentTouch&&document instanceof window.DocumentTouch,this.touchX=0,this.touchY=0,this.tooltip=this.sliderElem.querySelector(".tooltip-main"),this.tooltipInner=this.tooltip.querySelector(".tooltip-inner"),this.tooltip_min=this.sliderElem.querySelector(".tooltip-min"),this.tooltipInner_min=this.tooltip_min.querySelector(".tooltip-inner"),this.tooltip_max=this.sliderElem.querySelector(".tooltip-max"),this.tooltipInner_max=this.tooltip_max.querySelector(".tooltip-inner"),g[this.options.scale]&&(this.options.scale=g[this.options.scale]),p===!0&&(this._removeClass(this.sliderElem,"slider-horizontal"),this._removeClass(this.sliderElem,"slider-vertical"),this._removeClass(this.sliderElem,"slider-rtl"),this._removeClass(this.tooltip,"hide"),this._removeClass(this.tooltip_min,"hide"),this._removeClass(this.tooltip_max,"hide"),["left","right","top","width","height"].forEach(function(a){this._removeProperty(this.trackLow,a),this._removeProperty(this.trackSelection,a),this._removeProperty(this.trackHigh,a)},this),[this.handle1,this.handle2].forEach(function(a){this._removeProperty(a,"left"),this._removeProperty(a,"right"),this._removeProperty(a,"top")},this),[this.tooltip,this.tooltip_min,this.tooltip_max].forEach(function(a){this._removeProperty(a,"left"),this._removeProperty(a,"right"),this._removeProperty(a,"top"),this._removeProperty(a,"margin-left"),this._removeProperty(a,"margin-right"),this._removeProperty(a,"margin-top"),this._removeClass(a,"right"),this._removeClass(a,"left"),this._removeClass(a,"top")},this)),"vertical"===this.options.orientation?(this._addClass(this.sliderElem,"slider-vertical"),this.stylePos="top",this.mousePos="pageY",this.sizePos="offsetHeight"):(this._addClass(this.sliderElem,"slider-horizontal"),this.sliderElem.style.width=o,this.options.orientation="horizontal",this.options.rtl?this.stylePos="right":this.stylePos="left",this.mousePos="pageX",this.sizePos="offsetWidth"),this.options.rtl&&this._addClass(this.sliderElem,"slider-rtl"),this._setTooltipPosition(),Array.isArray(this.options.ticks)&&this.options.ticks.length>0&&(this.options.max=Math.max.apply(Math,this.options.ticks),this.options.min=Math.min.apply(Math,this.options.ticks)),Array.isArray(this.options.value)?(this.options.range=!0,this._state.value=this.options.value):this.options.range?this._state.value=[this.options.value,this.options.max]:this._state.value=this.options.value,this.trackLow=k||this.trackLow,this.trackSelection=j||this.trackSelection,this.trackHigh=l||this.trackHigh,"none"===this.options.selection?(this._addClass(this.trackLow,"hide"),this._addClass(this.trackSelection,"hide"),this._addClass(this.trackHigh,"hide")):("after"===this.options.selection||"before"===this.options.selection)&&(this._removeClass(this.trackLow,"hide"),this._removeClass(this.trackSelection,"hide"),this._removeClass(this.trackHigh,"hide")),this.handle1=m||this.handle1,this.handle2=n||this.handle2,p===!0)for(this._removeClass(this.handle1,"round triangle"),this._removeClass(this.handle2,"round triangle hide"),f=0;f<this.ticks.length;f++)this._removeClass(this.ticks[f],"round triangle hide");var I=["round","triangle","custom"],J=-1!==I.indexOf(this.options.handle);if(J)for(this._addClass(this.handle1,this.options.handle),this._addClass(this.handle2,this.options.handle),f=0;f<this.ticks.length;f++)this._addClass(this.ticks[f],this.options.handle);if(this._state.offset=this._offset(this.sliderElem),this._state.size=this.sliderElem[this.sizePos],this.setValue(this._state.value),this.handle1Keydown=this._keydown.bind(this,0),this.handle1.addEventListener("keydown",this.handle1Keydown,!1),this.handle2Keydown=this._keydown.bind(this,1),this.handle2.addEventListener("keydown",this.handle2Keydown,!1),this.mousedown=this._mousedown.bind(this),this.touchstart=this._touchstart.bind(this),this.touchmove=this._touchmove.bind(this),this.touchCapable){var K=!1;try{var L=Object.defineProperty({},"passive",{get:function(){K=!0}});window.addEventListener("test",null,L)}catch(M){}var N=K?{passive:!0}:!1;this.sliderElem.addEventListener("touchstart",this.touchstart,N),this.sliderElem.addEventListener("touchmove",this.touchmove,N)}if(this.sliderElem.addEventListener("mousedown",this.mousedown,!1),this.resize=this._resize.bind(this),window.addEventListener("resize",this.resize,!1),"hide"===this.options.tooltip)this._addClass(this.tooltip,"hide"),this._addClass(this.tooltip_min,"hide"),this._addClass(this.tooltip_max,"hide");else if("always"===this.options.tooltip)this._showTooltip(),this._alwaysShowTooltip=!0;else{if(this.showTooltip=this._showTooltip.bind(this),this.hideTooltip=this._hideTooltip.bind(this),this.options.ticks_tooltip){var O=this._addTickListener(),P=O.addMouseEnter(this,this.handle1),Q=O.addMouseLeave(this,this.handle1);this.handleCallbackMap.handle1={mouseEnter:P,mouseLeave:Q},P=O.addMouseEnter(this,this.handle2),Q=O.addMouseLeave(this,this.handle2),this.handleCallbackMap.handle2={mouseEnter:P,mouseLeave:Q}}else this.sliderElem.addEventListener("mouseenter",this.showTooltip,!1),this.sliderElem.addEventListener("mouseleave",this.hideTooltip,!1);this.handle1.addEventListener("focus",this.showTooltip,!1),this.handle1.addEventListener("blur",this.hideTooltip,!1),this.handle2.addEventListener("focus",this.showTooltip,!1),this.handle2.addEventListener("blur",this.hideTooltip,!1)}this.options.enabled?this.enable():this.disable()}var f={formatInvalidInputErrorMsg:function(a){return"Invalid input value '"+a+"' passed in"},callingContextNotSliderInstance:"Calling context element does not have instance of Slider bound to it. Check your code to make sure the JQuery object returned from the call to the slider() initializer is calling the method"},g={linear:{toValue:function(a){var b=a/100*(this.options.max-this.options.min),c=!0;if(this.options.ticks_positions.length>0){for(var d,e,f,g=0,h=1;h<this.options.ticks_positions.length;h++)if(a<=this.options.ticks_positions[h]){d=this.options.ticks[h-1],f=this.options.ticks_positions[h-1],e=this.options.ticks[h],g=this.options.ticks_positions[h];break}var i=(a-f)/(g-f);b=d+i*(e-d),c=!1}var j=c?this.options.min:0,k=j+Math.round(b/this.options.step)*this.options.step;return k<this.options.min?this.options.min:k>this.options.max?this.options.max:k},toPercentage:function(a){if(this.options.max===this.options.min)return 0;if(this.options.ticks_positions.length>0){for(var b,c,d,e=0,f=0;f<this.options.ticks.length;f++)if(a<=this.options.ticks[f]){b=f>0?this.options.ticks[f-1]:0,d=f>0?this.options.ticks_positions[f-1]:0,c=this.options.ticks[f],e=this.options.ticks_positions[f];break}if(f>0){var g=(a-b)/(c-b);return d+g*(e-d)}}return 100*(a-this.options.min)/(this.options.max-this.options.min)}},logarithmic:{toValue:function(a){var b=0===this.options.min?0:Math.log(this.options.min),c=Math.log(this.options.max),d=Math.exp(b+(c-b)*a/100);return Math.round(d)===this.options.max?this.options.max:(d=this.options.min+Math.round((d-this.options.min)/this.options.step)*this.options.step,d<this.options.min?this.options.min:d>this.options.max?this.options.max:d)},toPercentage:function(a){if(this.options.max===this.options.min)return 0;var b=Math.log(this.options.max),c=0===this.options.min?0:Math.log(this.options.min),d=0===a?0:Math.log(a);return 100*(d-c)/(b-c)}}};if(d=function(a,b){return e.call(this,a,b),this},d.prototype={_init:function(){},constructor:d,defaultOptions:{id:"",min:0,max:10,step:1,precision:0,orientation:"horizontal",value:5,range:!1,selection:"before",tooltip:"show",tooltip_split:!1,handle:"round",reversed:!1,rtl:"auto",enabled:!0,formatter:function(a){return Array.isArray(a)?a[0]+" : "+a[1]:a},natural_arrow_keys:!1,ticks:[],ticks_positions:[],ticks_labels:[],ticks_snap_bounds:0,ticks_tooltip:!1,scale:"linear",focus:!1,tooltip_position:null,labelledby:null,rangeHighlights:[]},getElement:function(){return this.sliderElem},getValue:function(){return this.options.range?this._state.value:this._state.value[0]},setValue:function(a,b,c){a||(a=0);var d=this.getValue();this._state.value=this._validateInputValue(a);var e=this._applyPrecision.bind(this);this.options.range?(this._state.value[0]=e(this._state.value[0]),this._state.value[1]=e(this._state.value[1]),this._state.value[0]=Math.max(this.options.min,Math.min(this.options.max,this._state.value[0])),this._state.value[1]=Math.max(this.options.min,Math.min(this.options.max,this._state.value[1]))):(this._state.value=e(this._state.value),this._state.value=[Math.max(this.options.min,Math.min(this.options.max,this._state.value))],this._addClass(this.handle2,"hide"),"after"===this.options.selection?this._state.value[1]=this.options.max:this._state.value[1]=this.options.min),this.options.max>this.options.min?this._state.percentage=[this._toPercentage(this._state.value[0]),this._toPercentage(this._state.value[1]),100*this.options.step/(this.options.max-this.options.min)]:this._state.percentage=[0,0,100],this._layout();var f=this.options.range?this._state.value:this._state.value[0];return this._setDataVal(f),b===!0&&this._trigger("slide",f),d!==f&&c===!0&&this._trigger("change",{oldValue:d,newValue:f}),this},destroy:function(){this._removeSliderEventHandlers(),this.sliderElem.parentNode.removeChild(this.sliderElem),this.element.style.display="",this._cleanUpEventCallbacksMap(),this.element.removeAttribute("data"),a&&(this._unbindJQueryEventHandlers(),this.$element.removeData("slider"))},disable:function(){return this._state.enabled=!1,this.handle1.removeAttribute("tabindex"),this.handle2.removeAttribute("tabindex"),this._addClass(this.sliderElem,"slider-disabled"),this._trigger("slideDisabled"),this},enable:function(){return this._state.enabled=!0,this.handle1.setAttribute("tabindex",0),this.handle2.setAttribute("tabindex",0),this._removeClass(this.sliderElem,"slider-disabled"),this._trigger("slideEnabled"),this},toggle:function(){return this._state.enabled?this.disable():this.enable(),this},isEnabled:function(){return this._state.enabled},on:function(a,b){return this._bindNonQueryEventHandler(a,b),this},off:function(b,c){a?(this.$element.off(b,c),this.$sliderElem.off(b,c)):this._unbindNonQueryEventHandler(b,c)},getAttribute:function(a){return a?this.options[a]:this.options},setAttribute:function(a,b){return this.options[a]=b,this},refresh:function(){return this._removeSliderEventHandlers(),e.call(this,this.element,this.options),a&&a.data(this.element,"slider",this),this},relayout:function(){return this._resize(),this._layout(),this},_removeSliderEventHandlers:function(){if(this.handle1.removeEventListener("keydown",this.handle1Keydown,!1),this.handle2.removeEventListener("keydown",this.handle2Keydown,!1),this.options.ticks_tooltip){for(var a=this.ticksContainer.getElementsByClassName("slider-tick"),b=0;b<a.length;b++)a[b].removeEventListener("mouseenter",this.ticksCallbackMap[b].mouseEnter,!1),a[b].removeEventListener("mouseleave",this.ticksCallbackMap[b].mouseLeave,!1);this.handle1.removeEventListener("mouseenter",this.handleCallbackMap.handle1.mouseEnter,!1),this.handle2.removeEventListener("mouseenter",this.handleCallbackMap.handle2.mouseEnter,!1),this.handle1.removeEventListener("mouseleave",this.handleCallbackMap.handle1.mouseLeave,!1),this.handle2.removeEventListener("mouseleave",this.handleCallbackMap.handle2.mouseLeave,!1)}this.handleCallbackMap=null,this.ticksCallbackMap=null,this.showTooltip&&(this.handle1.removeEventListener("focus",this.showTooltip,!1),this.handle2.removeEventListener("focus",this.showTooltip,!1)),this.hideTooltip&&(this.handle1.removeEventListener("blur",this.hideTooltip,!1),this.handle2.removeEventListener("blur",this.hideTooltip,!1)),this.showTooltip&&this.sliderElem.removeEventListener("mouseenter",this.showTooltip,!1),this.hideTooltip&&this.sliderElem.removeEventListener("mouseleave",this.hideTooltip,!1),this.sliderElem.removeEventListener("touchstart",this.touchstart,!1),this.sliderElem.removeEventListener("touchmove",this.touchmove,!1),this.sliderElem.removeEventListener("mousedown",this.mousedown,!1),window.removeEventListener("resize",this.resize,!1)},_bindNonQueryEventHandler:function(a,b){void 0===this.eventToCallbackMap[a]&&(this.eventToCallbackMap[a]=[]),this.eventToCallbackMap[a].push(b)},_unbindNonQueryEventHandler:function(a,b){var c=this.eventToCallbackMap[a];if(void 0!==c)for(var d=0;d<c.length;d++)if(c[d]===b){c.splice(d,1);break}},_cleanUpEventCallbacksMap:function(){for(var a=Object.keys(this.eventToCallbackMap),b=0;b<a.length;b++){var c=a[b];delete this.eventToCallbackMap[c]}},_showTooltip:function(){this.options.tooltip_split===!1?(this._addClass(this.tooltip,"in"),this.tooltip_min.style.display="none",this.tooltip_max.style.display="none"):(this._addClass(this.tooltip_min,"in"),this._addClass(this.tooltip_max,"in"),this.tooltip.style.display="none"),this._state.over=!0},_hideTooltip:function(){this._state.inDrag===!1&&this.alwaysShowTooltip!==!0&&(this._removeClass(this.tooltip,"in"),this._removeClass(this.tooltip_min,"in"),this._removeClass(this.tooltip_max,"in")),this._state.over=!1},_setToolTipOnMouseOver:function(a){function b(a,b){return b?[100-a.percentage[0],this.options.range?100-a.percentage[1]:a.percentage[1]]:[a.percentage[0],a.percentage[1]]}var c=this.options.formatter(a?a.value[0]:this._state.value[0]),d=a?b(a,this.options.reversed):b(this._state,this.options.reversed);this._setText(this.tooltipInner,c),this.tooltip.style[this.stylePos]=d[0]+"%","vertical"===this.options.orientation?this._css(this.tooltip,"margin-"+this.stylePos,-this.tooltip.offsetHeight/2+"px"):this._css(this.tooltip,"margin-"+this.stylePos,-this.tooltip.offsetWidth/2+"px")},_addTickListener:function(){return{addMouseEnter:function(a,b,c){var d=function(){var b=a._state,d=c>=0?c:this.attributes["aria-valuenow"].value,e=parseInt(d,10);b.value[0]=e,b.percentage[0]=a.options.ticks_positions[e],a._setToolTipOnMouseOver(b),a._showTooltip()};return b.addEventListener("mouseenter",d,!1),d},addMouseLeave:function(a,b){var c=function(){a._hideTooltip()};return b.addEventListener("mouseleave",c,!1),c}}},_layout:function(){var a;if(a=this.options.reversed?[100-this._state.percentage[0],this.options.range?100-this._state.percentage[1]:this._state.percentage[1]]:[this._state.percentage[0],this._state.percentage[1]],this.handle1.style[this.stylePos]=a[0]+"%",this.handle1.setAttribute("aria-valuenow",this._state.value[0]),isNaN(this.options.formatter(this._state.value[0]))&&this.handle1.setAttribute("aria-valuetext",this.options.formatter(this._state.value[0])),this.handle2.style[this.stylePos]=a[1]+"%",this.handle2.setAttribute("aria-valuenow",this._state.value[1]),isNaN(this.options.formatter(this._state.value[1]))&&this.handle2.setAttribute("aria-valuetext",this.options.formatter(this._state.value[1])),this.rangeHighlightElements.length>0&&Array.isArray(this.options.rangeHighlights)&&this.options.rangeHighlights.length>0)for(var b=0;b<this.options.rangeHighlights.length;b++){var c=this._toPercentage(this.options.rangeHighlights[b].start),d=this._toPercentage(this.options.rangeHighlights[b].end);if(this.options.reversed){var e=100-d;d=100-c,c=e}var f=this._createHighlightRange(c,d);f?"vertical"===this.options.orientation?(this.rangeHighlightElements[b].style.top=f.start+"%",this.rangeHighlightElements[b].style.height=f.size+"%"):(this.options.rtl?this.rangeHighlightElements[b].style.right=f.start+"%":this.rangeHighlightElements[b].style.left=f.start+"%",this.rangeHighlightElements[b].style.width=f.size+"%"):this.rangeHighlightElements[b].style.display="none"}if(Array.isArray(this.options.ticks)&&this.options.ticks.length>0){var g,h="vertical"===this.options.orientation?"height":"width";g="vertical"===this.options.orientation?"marginTop":this.options.rtl?"marginRight":"marginLeft";var i=this._state.size/(this.options.ticks.length-1);if(this.tickLabelContainer){var j=0;if(0===this.options.ticks_positions.length)"vertical"!==this.options.orientation&&(this.tickLabelContainer.style[g]=-i/2+"px"),j=this.tickLabelContainer.offsetHeight;else for(k=0;k<this.tickLabelContainer.childNodes.length;k++)this.tickLabelContainer.childNodes[k].offsetHeight>j&&(j=this.tickLabelContainer.childNodes[k].offsetHeight);"horizontal"===this.options.orientation&&(this.sliderElem.style.marginBottom=j+"px")}for(var k=0;k<this.options.ticks.length;k++){var l=this.options.ticks_positions[k]||this._toPercentage(this.options.ticks[k]);this.options.reversed&&(l=100-l),this.ticks[k].style[this.stylePos]=l+"%",this._removeClass(this.ticks[k],"in-selection"),this.options.range?l>=a[0]&&l<=a[1]&&this._addClass(this.ticks[k],"in-selection"):"after"===this.options.selection&&l>=a[0]?this._addClass(this.ticks[k],"in-selection"):"before"===this.options.selection&&l<=a[0]&&this._addClass(this.ticks[k],"in-selection"),this.tickLabels[k]&&(this.tickLabels[k].style[h]=i+"px","vertical"!==this.options.orientation&&void 0!==this.options.ticks_positions[k]?(this.tickLabels[k].style.position="absolute",this.tickLabels[k].style[this.stylePos]=l+"%",this.tickLabels[k].style[g]=-i/2+"px"):"vertical"===this.options.orientation&&(this.options.rtl?this.tickLabels[k].style.marginRight=this.sliderElem.offsetWidth+"px":this.tickLabels[k].style.marginLeft=this.sliderElem.offsetWidth+"px",this.tickLabelContainer.style[g]=this.sliderElem.offsetWidth/2*-1+"px"))}}var m;if(this.options.range){m=this.options.formatter(this._state.value),this._setText(this.tooltipInner,m),this.tooltip.style[this.stylePos]=(a[1]+a[0])/2+"%","vertical"===this.options.orientation?this._css(this.tooltip,"margin-"+this.stylePos,-this.tooltip.offsetHeight/2+"px"):this._css(this.tooltip,"margin-"+this.stylePos,-this.tooltip.offsetWidth/2+"px");var n=this.options.formatter(this._state.value[0]);this._setText(this.tooltipInner_min,n);var o=this.options.formatter(this._state.value[1]);this._setText(this.tooltipInner_max,o),this.tooltip_min.style[this.stylePos]=a[0]+"%","vertical"===this.options.orientation?this._css(this.tooltip_min,"margin-"+this.stylePos,-this.tooltip_min.offsetHeight/2+"px"):this._css(this.tooltip_min,"margin-"+this.stylePos,-this.tooltip_min.offsetWidth/2+"px"),this.tooltip_max.style[this.stylePos]=a[1]+"%","vertical"===this.options.orientation?this._css(this.tooltip_max,"margin-"+this.stylePos,-this.tooltip_max.offsetHeight/2+"px"):this._css(this.tooltip_max,"margin-"+this.stylePos,-this.tooltip_max.offsetWidth/2+"px")}else m=this.options.formatter(this._state.value[0]),this._setText(this.tooltipInner,m),this.tooltip.style[this.stylePos]=a[0]+"%","vertical"===this.options.orientation?this._css(this.tooltip,"margin-"+this.stylePos,-this.tooltip.offsetHeight/2+"px"):this._css(this.tooltip,"margin-"+this.stylePos,-this.tooltip.offsetWidth/2+"px");if("vertical"===this.options.orientation)this.trackLow.style.top="0",this.trackLow.style.height=Math.min(a[0],a[1])+"%",this.trackSelection.style.top=Math.min(a[0],a[1])+"%",this.trackSelection.style.height=Math.abs(a[0]-a[1])+"%",this.trackHigh.style.bottom="0",this.trackHigh.style.height=100-Math.min(a[0],a[1])-Math.abs(a[0]-a[1])+"%";else{"right"===this.stylePos?this.trackLow.style.right="0":this.trackLow.style.left="0",this.trackLow.style.width=Math.min(a[0],a[1])+"%","right"===this.stylePos?this.trackSelection.style.right=Math.min(a[0],a[1])+"%":this.trackSelection.style.left=Math.min(a[0],a[1])+"%",this.trackSelection.style.width=Math.abs(a[0]-a[1])+"%","right"===this.stylePos?this.trackHigh.style.left="0":this.trackHigh.style.right="0",this.trackHigh.style.width=100-Math.min(a[0],a[1])-Math.abs(a[0]-a[1])+"%";var p=this.tooltip_min.getBoundingClientRect(),q=this.tooltip_max.getBoundingClientRect();"bottom"===this.options.tooltip_position?p.right>q.left?(this._removeClass(this.tooltip_max,"bottom"),this._addClass(this.tooltip_max,"top"),this.tooltip_max.style.top="",this.tooltip_max.style.bottom="22px"):(this._removeClass(this.tooltip_max,"top"),this._addClass(this.tooltip_max,"bottom"),this.tooltip_max.style.top=this.tooltip_min.style.top,this.tooltip_max.style.bottom=""):p.right>q.left?(this._removeClass(this.tooltip_max,"top"),this._addClass(this.tooltip_max,"bottom"),this.tooltip_max.style.top="18px"):(this._removeClass(this.tooltip_max,"bottom"),this._addClass(this.tooltip_max,"top"),this.tooltip_max.style.top=this.tooltip_min.style.top)}},_createHighlightRange:function(a,b){return this._isHighlightRange(a,b)?a>b?{start:b,size:a-b}:{start:a,size:b-a}:null},_isHighlightRange:function(a,b){return a>=0&&100>=a&&b>=0&&100>=b?!0:!1},_resize:function(a){this._state.offset=this._offset(this.sliderElem),this._state.size=this.sliderElem[this.sizePos],this._layout()},_removeProperty:function(a,b){a.style.removeProperty?a.style.removeProperty(b):a.style.removeAttribute(b)},_mousedown:function(a){if(!this._state.enabled)return!1;this._state.offset=this._offset(this.sliderElem),this._state.size=this.sliderElem[this.sizePos];var b=this._getPercentage(a);if(this.options.range){var c=Math.abs(this._state.percentage[0]-b),d=Math.abs(this._state.percentage[1]-b);this._state.dragged=d>c?0:1,this._adjustPercentageForRangeSliders(b)}else this._state.dragged=0;this._state.percentage[this._state.dragged]=b,this._layout(),this.touchCapable&&(document.removeEventListener("touchmove",this.mousemove,!1),document.removeEventListener("touchend",this.mouseup,!1)),this.mousemove&&document.removeEventListener("mousemove",this.mousemove,!1),this.mouseup&&document.removeEventListener("mouseup",this.mouseup,!1),this.mousemove=this._mousemove.bind(this),this.mouseup=this._mouseup.bind(this),this.touchCapable&&(document.addEventListener("touchmove",this.mousemove,!1),document.addEventListener("touchend",this.mouseup,!1)),document.addEventListener("mousemove",this.mousemove,!1),document.addEventListener("mouseup",this.mouseup,!1),this._state.inDrag=!0;var e=this._calculateValue();return this._trigger("slideStart",e),this._setDataVal(e),this.setValue(e,!1,!0),a.returnValue=!1,this.options.focus&&this._triggerFocusOnHandle(this._state.dragged),!0},_touchstart:function(a){if(void 0===a.changedTouches)return void this._mousedown(a);var b=a.changedTouches[0];this.touchX=b.pageX,this.touchY=b.pageY},_triggerFocusOnHandle:function(a){0===a&&this.handle1.focus(),1===a&&this.handle2.focus()},_keydown:function(a,b){if(!this._state.enabled)return!1;var c;switch(b.keyCode){case 37:case 40:c=-1;break;case 39:case 38:c=1}if(c){if(this.options.natural_arrow_keys){var d="vertical"===this.options.orientation&&!this.options.reversed,e="horizontal"===this.options.orientation&&this.options.reversed;(d||e)&&(c=-c)}var f=this._state.value[a]+c*this.options.step,g=f/this.options.max*100;if(this._state.keyCtrl=a,this.options.range){this._adjustPercentageForRangeSliders(g);var h=this._state.keyCtrl?this._state.value[0]:f,i=this._state.keyCtrl?f:this._state.value[1];f=[h,i]}return this._trigger("slideStart",f),this._setDataVal(f),this.setValue(f,!0,!0),this._setDataVal(f),this._trigger("slideStop",f),this._layout(),this._pauseEvent(b),delete this._state.keyCtrl,!1}},_pauseEvent:function(a){a.stopPropagation&&a.stopPropagation(),a.preventDefault&&a.preventDefault(),a.cancelBubble=!0,a.returnValue=!1},_mousemove:function(a){if(!this._state.enabled)return!1;var b=this._getPercentage(a);this._adjustPercentageForRangeSliders(b),this._state.percentage[this._state.dragged]=b,this._layout();var c=this._calculateValue(!0);return this.setValue(c,!0,!0),!1},_touchmove:function(a){if(void 0!==a.changedTouches){var b=a.changedTouches[0],c=b.pageX-this.touchX,d=b.pageY-this.touchY;this._state.inDrag||("vertical"===this.options.orientation&&5>=c&&c>=-5&&(d>=15||-15>=d)?this._mousedown(a):5>=d&&d>=-5&&(c>=15||-15>=c)&&this._mousedown(a))}},_adjustPercentageForRangeSliders:function(a){if(this.options.range){var b=this._getNumDigitsAfterDecimalPlace(a);b=b?b-1:0;var c=this._applyToFixedAndParseFloat(a,b);0===this._state.dragged&&this._applyToFixedAndParseFloat(this._state.percentage[1],b)<c?(this._state.percentage[0]=this._state.percentage[1],this._state.dragged=1):1===this._state.dragged&&this._applyToFixedAndParseFloat(this._state.percentage[0],b)>c?(this._state.percentage[1]=this._state.percentage[0],this._state.dragged=0):0===this._state.keyCtrl&&this._state.value[1]/this.options.max*100<a?(this._state.percentage[0]=this._state.percentage[1],this._state.keyCtrl=1,this.handle2.focus()):1===this._state.keyCtrl&&this._state.value[0]/this.options.max*100>a&&(this._state.percentage[1]=this._state.percentage[0],this._state.keyCtrl=0,this.handle1.focus())}},_mouseup:function(){if(!this._state.enabled)return!1;this.touchCapable&&(document.removeEventListener("touchmove",this.mousemove,!1),document.removeEventListener("touchend",this.mouseup,!1)),document.removeEventListener("mousemove",this.mousemove,!1),document.removeEventListener("mouseup",this.mouseup,!1),this._state.inDrag=!1,this._state.over===!1&&this._hideTooltip();var a=this._calculateValue(!0);return this._layout(),this._setDataVal(a),this._trigger("slideStop",a),!1},_calculateValue:function(a){var b;if(this.options.range?(b=[this.options.min,this.options.max],0!==this._state.percentage[0]&&(b[0]=this._toValue(this._state.percentage[0]),b[0]=this._applyPrecision(b[0])),100!==this._state.percentage[1]&&(b[1]=this._toValue(this._state.percentage[1]),b[1]=this._applyPrecision(b[1]))):(b=this._toValue(this._state.percentage[0]),b=parseFloat(b),b=this._applyPrecision(b)),a){for(var c=[b,1/0],d=0;d<this.options.ticks.length;d++){ +var e=Math.abs(this.options.ticks[d]-b);e<=c[1]&&(c=[this.options.ticks[d],e])}if(c[1]<=this.options.ticks_snap_bounds)return c[0]}return b},_applyPrecision:function(a){var b=this.options.precision||this._getNumDigitsAfterDecimalPlace(this.options.step);return this._applyToFixedAndParseFloat(a,b)},_getNumDigitsAfterDecimalPlace:function(a){var b=(""+a).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/);return b?Math.max(0,(b[1]?b[1].length:0)-(b[2]?+b[2]:0)):0},_applyToFixedAndParseFloat:function(a,b){var c=a.toFixed(b);return parseFloat(c)},_getPercentage:function(a){!this.touchCapable||"touchstart"!==a.type&&"touchmove"!==a.type||(a=a.touches[0]);var b=a[this.mousePos],c=this._state.offset[this.stylePos],d=b-c;"right"===this.stylePos&&(d=-d);var e=d/this._state.size*100;return e=Math.round(e/this._state.percentage[2])*this._state.percentage[2],this.options.reversed&&(e=100-e),Math.max(0,Math.min(100,e))},_validateInputValue:function(a){if(isNaN(+a)){if(Array.isArray(a))return this._validateArray(a),a;throw new Error(f.formatInvalidInputErrorMsg(a))}return+a},_validateArray:function(a){for(var b=0;b<a.length;b++){var c=a[b];if("number"!=typeof c)throw new Error(f.formatInvalidInputErrorMsg(c))}},_setDataVal:function(a){this.element.setAttribute("data-value",a),this.element.setAttribute("value",a),this.element.value=a},_trigger:function(b,c){c=c||0===c?c:void 0;var d=this.eventToCallbackMap[b];if(d&&d.length)for(var e=0;e<d.length;e++){var f=d[e];f(c)}a&&this._triggerJQueryEvent(b,c)},_triggerJQueryEvent:function(a,b){var c={type:a,value:b};this.$element.trigger(c),this.$sliderElem.trigger(c)},_unbindJQueryEventHandlers:function(){this.$element.off(),this.$sliderElem.off()},_setText:function(a,b){"undefined"!=typeof a.textContent?a.textContent=b:"undefined"!=typeof a.innerText&&(a.innerText=b)},_removeClass:function(a,b){for(var c=b.split(" "),d=a.className,e=0;e<c.length;e++){var f=c[e],g=new RegExp("(?:\\s|^)"+f+"(?:\\s|$)");d=d.replace(g," ")}a.className=d.trim()},_addClass:function(a,b){for(var c=b.split(" "),d=a.className,e=0;e<c.length;e++){var f=c[e],g=new RegExp("(?:\\s|^)"+f+"(?:\\s|$)"),h=g.test(d);h||(d+=" "+f)}a.className=d.trim()},_offsetLeft:function(a){return a.getBoundingClientRect().left},_offsetRight:function(a){return a.getBoundingClientRect().right},_offsetTop:function(a){for(var b=a.offsetTop;(a=a.offsetParent)&&!isNaN(a.offsetTop);)b+=a.offsetTop,"BODY"!==a.tagName&&(b-=a.scrollTop);return b},_offset:function(a){return{left:this._offsetLeft(a),right:this._offsetRight(a),top:this._offsetTop(a)}},_css:function(b,c,d){if(a)a.style(b,c,d);else{var e=c.replace(/^-ms-/,"ms-").replace(/-([\da-z])/gi,function(a,b){return b.toUpperCase()});b.style[e]=d}},_toValue:function(a){return this.options.scale.toValue.apply(this,[a])},_toPercentage:function(a){return this.options.scale.toPercentage.apply(this,[a])},_setTooltipPosition:function(){var a=[this.tooltip,this.tooltip_min,this.tooltip_max];if("vertical"===this.options.orientation){var b;b=this.options.tooltip_position?this.options.tooltip_position:this.options.rtl?"left":"right";var c="left"===b?"right":"left";a.forEach(function(a){this._addClass(a,b),a.style[c]="100%"}.bind(this))}else"bottom"===this.options.tooltip_position?a.forEach(function(a){this._addClass(a,"bottom"),a.style.top="22px"}.bind(this)):a.forEach(function(a){this._addClass(a,"top"),a.style.top=-this.tooltip.outerHeight-14+"px"}.bind(this))}},a&&a.fn){var h=void 0;a.fn.slider?(windowIsDefined&&window.console.warn("bootstrap-slider.js - WARNING: $.fn.slider namespace is already bound. Use the $.fn.bootstrapSlider namespace instead."),h=c):(a.bridget(b,d),h=b),a.bridget(c,d),a(function(){a("input[data-provide=slider]")[h]()})}}(a),d}); \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/velum-master/vendor/assets/stylesheets/bootstrap-slider.min.css new/velum-master/vendor/assets/stylesheets/bootstrap-slider.min.css --- old/velum-master/vendor/assets/stylesheets/bootstrap-slider.min.css 1970-01-01 01:00:00.000000000 +0100 +++ new/velum-master/vendor/assets/stylesheets/bootstrap-slider.min.css 2018-02-22 10:57:04.000000000 +0100 @@ -0,0 +1,41 @@ +/*! ======================================================= + VERSION 9.9.0 +========================================================= */ +/*! ========================================================= + * bootstrap-slider.js + * + * Maintainers: + * Kyle Kemp + * - Twitter: @seiyria + * - Github: seiyria + * Rohit Kalkur + * - Twitter: @Rovolutionary + * - Github: rovolution + * + * ========================================================= + * + * bootstrap-slider is released under the MIT License + * Copyright (c) 2017 Kyle Kemp, Rohit Kalkur, and contributors + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * ========================================================= */.slider{display:inline-block;vertical-align:middle;position:relative}.slider.slider-horizontal{width:210px;height:20px}.slider.slider-horizontal .slider-track{height:10px;width:100%;margin-top:-5px;top:50%;left:0}.slider.slider-horizontal .slider-selection,.slider.slider-horizontal .slider-track-low,.slider.slider-horizontal .slider-track-high{height:100%;top:0;bottom:0}.slider.slider-horizontal .slider-tick,.slider.slider-horizontal .slider-handle{margin-left:-10px}.slider.slider-horizontal .slider-tick.triangle,.slider.slider-horizontal .slider-handle.triangle{position:relative;top:50%;transform:translateY(-50%);border-width:0 10px 10px 10px;width:0;height:0;border-bottom-color:#2e6da4;margin-top:0}.slider.slider-horizontal .slider-tick-container{white-space:nowrap;position:absolute;top:0;left:0;width:100%}.slider.slider-horizontal .slider-tick-label-container{white-space:nowrap;margin-top:20px}.slider.slider-horizontal .slider-tick-label-container .slider-tick-label{padding-top:4px;display:inline-block;text-align:center}.slider.slider-horizontal.slider-rtl .slider-track{left:initial;right:0}.slider.slider-horizontal.slider-rtl .slider-tick,.slider.slider-horizontal.slider-rtl .slider-handle{margin-left:initial;margin-right:-10px}.slider.slider-horizontal.slider-rtl .slider-tick-container{left:initial;right:0}.slider.slider-vertical{height:210px;width:20px}.slider.slider-vertical .slider-track{width:10px;height:100%;left:25%;top:0}.slider.slider-vertical .slider-selection{width:100%;left:0;top:0;bottom:0}.slider.slider-vertical .slider-track-low,.slider.slider-vertical .slider-track-high{width:100%;left:0;right:0}.slider.slider-vertical .slider-tick,.slider.slider-vertical .slider-handle{margin-top:-10px}.slider.slider-vertical .slider-tick.triangle,.slider.slider-vertical .slider-handle.triangle{border-width:10px 0 10px 10px;width:1px;height:1px;border-left-color:#2e6da4;border-right-color:#2e6da4;margin-left:0;margin-right:0}.slider.slider-vertical .slider-tick-label-container{white-space:nowrap}.slider.slider-vertical .slider-tick-label-container .slider-tick-label{padding-left:4px}.slider.slider-vertical.slider-rtl .slider-track{left:initial;right:25%}.slider.slider-vertical.slider-rtl .slider-selection{left:initial;right:0}.slider.slider-vertical.slider-rtl .slider-tick.triangle,.slider.slider-vertical.slider-rtl .slider-handle.triangle{border-width:10px 10px 10px 0}.slider.slider-vertical.slider-rtl .slider-tick-label-container .slider-tick-label{padding-left:initial;padding-right:4px}.slider.slider-disabled .slider-handle{background-image:-webkit-linear-gradient(top,#dfdfdf 0,#bebebe 100%);background-image:-o-linear-gradient(top,#dfdfdf 0,#bebebe 100%);background-image:linear-gradient(to bottom,#dfdfdf 0,#bebebe 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdfdfdf',endColorstr='#ffbebebe',GradientType=0)}.slider.slider-disabled .slider-track{background-image:-webkit-linear-gradient(top,#e5e5e5 0,#e9e9e9 100%);background-image:-o-linear-gradient(top,#e5e5e5 0,#e9e9e9 100%);background-image:linear-gradient(to bottom,#e5e5e5 0,#e9e9e9 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe5e5e5',endColorstr='#ffe9e9e9',GradientType=0);cursor:not-allowed}.slider input{display:none}.slider .tooltip.top{margin-top:-36px}.slider .tooltip-inner{white-space:nowrap;max-width:none}.slider .hide{display:none}.slider-track{position:absolute;cursor:pointer;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#f9f9f9 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#f9f9f9 100%);background-image:linear-gradient(to bottom,#f5f5f5 0,#f9f9f9 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#fff9f9f9',GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);border-radius:4px}.slider-selection{position:absolute;background-image:-webkit-linear-gradient(top,#f9f9f9 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#f9f9f9 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#f9f9f9 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff9f9f9',endColorstr='#fff5f5f5',GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;border-radius:4px}.slider-selection.tick-slider-selection{background-image:-webkit-linear-gradient(top,#8ac1ef 0,#82b3de 100%);background-image:-o-linear-gradient(top,#8ac1ef 0,#82b3de 100%);background-image:linear-gradient(to bottom,#8ac1ef 0,#82b3de 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff8ac1ef',endColorstr='#ff82b3de',GradientType=0)}.slider-track-low,.slider-track-high{position:absolute;background:transparent;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;border-radius:4px}.slider-handle{position:absolute;top:0;width:20px;height:20px;background-color:#337ab7;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7',endColorstr='#ff2e6da4',GradientType=0);filter:none;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05);border:0 solid transparent}.slider-handle.round{border-radius:50%}.slider-handle.triangle{background:transparent none}.slider-handle.custom{background:transparent none}.slider-handle.custom::before{line-height:20px;font-size:20px;content:'\2605';color:#726204}.slider-tick{position:absolute;width:20px;height:20px;background-image:-webkit-linear-gradient(top,#f9f9f9 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#f9f9f9 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#f9f9f9 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff9f9f9',endColorstr='#fff5f5f5',GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;filter:none;opacity:.8;border:0 solid transparent}.slider-tick.round{border-radius:50%}.slider-tick.triangle{background:transparent none}.slider-tick.custom{background:transparent none}.slider-tick.custom::before{line-height:20px;font-size:20px;content:'\2605';color:#726204}.slider-tick.in-selection{background-image:-webkit-linear-gradient(top,#8ac1ef 0,#82b3de 100%);background-image:-o-linear-gradient(top,#8ac1ef 0,#82b3de 100%);background-image:linear-gradient(to bottom,#8ac1ef 0,#82b3de 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff8ac1ef',endColorstr='#ff82b3de',GradientType=0);opacity:1} \ No newline at end of file