diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 50a2a47c5..6b311c6f5 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -4,9 +4,9 @@ //= require autocomplete-rails //= require vendor //= require jquery.dataTables_ext -//= require lib/lib //= require_tree ./design //= require_tree ./extra +//= require_tree ./lib //= require underscore //= require backbone diff --git a/app/assets/javascripts/backbone/models/advisory.js b/app/assets/javascripts/backbone/models/advisory.js index 654ac09c6..74e15fb21 100644 --- a/app/assets/javascripts/backbone/models/advisory.js +++ b/app/assets/javascripts/backbone/models/advisory.js @@ -3,7 +3,91 @@ Rosa.Models.Advisory = Backbone.Model.extend({ id: null, description: null, references: null, - update_type: null + update_type: null, + found: false + }, + + initialize: function() { + _.bindAll(this, 'findByAdvisoryID'); + + this.url = '/advisories'; + }, + + findByAdvisoryID: function(id, bl_type, options) { + var self = this; + + var urlError = function() { + throw new Error("A 'url' property or function must be specified"); + }; + + var typeError = function() { + throw new Error("A 'bl_type' must be 'security' or 'bugfix'"); + }; + + var idError = function() { + throw new Error("A 'id' must be a string at least 4 characters long"); + }; + + if ( (typeof(id) != "string") || (id.length < 4) ) { + idError(); + } + + if ( (bl_type == undefined) || (bl_type == null) || ((bl_type != 'security') && (bl_type != 'bugfix')) ) { + typeError(); + } + + options |= {}; + var data = _.extend({ + query: id, + bl_type: bl_type + }, {}); + + var params = _.extend({ + type: 'GET', + dataType: 'json', + beforeSend: function( xhr ) { + var token = $('meta[name="csrf-token"]').attr('content'); + if (token) xhr.setRequestHeader('X-CSRF-Token', token); + + self.trigger('search:start'); + } + }, options); + + if (!params.url) { + params.url = ((_.isFunction(this.url) ? this.url() : this.url) + '/search') || urlError(); + } + + params.data = data; + + var complete = options.complete; + params.complete = function(jqXHR, textStatus) { + //console.log(jqXHR); + + switch (jqXHR.status) { + case 200: + self.set(_.extend({ + found: true + }, JSON.parse(jqXHR.responseText)), {silent: true}); + self.trigger('search:end'); + break + + case 404: + self.set(self.defaults, {silent: true}); + self.trigger('search:end'); + break + + default: + self.set(self.defaults, {silent: true}); + self.trigger('search:failed'); + } + + if (complete) complete(jqXHR, textStatus); + } + + $.ajax(params); + + return this; + } }); diff --git a/app/assets/javascripts/backbone/routers/build_lists_advisories_router.js b/app/assets/javascripts/backbone/routers/build_lists_advisories_router.js index bf20ecfa0..950db332d 100644 --- a/app/assets/javascripts/backbone/routers/build_lists_advisories_router.js +++ b/app/assets/javascripts/backbone/routers/build_lists_advisories_router.js @@ -2,8 +2,7 @@ Rosa.Routers.BuildListsAdvisoriesRouter = Backbone.Router.extend({ routes: {}, initialize: function() { - this.advisoriesCollection = new Rosa.Collections.AdvisoriesCollection(Rosa.bootstrapedData.advisories); - this.advisoriesView = new Rosa.Views.BuildListAdvisoriesView({ collection: this.advisoriesCollection }); + this.advisoriesView = new Rosa.Views.BuildListAdvisoriesView({ model: new Rosa.Models.Advisory() }); this.advisoriesView.render(); } diff --git a/app/assets/javascripts/backbone/views/build_list_advisories_view.js b/app/assets/javascripts/backbone/views/build_list_advisories_view.js index 7a47f0797..0b950150f 100644 --- a/app/assets/javascripts/backbone/views/build_list_advisories_view.js +++ b/app/assets/javascripts/backbone/views/build_list_advisories_view.js @@ -1,56 +1,150 @@ Rosa.Views.BuildListAdvisoriesView = Backbone.View.extend({ initialize: function() { - _.bindAll(this, 'popoverTitle', 'popoverDesc', 'showAdvisory', - 'changeAdvisoryList', 'showPreview', 'showForm', 'hideAll'); - this.$el = $('#advisory_block'); - this._$form = this.$('#new_advisory_form'); - this._$preview = this.$('#advisory_preview'); - this._$type_select = $('#build_list_update_type'); - this._$selector = this.$('#attach_advisory'); + _.bindAll(this, 'showAdvisory', 'showPreview', 'showForm', + 'showSearch', 'hideAll', 'displayStatus', 'processSearch', + 'showInTypeSelect', 'typeSelectChange'); + + this.$el = $('#advisory_block'); + this._$type_select = $('#build_list_update_type'); + this._$publish_button = $('input[type="submit"][name="publish"]'); + + this._$form = this.$('#new_advisory_form'); + this._$preview = this.$('#advisory_preview'); + + this._$search = this.$('#advisory_search_block'); + this._$search_field = this.$('#advisory_search'); + this._$not_found = this.$('#advisory_search_block > .advisory_not_found'); + this._$server_error = this.$('#advisory_search_block > .server_error'); + this._$continue_input = this.$('#advisory_search_block > .continue_input'); + this._search_timer = null; + + this._$selector = this.$('#attach_advisory'); + + this._state_vars = {}; + this._state_vars = _.extend({ + checked_update_type: this._$type_select.val(), + header_text: this._$preview.children('h3').html() + }, this.state_vars); this._$selector.on('change', this.showAdvisory); - this._$type_select.on('change', this.changeAdvisoryList); + this._$search_field.on('input keyup', this.processSearch); + + this._$type_select.on('change', this.typeSelectChange); + + this.model.on('search:start', function() { + this._$publish_button.prop({disabled: true}); + }, this); + this.model.on('search:end', this.showPreview, this); + this.model.on('search:failed', this.handleSearchError, this); }, - changeAdvisoryList: function() { - this._$selector.children('.popoverable').hide(); - this._$selector.children('.popoverable.' + this._$type_select.val()).show(); - this._$selector.val('no').trigger('change'); - }, - - popoverTitle: function(el) { - return el.val(); - }, - - popoverDesc: function(el) { - return this.collection.get(el.val()).get('popover_desc'); - }, - - showAdvisory: function(el) { - var adv_id = this._$selector.val(); - switch (adv_id) { + showAdvisory: function(ev) { + this._$publish_button.prop({disabled: false}); + switch (this._$selector.val()) { case 'no': this.hideAll(); + this.showInTypeSelect('all'); break case 'new': this.showForm(); + this.showInTypeSelect('advisoriable'); break default: - this.showPreview(adv_id); + this.showSearch(); + this.showInTypeSelect('advisoriable'); + this._$publish_button.prop({disabled: true}); } }, + typeSelectChange: function(ev) { + switch (this._$selector.val()) { + case 'no': + this._state_vars.checked_update_type = this._$selector.val(); + break + case 'new': + break + default: + this._$search_field.trigger('input'); + } + }, + + showInTypeSelect: function(type) { + var children = this._$type_select.children('option'); + if (type != 'all') { + var visible_ch = children.filter('.' + type); + var sel = children.filter(':selected'); + + children.prop('disabled', true); + visible_ch.prop('disabled', false); + if (sel.prop('disabled')) { + sel.prop('selected', false); + visible_ch.first().prop('selected', true); + } + } else { + children.prop('disabled', false).prop('selected', false); + children.filter('option[value="' + this._state_vars.checked_update_type + '"]').prop('selected', true); + } + }, + + processSearch: function(ev) { + if (ev.type == "keyup") { + if (ev.keyCode != 13) { + return + } else { + ev.preventDefault(); + } + } + + var TIMER_INTERVAL = 500; + + var self = this; + + var timerCallback = function() { + if (self._$search_field.val().length > 3) { + // real search + self.model.findByAdvisoryID(self._$search_field.val(), self._$type_select.val()); + } else { + // hide preview if nothing to show + if (self._$preview.is(':visible')) { + self._$preview.slideUp(); + } + self.displayStatus('found'); + } + }; + + if (this.model.get('advisory_id') == this._$search_field.val()) { + this.showPreview(); + return; + } + // timeout before real AJAX request + clearTimeout(this._search_timer); + this._search_timer = setTimeout(timerCallback, TIMER_INTERVAL); + }, + showPreview: function(id) { + this._$publish_button.prop({disabled: false}); if (this._$form.is(':visible')) { this._$form.slideUp(); } - var adv = this.collection.get(id); var prev = this._$preview; - prev.children('h3').html(prev.children('h3').html() + ' ' + adv.get('advisory_id')); - prev.children('.descr').html(adv.get('description')); - prev.children('.refs').html(adv.get('references')); - if (!this._$preview.is(':visible')) { - this._$preview.slideDown(); + var adv = this.model; + if (adv.get('found')) { + this._$selector.children('option.advisory_id').val(adv.get('advisory_id')); + + prev.children('h3').html(this._state_vars.header_text + ' ' + adv.get('advisory_id')); + prev.children('.descr').html(adv.get('description')); + prev.children('.refs').html(adv.get('references')); + if (!this._$preview.is(':visible')) { + this._$preview.slideDown(); + } + this.displayStatus('found'); + } else { + if (this._$preview.is(':visible')) { + this._$preview.slideUp(); + } + this._$publish_button.prop({disabled: true}); + this.displayStatus('not_found'); + this._$selector.children('option.advisory_id').val(''); } }, @@ -58,12 +152,27 @@ Rosa.Views.BuildListAdvisoriesView = Backbone.View.extend({ if (this._$preview.is(':visible')) { this._$preview.slideUp(); } + if (this._$search.is(':visible')) { + this._$search.slideUp(); + } if (!this._$form.is(':visible')) { this._$form.slideDown(); } }, - hideAll: function() { + showSearch: function() { + if (this._$form.is(':visible')) { + this._$form.slideUp(); + } + if (!this._$search.is(':visible')) { + this._$search.slideDown(); + this._$search_field.trigger('input'); + } + }, + + handleSearchError: function() { + this._$publish_button.prop({disabled: true}); + this.displayStatus('error'); if (this._$preview.is(':visible')) { this._$preview.slideUp(); } @@ -72,14 +181,33 @@ Rosa.Views.BuildListAdvisoriesView = Backbone.View.extend({ } }, + hideAll: function() { + if (this._$preview.is(':visible')) { + this._$preview.slideUp(); + } + if (this._$search.is(':visible')) { + this._$search.slideUp(); + } + if (this._$form.is(':visible')) { + this._$form.slideUp(); + } + }, + + displayStatus: function(st) { + var ELEMS = { + 'found': this._$continue_input, + 'not_found': this._$not_found, + 'error': this._$server_error + }; + + this._$continue_input.hide(); + this._$not_found.hide(); + this._$server_error.hide(); + + ELEMS[st].show(); + }, + render: function() { - var title = this.popoverTitle; - var description = this.popoverDesc; - this.changeAdvisoryList(); - this.$('#attach_advisory > .popoverable').popover({ - title: function() { return title($(this)); }, - content: function() { return description($(this)); } - }); this.showAdvisory(); return this; } diff --git a/app/assets/javascripts/extra/build_list.js b/app/assets/javascripts/extra/build_list.js index 654fc0d04..6402c9e5d 100644 --- a/app/assets/javascripts/extra/build_list.js +++ b/app/assets/javascripts/extra/build_list.js @@ -9,10 +9,10 @@ $(document).ready(function() { if ($(this).val() == platform_id) { if ($(this).attr('data-released') === '1') { $('#build_list_auto_publish').removeAttr('checked').attr('disabled', 'disabled'); - disableUpdateTypes(); + //disableUpdateTypes(); } else { $('#build_list_auto_publish').removeAttr('disabled').attr('checked', 'checked'); - enableUpdateTypes(); + //enableUpdateTypes(); } $(this).attr('checked', 'checked').removeAttr('disabled').trigger('change'); diff --git a/app/assets/javascripts/lib/lib.js b/app/assets/javascripts/lib/lib.js deleted file mode 100644 index 2d346835e..000000000 --- a/app/assets/javascripts/lib/lib.js +++ /dev/null @@ -1,3 +0,0 @@ -//= require ./jquery.placeholder -//= require ./bootstrap-tooltip -//= require ./bootstrap-popover diff --git a/app/assets/stylesheets/design/custom.scss b/app/assets/stylesheets/design/custom.scss index 45d64635e..6f0d30434 100644 --- a/app/assets/stylesheets/design/custom.scss +++ b/app/assets/stylesheets/design/custom.scss @@ -962,10 +962,55 @@ form.mass_build input[type="checkbox"] { height: 11px; } -div#new_advisory_form, div#advisory_preview { +div#new_advisory_form, +div#advisory_preview, +div#advisory_search_block, +div#advisory_search_block div.info { display: none; } +div#advisory_search_block { + padding-bottom: 15px; +} + +p.hint_text { + color: #666666; + font-size: 0.9em; + padding: 0; +} + +div#advisory_block p.hint_text { + display: block; + width: 350px; +} + +div#advisory_search_block div.info { + width: 565px; + border: solid 1px; + border-radius: 5px; +} + +div#advisory_search_block div.info p { + text-align: center; + margin: 0.5em 2em 0.7em; +} + +div#advisory_search_block div.advisory_not_found { + background-color: #B7CFFF; + border-color: #6666FF; +} + +div#advisory_search_block div.server_error { + background-color: #FACFCF; + border-color: #FF7777; +} + +div#advisory_search_block div.continue_input { + background-color: #CFFACF; + border-color: #00CF00; + display: block; +} + /*=============== popovers ===============*/ .popover { @@ -1099,3 +1144,39 @@ form.mass_build section.right { .min_width_120 { min-width: 120px; } + +.chzn-select { + width: 350px; +} + +.packages_info_container ul { + list-style-type: none; + padding-left: 25px; + margin: 5px 0; +} + +.packages_info_container ul.platforms { + padding: 0; +} + +/* remove this lines after change to backbone */ +table.tablesorter.advisories thead tr.search th { + padding: 0 5px; +} + +table.tablesorter.advisories thead tr.search th input[type='text'] { + width: 640px; +} + +table.tablesorter.advisories thead tr.search th form { + float: left; +} + +table.tablesorter.advisories thead tr.search th form.button_to { + padding: 3px 0 0 7px; +} + +table.tablesorter tr td.no_results { + text-align: center; +} +/* end */ diff --git a/app/controllers/advisories_controller.rb b/app/controllers/advisories_controller.rb index 979712412..9bbf15f6c 100644 --- a/app/controllers/advisories_controller.rb +++ b/app/controllers/advisories_controller.rb @@ -1,20 +1,50 @@ # -*- encoding : utf-8 -*- class AdvisoriesController < ApplicationController before_filter :authenticate_user! - before_filter :find_advisory, :only => [:show] skip_before_filter :authenticate_user! if APP_CONFIG['anonymous_access'] - load_and_authorize_resource + load_resource :find_by => :advisory_id + authorize_resource + + before_filter :fetch_packages_info, :only => [:show] def index + @advisories = @advisories.scoped(:include => :platforms) + @advisories = @advisories.search_by_id(params[:q]) if params[:q] @advisories = @advisories.paginate(:page => params[:page]) + respond_to do |format| + format.html + format.atom + end end def show end + def search + @advisory = Advisory.by_update_type(params[:bl_type]).search_by_id(params[:query]).first + raise ActionController::RoutingError.new('Not Found') if @advisory.nil? + respond_to do |format| + format.json { render @advisory } + end + end + protected - def find_advisory - @advisory = Advisory.where(:advisory_id => params[:id]).limit(1).first if params[:id].present? + # this method fetches and structurize packages attached to current advisory. + def fetch_packages_info + @packages_info = Hash.new { |h, k| h[k] = {} } # maaagic, it's maaagic ;) + @advisory.build_lists.find_in_batches(:include => [:save_to_platform, :packages, :project]) do |batch| + batch.each do |build_list| + tmp = build_list.packages.inject({:srpm => nil, :rpm => []}) do |h, p| + p.package_type == 'binary' ? h[:rpm] << p.fullname : h[:srpm] = p.fullname + h + end + h = { build_list.project => tmp } + @packages_info[build_list.save_to_platform].merge!(h) do |pr, old, new| + {:srpm => new[:srpm], :rpm => old[:rpm].concat(new[:rpm]).uniq} + end + end + end end + end diff --git a/app/controllers/platforms/mass_builds_controller.rb b/app/controllers/platforms/mass_builds_controller.rb new file mode 100644 index 000000000..ecea50458 --- /dev/null +++ b/app/controllers/platforms/mass_builds_controller.rb @@ -0,0 +1,46 @@ +#class MassBuildsController < ApplicationController +class Platforms::MassBuildsController < Platforms::BaseController + before_filter :authenticate_user! + + load_and_authorize_resource :platform + load_and_authorize_resource + + skip_load_and_authorize_resource :only => [:index, :create] + skip_load_and_authorize_resource :platform, :only => [:cancel, :failed_builds_list] + skip_authorize_resource :platform, :only => [:create, :index] + + def create + mass_build = @platform.mass_builds.new(:repositories => params[:repositories], + :arches => params[:arches], + :auto_publish => params[:auto_publish] || false) + mass_build.user = current_user + authorize! :create, mass_build + + if mass_build.save + redirect_to(platform_mass_builds_path(@platform), :notice => t("flash.platform.build_all_success")) + else + @auto_publish_selected = params[:auto_publish].present? + @mass_builds = MassBuild.by_platform(@platform).order('created_at DESC').paginate(:page => params[:page], :per_page => 20) + flash[:warning] = mass_build.errors.full_messages.join('. ') + flash[:error] = t("flash.platform.build_all_error") + render :index + end + end + + def index + authorize! :edit, @platform + + @mass_builds = MassBuild.by_platform(@platform).order('created_at DESC').paginate(:page => params[:page], :per_page => 20) + @auto_publish_selected = true + end + + def cancel + @mass_build.cancel_all + flash[:notice] = t("flash.platform.cancel_mass_build") + redirect_to platform_mass_builds_path(@mass_build.platform) + end + + def failed_builds_list + render :text => @mass_build.generate_failed_builds_list + end +end diff --git a/app/controllers/platforms/platforms_controller.rb b/app/controllers/platforms/platforms_controller.rb index 14058ba36..5a948d06e 100644 --- a/app/controllers/platforms/platforms_controller.rb +++ b/app/controllers/platforms/platforms_controller.rb @@ -7,35 +7,6 @@ class Platforms::PlatformsController < Platforms::BaseController autocomplete :user, :uname - def build_all - mass_build = MassBuild.new( - :platform => @platform, - :user => current_user, - :repositories => params[:repositories], - :arches => params[:arches], - :auto_publish => params[:auto_publish] || false - ) - if mass_build.save - redirect_to(mass_builds_platform_path(@platform), :notice => t("flash.platform.build_all_success")) - else - @auto_publish_selected = params[:auto_publish].present? - @mass_builds = MassBuild.by_platform(@platform).order('created_at DESC').paginate(:page => params[:page], :per_page => 20) - flash[:warning] = mass_build.errors.full_messages.join('. ') - flash[:error] = t("flash.platform.build_all_error") - end - end - - def mass_builds - @mass_builds = MassBuild.by_platform(@platform).order('created_at DESC').paginate(:page => params[:page], :per_page => 20) - @auto_publish_selected = true - render :action => :build_all - end - - def failed_builds_list - @mass_build = MassBuild.find params[:mass_build_id] - render :text => @mass_build.generate_failed_builds_list - end - def index @platforms = @platforms.accessible_by(current_ability, :related).paginate(:page => params[:page], :per_page => 20) end diff --git a/app/controllers/projects/build_lists_controller.rb b/app/controllers/projects/build_lists_controller.rb index a5b3b8a0e..cb5115741 100644 --- a/app/controllers/projects/build_lists_controller.rb +++ b/app/controllers/projects/build_lists_controller.rb @@ -25,7 +25,8 @@ class Projects::BuildListsController < Projects::BaseController def index @action_url = @project ? search_project_build_lists_path(@project) : search_build_lists_path @filter = BuildList::Filter.new(@project, current_user, params[:filter] || {}) - @build_lists = @filter.find.recent.paginate :page => params[:page] + @build_lists = @filter.find.scoped(:include => [:save_to_platform, :project, :user, :arch]) + @build_lists = @build_lists.recent.paginate :page => params[:page] @build_server_status = begin BuildServer.get_status @@ -70,7 +71,6 @@ class Projects::BuildListsController < Projects::BaseController def show @item_groups = @build_list.items.group_by_level - @advisories = @build_list.project.advisories end def update @@ -171,11 +171,16 @@ class Projects::BuildListsController < Projects::BaseController @build_list.update_type = params[:build_list][:update_type] if params[:build_list][:update_type].present? if params[:attach_advisory].present? and params[:attach_advisory] != 'no' and !@build_list.advisory + + unless @build_list.update_type.in? BuildList::RELEASE_UPDATE_TYPES + redirect_to :back, :notice => t('lyout.build_lists.publish_fail') and return + end + if params[:attach_advisory] == 'new' # create new advisory - if !@build_list.build_advisory(params[:build_list][:advisory]) do |a| + unless @build_list.build_advisory(params[:build_list][:advisory]) do |a| a.update_type = @build_list.update_type - a.project = @build_list.project + a.projects << @build_list.project a.platforms << @build_list.save_to_platform unless a.platforms.include? @build_list.save_to_platform end.save redirect_to :back, :notice => t('layout.build_lists.publish_fail') and return @@ -187,12 +192,15 @@ class Projects::BuildListsController < Projects::BaseController redirect_to :back, :notice => t('layout.build_lists.publish_fail') and return end a.platforms << @build_list.save_to_platform unless a.platforms.include? @build_list.save_to_platform + a.projects << @build_list.project unless a.projects.include? @build_list.project @build_list.advisory = a unless a.save redirect_to :back, :notice => t('layout.build_lists.publish_fail') and return end end + end + if @build_list.save and @build_list.now_publish redirect_to :back, :notice => t('layout.build_lists.publish_success') else diff --git a/app/helpers/advisories_helper.rb b/app/helpers/advisories_helper.rb index 62a940bb7..42634b590 100644 --- a/app/helpers/advisories_helper.rb +++ b/app/helpers/advisories_helper.rb @@ -1,9 +1,13 @@ # -*- encoding : utf-8 -*- module AdvisoriesHelper def advisories_select_options(advisories, opts = {:class => 'popoverable'}) - def_values = [[t("layout.advisories.no_"), 'no'], [t("layout.advisories.new"), 'new']] - options_for_select(def_values, def_values.first) + - options_for_select(advisories.map { |a| [a.advisory_id, :class => "#{opts[:class]} #{a.update_type}"] }) + def_values = [[t("layout.advisories.no_"), 'no'], [t("layout.advisories.new"), 'new'], [t("layout.advisories.existing"), 'existing', {:class => 'advisory_id'}]] + options_for_select(def_values, def_values.first) + end + + def advisory_id_for_hint + sprintf(Advisory::ID_STRING_TEMPLATE, :type => "{#{Advisory::TYPES.values.join(',')}}", + :year => 'YYYY', :id => 'XXXX') end def construct_ref_link(ref) diff --git a/app/helpers/build_lists_helper.rb b/app/helpers/build_lists_helper.rb index dde82260e..6509b3cb5 100644 --- a/app/helpers/build_lists_helper.rb +++ b/app/helpers/build_lists_helper.rb @@ -23,4 +23,24 @@ module BuildListsHelper '' end + + def build_list_classified_update_types + advisoriable = BuildList::RELEASE_UPDATE_TYPES.map do |el| + [el, {:class => 'advisoriable'}] + end + nonadvisoriable = (BuildList::UPDATE_TYPES - BuildList::RELEASE_UPDATE_TYPES).map do |el| + [el, {:class => 'nonadvisoriable'}] + end + + return advisoriable + nonadvisoriable + end + + def build_list_version_link(build_list, str_version = false) + if build_list.commit_hash.present? + link_to str_version ? "#{shortest_hash_id @build_list.commit_hash} ( #{@build_list.project_version} )" : shortest_hash_id(build_list.commit_hash), + commit_path(build_list.project.owner, build_list.project, build_list.commit_hash) + else + build_list.project_version + end + end end diff --git a/app/models/ability.rb b/app/models/ability.rb index 57d309d48..08f7c45e2 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -85,22 +85,25 @@ class Ability can [:read, :related, :members], Platform, :owner_type => 'Group', :owner_id => user.group_ids can([:read, :related, :members], Platform, read_relations_for('platforms')) {|platform| local_reader? platform} can([:update, :members], Platform) {|platform| local_admin? platform} - can([:destroy, :members, :add_member, :remove_member, :remove_members, :build_all, :mass_builds] , Platform) {|platform| owner? platform} + can([:destroy, :members, :add_member, :remove_member, :remove_members] , Platform) {|platform| owner?(platform) || local_admin?(platform) } can [:autocomplete_user_uname, :read_advisories, :advisories], Platform + can([:failed_builds_list, :create], MassBuild) {|mass_build| (owner?(mass_build.platform) || local_admin?(mass_build.platform)) && mass_build.platform.main? } + can(:cancel, MassBuild) {|mass_build| (owner?(mass_build.platform) || local_admin?(mass_build.platform)) && !mass_build.stop_build && mass_build.platform.main?} + can [:read, :projects_list], Repository, :platform => {:visibility => 'open'} can [:read, :projects_list], Repository, :platform => {:owner_type => 'User', :owner_id => user.id} can [:read, :projects_list], Repository, :platform => {:owner_type => 'Group', :owner_id => user.group_ids} can([:read, :projects_list], Repository, read_relations_for('repositories', 'platforms')) {|repository| local_reader? repository.platform} can([:create, :update, :projects_list, :add_project, :remove_project], Repository) {|repository| local_admin? repository.platform} - can(:clear, Platform) {|platform| local_admin?(platform) && platform.platform_type == 'personal'} + can(:clear, Platform) {|platform| local_admin?(platform) && platform.personal?} can([:change_visibility, :settings, :destroy], Repository) {|repository| owner? repository.platform} can :read, Product, :platform => {:visibility => 'open'} can :read, Product, :platform => {:owner_type => 'User', :owner_id => user.id, :platform_type => 'main'} can :read, Product, :platform => {:owner_type => 'Group', :owner_id => user.group_ids, :platform_type => 'main'} - can(:read, Product, read_relations_for('products', 'platforms')) {|product| product.platform.platform_type == 'main'} - can([:create, :update, :destroy, :clone], Product) {|product| local_admin? product.platform and product.platform.platform_type == 'main'} + can(:read, Product, read_relations_for('products', 'platforms')) {|product| product.platform.main?} + can([:create, :update, :destroy, :clone], Product) {|product| local_admin? product.platform and product.platform.main?} can(:create, ProductBuildList) {|pbl| can?(:update, pbl.product)} can(:destroy, ProductBuildList) {|pbl| can?(:destroy, pbl.product)} @@ -131,7 +134,10 @@ class Ability cannot [:members, :add_member, :remove_member, :remove_members], Platform, :platform_type => 'personal' cannot [:create, :update, :destroy, :clone], Product, :platform => {:platform_type => 'personal'} - cannot [:clone, :build_all, :mass_builds], Platform, :platform_type => 'personal' + cannot [:clone], Platform, :platform_type => 'personal' + + cannot([:failed_builds_list, :create], MassBuild) {|mass_build| mass_build.platform.personal?} + cannot(:cancel, MassBuild) {|mass_build| mass_build.platform.personal? || mass_build.stop_build} can :create, Subscribe do |subscribe| !subscribe.subscribeable.subscribes.exists?(:user_id => user.id) diff --git a/app/models/advisory.rb b/app/models/advisory.rb index c643f333b..4410ba621 100644 --- a/app/models/advisory.rb +++ b/app/models/advisory.rb @@ -1,17 +1,21 @@ class Advisory < ActiveRecord::Base has_and_belongs_to_many :platforms + has_and_belongs_to_many :projects has_many :build_lists - belongs_to :project validates :description, :update_type, :presence => true + validates :update_type, :inclusion => BuildList::RELEASE_UPDATE_TYPES after_create :generate_advisory_id before_save :normalize_references, :if => :references_changed? - ID_TEMPLATE = 'ROSA-%s-%d:%04d' + ID_TEMPLATE = 'ROSA-%s-%d:%04d' + ID_STRING_TEMPLATE = 'ROSA-%s-%04s:%04s' TYPES = {'security' => 'SA', 'bugfix' => 'A'} - scope :by_project, lambda {|p| where('project_id' => p.try(:id) || p)} + scope :search_by_id, lambda { |aid| where('advisory_id ILIKE ?', "%#{aid.to_s.strip}%") } + scope :by_update_type, lambda { |ut| where(:update_type => ut) } + default_scope order('created_at DESC') def to_param advisory_id diff --git a/app/models/build_list.rb b/app/models/build_list.rb index 14ccd466d..a3f0b416b 100644 --- a/app/models/build_list.rb +++ b/app/models/build_list.rb @@ -16,9 +16,9 @@ class BuildList < ActiveRecord::Base validates :project_id, :project_version, :arch, :include_repos, :presence => true validates_numericality_of :priority, :greater_than_or_equal_to => 0 validates :update_type, :inclusion => UPDATE_TYPES, - :unless => Proc.new { |b| b.save_to_platform.released } + :unless => Proc.new { |b| b.advisory.present? } validates :update_type, :inclusion => {:in => RELEASE_UPDATE_TYPES, :message => I18n.t('flash.build_list.frozen_platform')}, - :if => Proc.new { |b| b.save_to_platform.released && b.mass_build_id.nil?} + :if => Proc.new { |b| b.advisory.present? } validate lambda { errors.add(:build_for_platform, I18n.t('flash.build_list.wrong_platform')) if save_to_platform.platform_type == 'main' && save_to_platform_id != build_for_platform_id } @@ -76,6 +76,7 @@ class BuildList < ActiveRecord::Base scope :for_platform, lambda { |platform| where(:build_for_platform_id => platform.id) } scope :by_mass_build, lambda { |mass_build| where(:mass_build_id => mass_build) } scope :scoped_to_arch, lambda {|arch| where(:arch_id => arch) } + scope :scoped_to_save_platform, lambda {|pl_id| where(:save_to_platform_id => pl_id) } scope :scoped_to_project_version, lambda {|project_version| where(:project_version => project_version) } scope :scoped_to_is_circle, lambda {|is_circle| where(:is_circle => is_circle) } scope :for_creation_date_period, lambda{|start_date, end_date| @@ -219,6 +220,10 @@ class BuildList < ActiveRecord::Base self.class.human_status(status) end + def self.status_by_human(human) + BuildList::HUMAN_STATUSES.key human + end + def set_items(items_hash) self.items = [] diff --git a/app/models/build_list/filter.rb b/app/models/build_list/filter.rb index f023d25cc..b6095fc71 100644 --- a/app/models/build_list/filter.rb +++ b/app/models/build_list/filter.rb @@ -15,14 +15,17 @@ class BuildList::Filter build_lists = build_lists.accessible_by(::Ability.new(@user), @options[:ownership].to_sym) if @options[:ownership] build_lists = build_lists.for_status(@options[:status]) if @options[:status] build_lists = build_lists.scoped_to_arch(@options[:arch_id]) if @options[:arch_id] + build_lists = build_lists.scoped_to_save_platform(@options[:platform_id]) if @options[:platform_id] build_lists = build_lists.scoped_to_project_version(@options[:project_version]) if @options[:project_version] build_lists = build_lists.scoped_to_is_circle(@options[:is_circle]) if @options[:is_circle].present? build_lists = build_lists.scoped_to_project_name(@options[:project_name]) if @options[:project_name] build_lists = build_lists.by_mass_build(@options[:mass_build_id]) if @options[:mass_build_id] - if @options[:created_at_start] || @options[:created_at_end] - build_lists = build_lists.for_creation_date_period(@options[:created_at_start], @options[:created_at_end]) - end +# TODO [BuildList#created_at filters] Uncomment here and in build_lists/_filter.html.haml to return filters +# +# if @options[:created_at_start] || @options[:created_at_end] +# build_lists = build_lists.for_creation_date_period(@options[:created_at_start], @options[:created_at_end]) +# end if @options[:updated_at_start] || @options[:updated_at_end] build_lists = build_lists.for_notified_date_period(@options[:updated_at_start], @options[:updated_at_end]) end @@ -51,6 +54,7 @@ class BuildList::Filter :updated_at_start => nil, :updated_at_end => nil, :arch_id => nil, + :platform_id => nil, :is_circle => nil, :project_version => nil, :bs_id => nil, @@ -66,6 +70,7 @@ class BuildList::Filter @options[:updated_at_end] = build_date_from_params(:updated_at_end, @options) @options[:project_version] = @options[:project_version].presence @options[:arch_id] = @options[:arch_id].present? ? @options[:arch_id].to_i : nil + @options[:platform_id] = @options[:platform_id].present? ? @options[:platform_id].to_i : nil @options[:is_circle] = @options[:is_circle].present? ? @options[:is_circle] == "1" : nil @options[:bs_id] = @options[:bs_id].presence @options[:project_name] = @options[:project_name].presence diff --git a/app/models/mass_build.rb b/app/models/mass_build.rb index 3c13d754a..8cdc5c0f9 100644 --- a/app/models/mass_build.rb +++ b/app/models/mass_build.rb @@ -6,11 +6,13 @@ class MassBuild < ActiveRecord::Base scope :by_platform, lambda { |platform| where(:platform_id => platform.id) } attr_accessor :repositories, :arches + attr_accessible :repositories, :arches, :auto_publish - validates :platform_id, :arch_names, :name, :user_id, :repositories, :presence => true + validates :platform_id, :arch_names, :name, :user_id, :repositories, :rep_names, :presence => true validates_inclusion_of :auto_publish, :in => [true, false] after_create :build_all + before_validation :set_data COUNT_STATUSES = [ :build_lists, @@ -21,16 +23,6 @@ class MassBuild < ActiveRecord::Base :build_error ] - def initialize(args = nil) - super - - if new_record? - rep_names = Repository.where(:id => self.repositories).map(&:name).join(", ") - self.name = "#{Time.now.utc.to_date.strftime("%d.%b")}-#{platform.name}(#{rep_names})" - self.arch_names = Arch.where(:id => self.arches).map(&:name).join(", ") - end - end - # ATTENTION: repositories and arches must be set before calling this method! def build_all platform.build_all( @@ -50,4 +42,23 @@ class MassBuild < ActiveRecord::Base end report end + + def cancel_all + self.update_attribute(:stop_build, true) + self.build_lists.find_each(:batch_size => 100) do |bl| + bl.cancel + end + end + later :cancel_all, :queue => :clone_build + + private + + def set_data + if new_record? + self.rep_names = Repository.where(:id => self.repositories).map(&:name).join(", ") + self.name = "#{Time.now.utc.to_date.strftime("%d.%b")}-#{platform.name}" + self.arch_names = Arch.where(:id => self.arches).map(&:name).join(", ") + end + end + end diff --git a/app/models/platform.rb b/app/models/platform.rb index 95cc24ba2..ddfadd1ee 100644 --- a/app/models/platform.rb +++ b/app/models/platform.rb @@ -107,6 +107,10 @@ class Platform < ActiveRecord::Base platform_type == 'personal' end + def main? + platform_type == 'main' + end + def base_clone(attrs = {}) # :description, :name, :owner dup.tap do |c| attrs.each {|k,v| c.send("#{k}=", v)} # c.attributes = attrs @@ -167,6 +171,7 @@ class Platform < ActiveRecord::Base auto_publish = opts[:auto_publish] || false user = opts[:user] mass_build_id = opts[:mass_build_id] + mass_build = MassBuild.find mass_build_id repositories.each do |rep| rep.projects.find_in_batches(:batch_size => 2) do |group| @@ -174,6 +179,7 @@ class Platform < ActiveRecord::Base group.each do |p| arches.map(&:name).each do |arch| begin + return if mass_build.reload.stop_build p.build_for(self, user, arch, auto_publish, mass_build_id) rescue RuntimeError, Exception # p.async(:build_for, self, user, arch, auto_publish, mass_build_id) # TODO need this? diff --git a/app/models/project.rb b/app/models/project.rb index ee3e48b7b..c41310668 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -19,8 +19,8 @@ class Project < ActiveRecord::Base has_many :collaborators, :through => :relations, :source => :actor, :source_type => 'User' has_many :groups, :through => :relations, :source => :actor, :source_type => 'Group' - has_many :advisories # should be without :dependent => :destroy has_many :packages, :class_name => "BuildList::Package", :dependent => :destroy + has_and_belongs_to_many :advisories # should be without :dependent => :destroy validates :name, :uniqueness => {:scope => [:owner_id, :owner_type], :case_sensitive => false}, :presence => true, :format => {:with => /^#{NAME_REGEXP}$/, :message => I18n.t("activerecord.errors.project.uname")} validates :owner, :presence => true diff --git a/app/models/user.rb b/app/models/user.rb index 7bbd42965..ede3710e6 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -73,7 +73,7 @@ class User < ActiveRecord::Base end def fullname - return "#{uname} (#{name})" + return name.present? && name.length > 0 ? "#{uname} (#{name})" : uname end def user_appeal diff --git a/app/views/advisories/_advisory.json.jbuilder b/app/views/advisories/_advisory.json.jbuilder new file mode 100644 index 000000000..5bf528434 --- /dev/null +++ b/app/views/advisories/_advisory.json.jbuilder @@ -0,0 +1,6 @@ +json.id advisory.id +json.advisory_id advisory.advisory_id +json.description advisory.description +json.references advisory.references.split("\n").map { |ref| construct_ref_link(ref) }.join('
') +json.update_type advisory.update_type + diff --git a/app/views/advisories/_feed_partial.haml b/app/views/advisories/_feed_partial.haml new file mode 100644 index 000000000..421dbd53d --- /dev/null +++ b/app/views/advisories/_feed_partial.haml @@ -0,0 +1,8 @@ +%h3= t("activerecord.attributes.advisory.description") +%p= simple_format advisory.description + +%h3= t("activerecord.attributes.advisory.references") +%p + - advisory.references.gsub(/\r| /, '').split("\n").each do |ref| + = construct_ref_link(ref) + %br diff --git a/app/views/advisories/_list.html.haml b/app/views/advisories/_list.html.haml index b6d80ebc9..c19ff66b9 100644 --- a/app/views/advisories/_list.html.haml +++ b/app/views/advisories/_list.html.haml @@ -4,5 +4,29 @@ %th.th1= t("activerecord.attributes.advisory.advisory_id") %th.th2= t("layout.advisories.affected_versions") %th.th3= t("activerecord.attributes.advisory.description") + %tr.search + -# TODO: change filter to Backbone.js + %th{:colspan => 3, :rowspan => 1} + = form_tag advisories_path, :method => :get do |f| + = text_field_tag('q', params[:q], :placeholder => t("layout.advisories.search_by_id"), :class => params[:q].present? ? 'black' : 'gray') + %input{:type => 'submit', :value => t("layout.search.header")} + =# link_to t('layout.back'), advisories_path, :class => 'button' + = button_to t('layout.clear'), {:action => :index} , :method => :get + %tbody - = render :partial => 'list_item', :collection => list, :as => :advisory + - if list.size > 0 + = render :partial => 'list_item', :collection => list, :as => :advisory + - else + %tr.odd + %td.no_results{:colspan => 3} + = t("layout.search.no_results", :query => params[:q]) + +:javascript + $(function() { + var $search = $('tr.search > th input[type="text"]'); + $search.on('blur focus', function() { + if ($search.val() === '') { + $search.toggleClass('gray black'); + } + }); + }); diff --git a/app/views/advisories/_packages_info.html.haml b/app/views/advisories/_packages_info.html.haml new file mode 100644 index 000000000..2cc177c18 --- /dev/null +++ b/app/views/advisories/_packages_info.html.haml @@ -0,0 +1,20 @@ +.packages_info_container + %h3= t('layout.advisories.affected_in') + %ul.platforms + - @packages_info.each_pair do |platform, projects| + %li + %p= raw "#{t('activerecord.models.platform')} #{ link_to platform.name, platform_path(platform) }" + %ul + - projects.each_pair do |project, packages| + %li + %p= raw "#{ t('activerecord.models.project') } #{ link_to project.name, project_path(project) }" + %ul + %li + %p= "SRPM:" + %ul + %li= packages[:srpm] + %li + %p= "RPM:" + %ul + - packages[:rpm].each do |package| + %li= package diff --git a/app/views/advisories/index.atom.builder b/app/views/advisories/index.atom.builder new file mode 100644 index 000000000..1a27b5c32 --- /dev/null +++ b/app/views/advisories/index.atom.builder @@ -0,0 +1,14 @@ +atom_feed do |feed| + feed.title(t("layout.advisories.atom_title")) + feed.updated(@advisories.first.created_at) if @advisories.length > 0 + + @advisories.each do |advisory| + feed.entry(advisory, :url => advisory_url(advisory)) do |entry| + content = raw(render(:inline => true, :partial => 'feed_partial', :locals => { :advisory => advisory })) + + entry.title("#{t("activerecord.models.advisory")} #{advisory.advisory_id}") + entry.content(content, :type => 'html') + + end + end +end diff --git a/app/views/advisories/index.html.haml b/app/views/advisories/index.html.haml index 431a3e919..3e73c2095 100644 --- a/app/views/advisories/index.html.haml +++ b/app/views/advisories/index.html.haml @@ -1,4 +1,8 @@ - set_meta_tags :title => t('layout.advisories.list_header') - render :partial => 'submenu' +%h3.fix + = t("layout.advisories.list_header") + = link_to image_tag("rss.ico", :width => '15px', :height => '15px', :class => 'atom_icon'), + APP_CONFIG['anonymous_access'] ? advisories_path(:format => 'atom') : advisories_path(:format => 'atom', :token => current_user.authentication_token) = render :partial => 'list', :object => @advisories = will_paginate @advisories diff --git a/app/views/advisories/show.html.haml b/app/views/advisories/show.html.haml index 483ba81a9..422ccd0ec 100644 --- a/app/views/advisories/show.html.haml +++ b/app/views/advisories/show.html.haml @@ -3,8 +3,9 @@ %h3= "#{t("activerecord.models.advisory")} #{@advisory.advisory_id}".html_safe -.leftlist= "#{t("layout.advisories.project_name")}:".html_safe -.rightlist= link_to @advisory.project.name, project_path(@advisory.project) +.leftlist= "#{t("layout.advisories.project_names")}:".html_safe +.rightlist + = raw @advisory.projects.map{ |p| link_to p.name_with_owner, project_path(p) }.join(', ') .both .leftlist= "#{t("activerecord.attributes.advisory.created_at")}:".html_safe @@ -33,5 +34,12 @@ %br .both +.leftlist= "#{t("layout.advisories.build_lists")}:".html_safe +.rightlist + = raw @advisory.build_lists.map{ |bl| link_to bl.id, build_list_path(bl) }.join(', ') +.both + += render :partial => 'packages_info' + :javascript $('article .all').addClass('bigpadding'); diff --git a/app/views/groups/members/index.html.haml b/app/views/groups/members/index.html.haml index 0fb2ab5f0..9f80db048 100644 --- a/app/views/groups/members/index.html.haml +++ b/app/views/groups/members/index.html.haml @@ -15,7 +15,7 @@ %span#niceCheckbox1.niceCheck-main= check_box_tag "user_remove[#{user.id}][]" %td .img= image_tag avatar_url(user) - .forimg= link_to user.name, user_path(user) + .forimg= link_to user.fullname, user_path(user) - Relation::ROLES.each_with_index do |role, i| %td .radio= radio_button_tag "user[#{user.id}]", role, ((parent.actors.exists? :actor_id => user.id, :actor_type => 'User', :role => role) ? :checked : nil), :class => 'niceRadio' diff --git a/app/views/platforms/base/_sidebar.html.haml b/app/views/platforms/base/_sidebar.html.haml index 2ba6c1279..434e2ad43 100644 --- a/app/views/platforms/base/_sidebar.html.haml +++ b/app/views/platforms/base/_sidebar.html.haml @@ -10,9 +10,9 @@ = link_to t("layout.platforms.about"), platform_path(@platform) %li{:class => (contr == :repositories) ? 'active' : ''} = link_to t("layout.repositories.list_header"), platform_repositories_path(@platform) - - if can? :mass_builds, @platform - %li{:class => (contr == :platforms && [:mass_builds, :build_all].include?(act)) ? 'active' : ''} - = link_to t("layout.platforms.mass_build"), mass_builds_platform_path(@platform) + - if can? :edit, @platform + %li{:class => (contr == :mass_builds && [:index, :create].include?(act)) ? 'active' : ''} + = link_to t("layout.platforms.mass_build"), platform_mass_builds_path(@platform) - if can? :read, @platform.products.build %li{:class => (contr == :products) ? 'active' : ''} = link_to t("layout.products.list_header"), platform_products_path(@platform) diff --git a/app/views/platforms/platforms/build_all.html.haml b/app/views/platforms/mass_builds/index.html.haml similarity index 73% rename from app/views/platforms/platforms/build_all.html.haml rename to app/views/platforms/mass_builds/index.html.haml index 67b275ac9..15ba91f7a 100644 --- a/app/views/platforms/platforms/build_all.html.haml +++ b/app/views/platforms/mass_builds/index.html.haml @@ -1,7 +1,7 @@ -= render 'submenu' -= render 'sidebar' += render 'platforms/base/submenu' += render 'platforms/base/sidebar' -= form_for :build, :url => build_all_platform_path(@platform), :html => { :class => 'form mass_build', :method => :post } do |f| += form_for :build, :url => platform_mass_builds_path(@platform), :html => { :class => 'form mass_build', :method => :post } do |f| %section.left %h3= t("layout.mass_builds.repositories") - @platform.repositories.each do |rep| @@ -32,6 +32,7 @@ %th.lpadding16= t('activerecord.attributes.mass_build.name') %th.lpadding16= t("layout.mass_builds.statuses") %th.lpadding16= t("layout.mass_builds.failed_builds_list") + %th.lpadding16= t("layout.mass_builds.cancel_mass_build") %th.lpadding16= t("layout.mass_builds.extended_data") - @mass_builds.each do |mass_build| %tr @@ -39,16 +40,20 @@ %td= link_to mass_build.name, build_lists_path(:filter => {:mass_build_id => mass_build.id}) %td.min_width_120 - MassBuild::COUNT_STATUSES.each do |status| - = link_to t("layout.build_lists.statuses.#{status}") + ": ", build_lists_path(:filter => {:status => status, :mass_build_id => mass_build.id}) + = link_to t("layout.build_lists.statuses.#{status}") + ": ", build_lists_path(:filter => {:mass_build_id => mass_build.id, :ownership => 'index'}.merge(status != :build_lists ? {:status => BuildList.status_by_human(status)} : {})) = mass_build.read_attribute "#{status}_count" .both - %td= link_to t("layout.mass_builds.failed_builds_list"), failed_builds_list_platforms_path(:mass_build_id => mass_build.id), :target => "_blank" + %td= link_to t("layout.mass_builds.failed_builds_list"), failed_builds_list_platform_mass_build_path(@platform, mass_build.id), :target => "_blank" if can?(:failed_builds_list, mass_build) + %td= link_to image_tag('x.png'), cancel_platform_mass_build_path(@platform, mass_build.id), :method => :post, :confirm => t("layout.mass_builds.cancel_confirm") if can?(:cancel, mass_build) %td %a.toggle_btn{:href => "#toggle_#{ mass_build.id }", :'data-target' => "#toggle_#{ mass_build.id }"}= t("layout.mass_builds.extended_data") .toggle{:id => "toggle_#{ mass_build.id }"} = t('activerecord.attributes.mass_build.arch_names') + ": " = mass_build.arch_names .both + = t('activerecord.attributes.mass_build.rep_names') + ": " + = mass_build.rep_names + .both = t('activerecord.attributes.mass_build.user') + ": " = link_to mass_build.user.fullname, mass_build.user .both diff --git a/app/views/platforms/repositories/_proj_ajax.js.erb b/app/views/platforms/repositories/_proj_ajax.js.erb index 9fd8431d4..3058ffbb2 100644 --- a/app/views/platforms/repositories/_proj_ajax.js.erb +++ b/app/views/platforms/repositories/_proj_ajax.js.erb @@ -5,8 +5,8 @@ "aaData": [ <% @projects.each do |project| %> [ - "<%=j link_to("#{project.owner.respond_to?(:uname) ? project.owner.uname : project.owner.name} / #{project.name}", project) %>", - "<%= truncate(project.description || '', :length => 60).gsub("\n", ' ').gsub("\r", ' ') %>", + "<%=j link_to(project.name_with_owner, project) %>", + "<%= truncate(project.description || '', :length => 60).gsub(/\n|\r|\t/, ' ') %>", "<%=j link_to t("layout.add"), url_for(:controller => :repositories, :action => :add_project, :project_id => project.id) %>" ]<%= project == @projects.last ? '' : ',' %> <% end %> diff --git a/app/views/platforms/repositories/_project.js.erb b/app/views/platforms/repositories/_project.js.erb index fbff4878d..2dd623397 100644 --- a/app/views/platforms/repositories/_project.js.erb +++ b/app/views/platforms/repositories/_project.js.erb @@ -13,7 +13,7 @@ j(link_to("#{project.owner.respond_to?(:uname) ? project.owner.uname : project.owner.name} / #{project.name}", project)) + "").html_safe %>", - "<%= truncate(project.description || '', :length => 60).gsub("\n", ' ').gsub("\r", ' ') %>", + "<%= truncate(project.description || '', :length => 60).gsub(/\n|\r|\t/, ' ') %>", "<%= if can? :remove_project, @repository j(link_to(' '.html_safe, diff --git a/app/views/projects/build_lists/_build_list.html.haml b/app/views/projects/build_lists/_build_list.html.haml index 9eb7b1380..4c6005c80 100644 --- a/app/views/projects/build_lists/_build_list.html.haml +++ b/app/views/projects/build_lists/_build_list.html.haml @@ -1,10 +1,8 @@ %tr{:id => "row#{build_list_counter}", :class => "#{build_list_status_color(build_list.status)}"} %td= link_to (build_list.bs_id.present? ? build_list.bs_id : t("layout.build_lists.bs_id_not_set")), build_list - %td= build_list.mass_build_id ? link_to(build_list.mass_build.name, mass_builds_platform_path(build_list.save_to_platform)) : "" %td= build_list.human_status - %td= link_to build_list.project.name, build_list.project - %td= link_to build_list.project_version, "#" + %td= link_to build_list.project.name_with_owner, build_list.project + %td= build_list_version_link(build_list) + %td= link_to build_list.save_to_platform.name, build_list.save_to_platform %td= build_list.arch.name %td= link_to build_list.user.try(:fullname), build_list.user - %td= link_to image_tag('x.png', :class => 'delete-row', :id => "delete-row#{build_list_counter}"), cancel_build_list_path(build_list), :method => :put, :confirm => t('layout.confirm') if can?(:cancel, build_list) - %td= build_list.updated_at diff --git a/app/views/projects/build_lists/_filter.html.haml b/app/views/projects/build_lists/_filter.html.haml index 67ea7082c..0d7d38eaa 100644 --- a/app/views/projects/build_lists/_filter.html.haml +++ b/app/views/projects/build_lists/_filter.html.haml @@ -37,18 +37,19 @@ %br = f.submit t("layout.search.header") .block - %h3.small= t("activerecord.attributes.build_list.mass_build") - .lineForm.aside= f.select :mass_build_id, options_from_collection_for_select( MassBuild.all, :id, :name, @filter.mass_build_id ), {:include_blank => true} %h3.small= t("activerecord.attributes.build_list.status") .lineForm.aside= f.select :status, BuildList::STATUSES.collect{|status| [BuildList.human_status(status), status]}, {:include_blank => true, :selected => @filter.status}, {:class => 'sel80 aside', :id => 'status', :tabindex => 2} - %h3.small= t("activerecord.attributes.build_list.is_circle") - .lineForm.aside= f.select :is_circle, [[t("layout.yes_"), 1], [t("layout.no_"), 0]], {:include_blank => true, :selected => @filter.is_circle.present? ? (@filter.is_circle ? "1" : "0") : nil}, {:class => 'sel80 aside', :id => 'recurrent', :tabindex => 2} + %h3.small= t("activerecord.models.platform") + .lineForm.aside= f.select :platform_id, Platform.main.collect{|pl| [pl.name, pl.id]}, {:include_blank => true, :selected => @filter.platform_id}, {:class => 'sel80 aside', :id => 'platform', :tabindex => 2} + %h3.small= t("activerecord.attributes.build_list.mass_build") + .lineForm.aside= f.select :mass_build_id, options_from_collection_for_select( MassBuild.all, :id, :name, @filter.mass_build_id ), {:include_blank => true} %h3.small= t("activerecord.attributes.build_list.arch") .lineForm.aside= f.select :arch_id, Arch.recent.collect{|arch| [arch.name, arch.id]}, {:include_blank => true, :selected => @filter.arch_id}, {:class => 'sel80 aside', :id => 'architecture', :tabindex => 2} - %h3.small= t("layout.build_lists.created_at_start") - .date_select= f.date_select(:created_at_start, :include_blank => true, :selected => @filter.created_at_start) - %h3.small= t("layout.build_lists.created_at_end") - .date_select= f.date_select(:created_at_end, :include_blank => true, :selected => @filter.created_at_end) + -# TODO [BuildList#created_at filters] Uncomment here and in BuildList::Filter to return filters + %h3.small= t("layout.build_lists.created_at_start") + .date_select= f.date_select(:created_at_start, :include_blank => true, :selected => @filter.created_at_start) + %h3.small= t("layout.build_lists.created_at_end") + .date_select= f.date_select(:created_at_end, :include_blank => true, :selected => @filter.created_at_end) %h3.small= t("layout.build_lists.updated_at_start") .date_select= f.date_select(:updated_at_start, :include_blank => true, :selected => @filter.updated_at_start) %h3.small= t("layout.build_lists.updated_at_end") diff --git a/app/views/projects/build_lists/index.html.haml b/app/views/projects/build_lists/index.html.haml index 37cb2c2ea..67d89fc2b 100644 --- a/app/views/projects/build_lists/index.html.haml +++ b/app/views/projects/build_lists/index.html.haml @@ -4,14 +4,12 @@ %thead %tr %th.lpadding16= t("activerecord.attributes.build_list.bs_id") - %th.lpadding16= t('activerecord.attributes.build_list.mass_build_id') %th.lpadding16= t("activerecord.attributes.build_list.status") %th.lpadding16= t("activerecord.attributes.build_list.project") %th.lpadding16= t("activerecord.attributes.build_list.project_version") + %th.lpadding16= t("activerecord.attributes.build_list.save_to_platform") %th.lpadding16= t("activerecord.attributes.build_list.arch") %th.lpadding16= t("activerecord.attributes.build_list.user") - %th= t("layout.build_lists.action") - %th.lpadding16= t("activerecord.attributes.build_list.updated_at") %tbody= render :partial => 'projects/build_lists/build_list', :collection => @build_lists .both diff --git a/app/views/projects/build_lists/show.html.haml b/app/views/projects/build_lists/show.html.haml index 4e4f68ad7..f061efcf3 100644 --- a/app/views/projects/build_lists/show.html.haml +++ b/app/views/projects/build_lists/show.html.haml @@ -14,6 +14,10 @@ - container_url = "http://#{request.host_with_port}/downloads#{@build_list.container_path}" = link_to container_url, container_url .both + + .leftlist= t("activerecord.attributes.build_list.bs_id") + .rightlist= @build_list.bs_id.present? ? @build_list.bs_id : t("layout.build_lists.bs_id_not_set") + .both .leftlist= t("activerecord.attributes.build_list.user") .rightlist = link_to @build_list.user.try(:fullname), @build_list.user @@ -32,7 +36,7 @@ .leftlist= t("activerecord.attributes.build_list.update_type") .rightlist - if @build_list.can_publish? and can?(:publish, @build_list) - = f.select :update_type, options_for_select(BuildList::RELEASE_UPDATE_TYPES, @build_list.update_type) + = f.select :update_type, options_for_select(build_list_classified_update_types, @build_list.update_type) - else = @build_list.update_type .both @@ -43,7 +47,7 @@ .rightlist= t("layout.#{@build_list.auto_publish}_") .both .leftlist= t("activerecord.attributes.build_list.project_version") - .rightlist= @build_list.project_version + .rightlist= build_list_version_link(@build_list, true) .both .leftlist= t("activerecord.attributes.build_list.arch") .rightlist= @build_list.arch.name @@ -55,6 +59,12 @@ .rightlist= t("layout.#{@build_list.is_circle?}_") .both + - if @build_list.mass_build_id.present? + .leftlist= t("activerecord.attributes.mass_build_id") + .rightlist= link_to @build_list.mass_build.name, platform_mass_builds_path(@build_list.save_to_platform) + .both + + - if @build_list.advisory.present? .leftlist= t("layout.build_lists.attached_advisory") .rightlist= link_to @build_list.advisory.advisory_id, advisory_path(@build_list.advisory) @@ -71,12 +81,29 @@ = "#{@build_list.human_current_duration} / #{@build_list.project.human_average_build_time}" .both + - if @build_list.can_cancel? and can?(:cancel, @build_list) + = link_to t("layout.build_lists.cancel"), cancel_build_list_path(@build_list), + :method => :put, :confirm => t("layout.confirm"), :class => 'button' + - if @build_list.can_publish? and @build_list.save_to_platform.released and @build_list.advisory.nil? #advisory_block .leftlist= label_tag :attach_advisory, t("layout.build_lists.attached_advisory") - .rightlist= select_tag :attach_advisory, advisories_select_options(@advisories) + .rightlist + = select_tag :attach_advisory, advisories_select_options(@advisories) + %p.hint_text= t("layout.advisories.publication_info", :update_types => BuildList::RELEASE_UPDATE_TYPES.join(', ')) .both + #advisory_search_block + %h3= t("layout.advisories.search_by_id") + .leftlist= label_tag :advisory_search, t("layout.advisories.search_hint") + .rightlist + %input#advisory_search{:type => 'text'} + %p.hint_text= t("layout.advisories.advisory_id_info", :advisory_format => advisory_id_for_hint) + .both + - %w(advisory_not_found server_error continue_input).each do |el| + .info{:class => el} + %p= t("layout.advisories.banners.#{el}") + #new_advisory_form = f.fields_for @build_list.build_advisory do |f| = render :partial => 'advisories/form', :locals => {:f => f} @@ -91,11 +118,8 @@ .leftlist= t("activerecord.attributes.advisory.references") .rightlist.refs   .both - :javascript $(function() { - Rosa.bootstrapedData.advisories = #{ render 'advisories/advisories.json.jbuilder', - :advisories => @advisories }; var r = new Rosa.Routers.BuildListsAdvisoriesRouter(); }); @@ -107,7 +131,6 @@ - if @item_groups.blank? %h4.nomargin= t("layout.build_lists.no_items_data") - @item_groups.each_with_index do |group, level| - -#%h4.nomargin= "#{group} ##{level}" - group.each do |item| %h4.nomargin= "#{item.name} ##{level}" %table.tablesorter.width565{:cellpadding => "0", :cellspacing => "0"} diff --git a/app/views/projects/projects/_project.html.haml b/app/views/projects/projects/_project.html.haml index f5514643f..a59667f52 100644 --- a/app/views/projects/projects/_project.html.haml +++ b/app/views/projects/projects/_project.html.haml @@ -2,10 +2,7 @@ %td = link_to project do .table-sort-left= image_tag visibility_icon(project.visibility) - .table-sort-right - = link_to project.owner.uname, project.owner.class == User ? user_path(project.owner) : group_path(project.owner) #{project.owner.uname} / #{project.name} - #{ ' / ' } - = link_to project.name, project_path(project) + .table-sort-right= link_to project.name_with_owner, project_path(project) %td.td2= project.description - alone_member = alone_member? project %td diff --git a/app/views/projects/projects/_project.json.jbuilder b/app/views/projects/projects/_project.json.jbuilder index 4052c1171..cd11bcb2a 100644 --- a/app/views/projects/projects/_project.json.jbuilder +++ b/app/views/projects/projects/_project.json.jbuilder @@ -1,7 +1,7 @@ json.project do |proj| proj.visibility project.visibility.to_s - proj.name project.name + proj.name project.name_with_owner proj.description project.description proj.link project_path(project) diff --git a/app/views/projects/projects/index.html.haml b/app/views/projects/projects/index.html.haml index ed1a4777c..01c48b8bd 100644 --- a/app/views/projects/projects/index.html.haml +++ b/app/views/projects/projects/index.html.haml @@ -45,11 +45,10 @@ var image = '' + project.visibility + ''; - var owner = '' + project.owner.name + ''; var project = '' + project.name + ''; return '
' + image + "
\n" + - '
' + owner + ' / ' + project + '
'; + '
' + project + '
'; } var thirdColumn = function(row) { diff --git a/config/locales/layout.en.yml b/config/locales/layout.en.yml index 3e1625958..d5ff40b31 100644 --- a/config/locales/layout.en.yml +++ b/config/locales/layout.en.yml @@ -14,6 +14,7 @@ en: read_access: read-only by: by + clear: Clear remove: Remove diff --git a/config/locales/layout.ru.yml b/config/locales/layout.ru.yml index ba70c945d..a9782afcd 100644 --- a/config/locales/layout.ru.yml +++ b/config/locales/layout.ru.yml @@ -14,6 +14,7 @@ ru: read_access: только чтение by: '' + clear: Очистить remove: Убрать diff --git a/config/locales/layout/search.en.yml b/config/locales/layout/search.en.yml index 91a1a7a63..177ee6cba 100644 --- a/config/locales/layout/search.en.yml +++ b/config/locales/layout/search.en.yml @@ -4,6 +4,7 @@ en: header: Search advanced: Advanced search all: Show All + no_results: Nothing found for "%{query}". types: all: All projects: Projects diff --git a/config/locales/layout/search.ru.yml b/config/locales/layout/search.ru.yml index 2850bd5bd..53df60bcd 100644 --- a/config/locales/layout/search.ru.yml +++ b/config/locales/layout/search.ru.yml @@ -4,6 +4,7 @@ ru: header: Поиск advanced: Расширенный поиск all: Показать все + no_results: По запросу "%{query}" ничего не найдено. types: all: Все projects: Проекты diff --git a/config/locales/models/advisory.en.yml b/config/locales/models/advisory.en.yml index dd9ee79b5..fe3e1a16f 100644 --- a/config/locales/models/advisory.en.yml +++ b/config/locales/models/advisory.en.yml @@ -1,13 +1,27 @@ en: layout: advisories: + atom_header: Advisories list_header: Advisories form_header: New advisory project_name: Project + project_names: Projects + build_lists: Build Lists affected_versions: Affected versions + affected_in: Affected in ref_comment: Add links one by row no_: No new: New + existing: Existing + search_by_id: Search advisory by it's ID + search_hint: Paste full AdvisoryID into text field or enter there its uniq part. + advisory_id_info: AdvisoryID is a string %{advisory_format}, where 'XXXX' (at least 4 symbols) is a uniq part. + publication_info: Advisory might be applied only to Build Lists with [%{update_types}] update types. + + banners: + advisory_not_found: Couldn't find advisory with given ID for this type of Build List. + server_error: Server problem. Please try again later. + continue_input: Continue input while needed Advisory appears in preview. flash: advisories: diff --git a/config/locales/models/advisory.ru.yml b/config/locales/models/advisory.ru.yml index f0914a12d..bf153d7a9 100644 --- a/config/locales/models/advisory.ru.yml +++ b/config/locales/models/advisory.ru.yml @@ -1,13 +1,27 @@ ru: layout: advisories: + atom_title: Бюллетени list_header: Бюллетени form_header: Новый бюллетень project_name: Проект + project_names: Проекты + build_lists: Сборочные листы affected_versions: Применен в версиях + affected_in: Применен в ref_comment: Вставляйте ссылки по одной на строку no_: Нет new: Новый + existing: Существующий + search_by_id: Искать бюллетень по его ID + search_hint: Скопируйте в поле ввода полный AdvisoryID или введите его уникальную часть + advisory_id_info: AdvisoryID имеет формат %{advisory_format}, где 'XXXX' (минимум 4 символа) - это уникальная часть. + publication_info: Бюллетень может быть присоединен только к сборочному листу с типами обновления %{update_types}. + + banners: + advisory_not_found: Не удалось найти запрашиваемый бюллетень для сборочного листа этого типа. + server_error: Произошла ошибка сервера. Попробуйте позже. + continue_input: Продолжайте вводить ID до тех пор, пока не найдется нужный бюллетень. flash: advisories: diff --git a/config/locales/models/build_list.en.yml b/config/locales/models/build_list.en.yml index 139ae436b..cdcf7530e 100644 --- a/config/locales/models/build_list.en.yml +++ b/config/locales/models/build_list.en.yml @@ -18,8 +18,8 @@ en: additional_repos: Additional repositories include_repos: Included repositories created_at: Created on - save_to_platform: Repository for package storage - build_for_platform: Platform + save_to_platform: Platform + build_for_platform: Build for platform update_type: Update type build_requires: Build with all the required packages auto_publish: Automated publising @@ -58,6 +58,7 @@ en: packages_header: Container data no_items_data: No data show: Show + cancel: Cancel build cancel_success: 'Build canceled' cancel_fail: 'Errors during build cancelation!' publish_success: 'Build is queued for publishing' diff --git a/config/locales/models/build_list.ru.yml b/config/locales/models/build_list.ru.yml index 910292188..5c5820a9d 100644 --- a/config/locales/models/build_list.ru.yml +++ b/config/locales/models/build_list.ru.yml @@ -18,8 +18,8 @@ ru: additional_repos: Дополнительные репозитории include_repos: Подключаемые репозитории created_at: Создан - save_to_platform: Репозиторий для сохранения пакетов - build_for_platform: Платформа + save_to_platform: Платформа + build_for_platform: Собрано для платформы update_type: Критичность обновления build_requires: Пересборка с зависимостями auto_publish: Автоматическая публикация @@ -57,6 +57,7 @@ ru: packages_header: Данные о контейнере no_items_data: Данных нет show: Просмотр + cancel: Отменить сборку cancel_success: 'Сборка отменена.' cancel_fail: 'При отмене сборки произошла ошибка!' publish_success: 'Сборка поставлена в очередь на публикацию.' diff --git a/config/locales/models/mass_build.en.yml b/config/locales/models/mass_build.en.yml index 508bc8507..734172bcc 100644 --- a/config/locales/models/mass_build.en.yml +++ b/config/locales/models/mass_build.en.yml @@ -5,6 +5,8 @@ en: extended_data: Extended data failed_builds_list: Failed Builds List statuses: Statuses + cancel_mass_build: Cancel + cancel_confirm: Are you sure you want to cancel mass build? activerecord: models: mass_build: Mass Build @@ -18,3 +20,4 @@ en: user: User auto_publish: Auto Publish repositories: Repositories + rep_names: Repositories diff --git a/config/locales/models/mass_build.ru.yml b/config/locales/models/mass_build.ru.yml index b5d0fee6f..5e7cc7c38 100644 --- a/config/locales/models/mass_build.ru.yml +++ b/config/locales/models/mass_build.ru.yml @@ -5,6 +5,8 @@ ru: extended_data: Параметры задания failed_builds_list: Список ошибок сборок statuses: Статусы + cancel_mass_build: Отмена + cancel_confirm: Вы уверены, что хотите отменить массовую сборку? activerecord: models: mass_build: Массовая Сборка @@ -18,3 +20,4 @@ ru: user: Пользователь auto_publish: Авто Публикация repositories: Репозитории + rep_names: Репозитории diff --git a/config/locales/models/platform.en.yml b/config/locales/models/platform.en.yml index af51cd808..cf44b6550 100644 --- a/config/locales/models/platform.en.yml +++ b/config/locales/models/platform.en.yml @@ -59,6 +59,7 @@ en: destroyed: Platform deleted build_all_success: All project build in progress build_all_error: Mass build failed + cancel_mass_build: Mass build canceled clone_success: Cloned successfully members: already_added: "%{name} is already a member of platform" diff --git a/config/locales/models/platform.ru.yml b/config/locales/models/platform.ru.yml index 387b2c0f9..6c04bbca8 100644 --- a/config/locales/models/platform.ru.yml +++ b/config/locales/models/platform.ru.yml @@ -59,6 +59,7 @@ ru: destroyed: Платформа успешно удалена build_all_success: Все проекты успешно отправлены на сборку build_all_error: Сборка не удалась! + cancel_mass_build: Массовая сборка отменена clone_success: Клонирование успешно members: already_added: "%{name} уже является участником платформы" diff --git a/config/routes.rb b/config/routes.rb index a35552d04..3d9ecac83 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -38,7 +38,9 @@ Rosa::Application.routes.draw do end end - resources :advisories, :only => [:index, :show] + resources :advisories, :only => [:index, :show, :search] do + get :search, :on => :collection + end scope :module => 'platforms' do resources :platforms do @@ -51,13 +53,16 @@ Rosa::Application.routes.draw do delete :remove_member post :add_member post :make_clone - post :build_all - get :mass_builds get :advisories end - collection do - get :failed_builds_list + + resources :mass_builds, :only => [:create, :index] do + member do + get :failed_builds_list + post :cancel + end end + get :autocomplete_user_uname, :on => :collection resources :repositories do member do diff --git a/db/migrate/20120607153342_add_many_projects_to_advisories.rb b/db/migrate/20120607153342_add_many_projects_to_advisories.rb new file mode 100644 index 000000000..80ed9ad62 --- /dev/null +++ b/db/migrate/20120607153342_add_many_projects_to_advisories.rb @@ -0,0 +1,40 @@ +class AddManyProjectsToAdvisories < ActiveRecord::Migration + def up + create_table :advisories_projects, :id => false do |t| + t.integer :advisory_id + t.integer :project_id + end + add_index :advisories_projects, :advisory_id + add_index :advisories_projects, :project_id + add_index :advisories_projects, [:advisory_id, :project_id], :name => :advisory_project_index, :unique => true + + Advisory.find_in_batches do |b| + b.each do |advisory| + advisory.projects << Project.find(advisory.project_id) + advisory.save + end + end + + change_table :advisories do |t| + t.remove :project_id + end + end + + def down + change_table :advisories do |t| + t.integer :project_id + end + + Advisory.find_in_batches do |b| + b.each do |advisory| + advisory.project_id = advisory.projects.first.id + advisory.save + end + end + + remove_index :advisories_projects, :column => :advisory_id + remove_index :advisories_projects, :column => :project_id + remove_index :advisories_projects, :name => :advisory_project_index + drop_table :advisories_projects + end +end diff --git a/db/migrate/20120628142723_add_rep_names_to_mass_builds.rb b/db/migrate/20120628142723_add_rep_names_to_mass_builds.rb new file mode 100644 index 000000000..12fdc158d --- /dev/null +++ b/db/migrate/20120628142723_add_rep_names_to_mass_builds.rb @@ -0,0 +1,5 @@ +class AddRepNamesToMassBuilds < ActiveRecord::Migration + def change + add_column :mass_builds, :rep_names, :string + end +end diff --git a/db/migrate/20120629134216_add_stop_build_to_mass_builds.rb b/db/migrate/20120629134216_add_stop_build_to_mass_builds.rb new file mode 100644 index 000000000..1e92dbc34 --- /dev/null +++ b/db/migrate/20120629134216_add_stop_build_to_mass_builds.rb @@ -0,0 +1,5 @@ +class AddStopBuildToMassBuilds < ActiveRecord::Migration + def change + add_column :mass_builds, :stop_build, :boolean, :null => false, :default => false + end +end diff --git a/db/migrate/20120703101719_add_null_false_to_mass_builds_counters.rb b/db/migrate/20120703101719_add_null_false_to_mass_builds_counters.rb new file mode 100644 index 000000000..6ae37a904 --- /dev/null +++ b/db/migrate/20120703101719_add_null_false_to_mass_builds_counters.rb @@ -0,0 +1,10 @@ +class AddNullFalseToMassBuildsCounters < ActiveRecord::Migration + def change + change_column :mass_builds, :build_lists_count, :integer, :default => 0, :null => false + change_column :mass_builds, :build_published_count, :integer, :default => 0, :null => false + change_column :mass_builds, :build_pending_count, :integer, :default => 0, :null => false + change_column :mass_builds, :build_started_count, :integer, :default => 0, :null => false + change_column :mass_builds, :build_publish_count, :integer, :default => 0, :null => false + change_column :mass_builds, :build_error_count, :integer, :default => 0, :null => false + end +end diff --git a/db/schema.rb b/db/schema.rb index b21ec3a30..cafd05648 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20120628165702) do +ActiveRecord::Schema.define(:version => 20120703101719) do create_table "activity_feeds", :force => true do |t| t.integer "user_id", :null => false @@ -23,7 +23,6 @@ ActiveRecord::Schema.define(:version => 20120628165702) do create_table "advisories", :force => true do |t| t.string "advisory_id" - t.integer "project_id" t.text "description", :default => "" t.text "references", :default => "" t.text "update_type", :default => "" @@ -32,7 +31,6 @@ ActiveRecord::Schema.define(:version => 20120628165702) do end add_index "advisories", ["advisory_id"], :name => "index_advisories_on_advisory_id", :unique => true - add_index "advisories", ["project_id"], :name => "index_advisories_on_project_id" add_index "advisories", ["update_type"], :name => "index_advisories_on_update_type" create_table "advisories_platforms", :id => false, :force => true do |t| @@ -44,6 +42,15 @@ ActiveRecord::Schema.define(:version => 20120628165702) do add_index "advisories_platforms", ["advisory_id", "platform_id"], :name => "advisory_platform_index", :unique => true add_index "advisories_platforms", ["platform_id"], :name => "index_advisories_platforms_on_platform_id" + create_table "advisories_projects", :id => false, :force => true do |t| + t.integer "advisory_id" + t.integer "project_id" + end + + add_index "advisories_projects", ["advisory_id"], :name => "index_advisories_projects_on_advisory_id" + add_index "advisories_projects", ["advisory_id", "project_id"], :name => "advisory_project_index", :unique => true + add_index "advisories_projects", ["project_id"], :name => "index_advisories_projects_on_project_id" + create_table "arches", :force => true do |t| t.string "name", :null => false t.datetime "created_at", :null => false @@ -204,12 +211,14 @@ ActiveRecord::Schema.define(:version => 20120628165702) do t.string "arch_names" t.integer "user_id" t.boolean "auto_publish", :default => false, :null => false - t.integer "build_lists_count", :default => 0 - t.integer "build_published_count", :default => 0 - t.integer "build_pending_count", :default => 0 - t.integer "build_started_count", :default => 0 - t.integer "build_publish_count", :default => 0 - t.integer "build_error_count", :default => 0 + t.integer "build_lists_count", :default => 0, :null => false + t.integer "build_published_count", :default => 0, :null => false + t.integer "build_pending_count", :default => 0, :null => false + t.integer "build_started_count", :default => 0, :null => false + t.integer "build_publish_count", :default => 0, :null => false + t.integer "build_error_count", :default => 0, :null => false + t.string "rep_names" + t.boolean "stop_build", :default => false, :null => false end create_table "platforms", :force => true do |t| diff --git a/lib/modules/models/owner.rb b/lib/modules/models/owner.rb index b92a067be..182682e18 100644 --- a/lib/modules/models/owner.rb +++ b/lib/modules/models/owner.rb @@ -10,6 +10,14 @@ module Modules module ClassMethods end + + module InstanceMethods + + def name_with_owner + "#{owner.respond_to?(:uname) ? owner.uname : owner.name}/#{self.name}" + end + + end end end end diff --git a/spec/controllers/platforms/mass_builds_controller_spec.rb b/spec/controllers/platforms/mass_builds_controller_spec.rb new file mode 100644 index 000000000..6a85981f9 --- /dev/null +++ b/spec/controllers/platforms/mass_builds_controller_spec.rb @@ -0,0 +1,151 @@ +# -*- encoding : utf-8 -*- +require 'spec_helper' + +shared_examples_for 'mass_build platform owner' do + it 'should be able to perform index action' do + get :index, :platform_id => @platform + response.should render_template(:index) + end + + it 'should be able to perform create action' do + post :create, @create_params + response.should redirect_to(platform_mass_builds_path(@platform)) + end + + it 'should be able to perform cancel action' do + post :cancel, :platform_id => @platform, :id => @mass_build + response.should redirect_to(platform_mass_builds_path(@platform)) + end + + it 'should change stop_build on cancel' do + post :cancel, :platform_id => @platform, :id => @mass_build + @mass_build.reload.stop_build.should == true + end + + it 'should not be able to perform cancel action if stop_build is true' do + @mass_build.update_attribute(:stop_build, true) + post :cancel, :platform_id => @platform, :id => @mass_build + response.should redirect_to(forbidden_path) + end + + it 'should change objects count on create success' do + lambda { post :create, @create_params }.should change{ MassBuild.count }.by(1) + end + + context 'for personal platform' do + before(:each) do + Platform.update_all("platform_type = 'personal'") + end + + [:cancel, :failed_builds_list, :create].each do |action| + it "should not be able to perform #{ action } action" do + get action, :platform_id => @platform, :id => @mass_build.id + response.should redirect_to(forbidden_path) + end + end + end +end + +shared_examples_for 'mass_build platform reader' do + [:index, :create].each do |action| + it "should not be able to perform #{ action } action" do + get action, :platform_id => @platform + response.should redirect_to(forbidden_path) + end + end + + [:cancel, :failed_builds_list].each do |action| + it "should not be able to perform #{ action } action" do + get action, :platform_id => @platform, :id => @mass_build.id + response.should redirect_to(forbidden_path) + end + end + + it 'should not change objects count on create success' do + lambda { post :create, @create_params }.should change{ MassBuild.count }.by(0) + end + + it 'should not change stop_build on cancel' do + post :cancel, :platform_id => @platform, :id => @mass_build + @mass_build.reload.stop_build.should == false + end +end + + +describe Platforms::MassBuildsController do + before(:each) do + stub_symlink_methods + + @platform = FactoryGirl.create(:platform) + @repository = FactoryGirl.create(:repository, :platform => @platform) + @personal_platform = FactoryGirl.create(:platform, :platform_type => 'personal') + @user = FactoryGirl.create(:user) + @create_params = { + :platform_id => @platform, + :repositories => [@platform.repositories.first.id], + :arches => [Arch.first.id], + :auto_publish => true + } + + @mass_build = FactoryGirl.create(:mass_build, :platform => @platform, :user => @user) + end + + context 'for guest' do + [:index, :create, :cancel, :failed_builds_list].each do |action| + it "should not be able to perform #{ action } action" do + get action, :platform_id => @platform + response.should redirect_to(new_user_session_path) + end + end + + it 'should not change objects count on create success' do + lambda { post :create, @create_params }.should change{ MassBuild.count }.by(0) + end + + it 'should not change stop_build on cancel' do + post :cancel, :platform_id => @platform, :id => @mass_build + @mass_build.reload.stop_build.should == false + end + end + + context 'for global admin' do + before(:each) do + @admin = FactoryGirl.create(:admin) + @user = FactoryGirl.create(:user) + set_session_for(@admin) + end + + it_should_behave_like 'mass_build platform owner' + end + + context 'for owner user' do + before(:each) do + @user = FactoryGirl.create(:user) + set_session_for(@user) + @platform.update_attribute(:owner, @user) + end + + it_should_behave_like 'mass_build platform owner' + end + + context 'for admin user' do + before(:each) do + @user = FactoryGirl.create(:user) + set_session_for(@user) + @platform.relations.create!(:actor_type => 'User', :actor_id => @user.id, :role => 'admin') + end + + it_should_behave_like 'mass_build platform owner' + end + + context 'for reader user' do + before(:each) do + @user = FactoryGirl.create(:user) + set_session_for(@user) + @platform.relations.create!(:actor_type => 'User', :actor_id => @user.id, :role => 'reader') + end + + it_should_behave_like 'mass_build platform reader' + end +end + diff --git a/spec/factories/mass_build.rb b/spec/factories/mass_build.rb new file mode 100644 index 000000000..e9afc907a --- /dev/null +++ b/spec/factories/mass_build.rb @@ -0,0 +1,13 @@ +# -*- encoding : utf-8 -*- +FactoryGirl.define do + factory :mass_build do + association :platform + #name FactoryGirl.generate(:name) + association :user + repositories { |mb| [ mb.platform.repositories.first.id ] } + arches { [ Arch.first.id ] } + auto_publish true + stop_build false + end +end + diff --git a/vendor/assets/images/chosen/chosen-sprite.png b/vendor/assets/images/chosen/chosen-sprite.png new file mode 100755 index 000000000..113dc9885 Binary files /dev/null and b/vendor/assets/images/chosen/chosen-sprite.png differ diff --git a/app/assets/javascripts/lib/bootstrap-popover.js b/vendor/assets/javascripts/bootstrap-popover.js similarity index 100% rename from app/assets/javascripts/lib/bootstrap-popover.js rename to vendor/assets/javascripts/bootstrap-popover.js diff --git a/app/assets/javascripts/lib/bootstrap-tooltip.js b/vendor/assets/javascripts/bootstrap-tooltip.js similarity index 100% rename from app/assets/javascripts/lib/bootstrap-tooltip.js rename to vendor/assets/javascripts/bootstrap-tooltip.js diff --git a/vendor/assets/javascripts/chosen.jquery.js b/vendor/assets/javascripts/chosen.jquery.js new file mode 100755 index 000000000..8b28bc045 --- /dev/null +++ b/vendor/assets/javascripts/chosen.jquery.js @@ -0,0 +1,1003 @@ +// Chosen, a Select Box Enhancer for jQuery and Protoype +// by Patrick Filler for Harvest, http://getharvest.com +// +// Version 0.9.8 +// Full source at https://github.com/harvesthq/chosen +// Copyright (c) 2011 Harvest http://getharvest.com + +// MIT License, https://github.com/harvesthq/chosen/blob/master/LICENSE.md +// This file is generated by `cake build`, do not edit it by hand. +(function() { + var SelectParser; + + SelectParser = (function() { + + function SelectParser() { + this.options_index = 0; + this.parsed = []; + } + + SelectParser.prototype.add_node = function(child) { + if (child.nodeName === "OPTGROUP") { + return this.add_group(child); + } else { + return this.add_option(child); + } + }; + + SelectParser.prototype.add_group = function(group) { + var group_position, option, _i, _len, _ref, _results; + group_position = this.parsed.length; + this.parsed.push({ + array_index: group_position, + group: true, + label: group.label, + children: 0, + disabled: group.disabled + }); + _ref = group.childNodes; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + option = _ref[_i]; + _results.push(this.add_option(option, group_position, group.disabled)); + } + return _results; + }; + + SelectParser.prototype.add_option = function(option, group_position, group_disabled) { + if (option.nodeName === "OPTION") { + if (option.text !== "") { + if (group_position != null) this.parsed[group_position].children += 1; + this.parsed.push({ + array_index: this.parsed.length, + options_index: this.options_index, + value: option.value, + text: option.text, + html: option.innerHTML, + selected: option.selected, + disabled: group_disabled === true ? group_disabled : option.disabled, + group_array_index: group_position, + classes: option.className, + style: option.style.cssText + }); + } else { + this.parsed.push({ + array_index: this.parsed.length, + options_index: this.options_index, + empty: true + }); + } + return this.options_index += 1; + } + }; + + return SelectParser; + + })(); + + SelectParser.select_to_array = function(select) { + var child, parser, _i, _len, _ref; + parser = new SelectParser(); + _ref = select.childNodes; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + child = _ref[_i]; + parser.add_node(child); + } + return parser.parsed; + }; + + this.SelectParser = SelectParser; + +}).call(this); + +/* +Chosen source: generate output using 'cake build' +Copyright (c) 2011 by Harvest +*/ + +(function() { + var AbstractChosen, root; + + root = this; + + AbstractChosen = (function() { + + function AbstractChosen(form_field, options) { + this.form_field = form_field; + this.options = options != null ? options : {}; + this.set_default_values(); + this.is_multiple = this.form_field.multiple; + this.set_default_text(); + this.setup(); + this.set_up_html(); + this.register_observers(); + this.finish_setup(); + } + + AbstractChosen.prototype.set_default_values = function() { + var _this = this; + this.click_test_action = function(evt) { + return _this.test_active_click(evt); + }; + this.activate_action = function(evt) { + return _this.activate_field(evt); + }; + this.active_field = false; + this.mouse_on_container = false; + this.results_showing = false; + this.result_highlighted = null; + this.result_single_selected = null; + this.allow_single_deselect = (this.options.allow_single_deselect != null) && (this.form_field.options[0] != null) && this.form_field.options[0].text === "" ? this.options.allow_single_deselect : false; + this.disable_search_threshold = this.options.disable_search_threshold || 0; + this.search_contains = this.options.search_contains || false; + this.choices = 0; + this.single_backstroke_delete = this.options.single_backstroke_delete || false; + return this.max_selected_options = this.options.max_selected_options || Infinity; + }; + + AbstractChosen.prototype.set_default_text = function() { + if (this.form_field.getAttribute("data-placeholder")) { + this.default_text = this.form_field.getAttribute("data-placeholder"); + } else if (this.is_multiple) { + this.default_text = this.options.placeholder_text_multiple || this.options.placeholder_text || "Select Some Options"; + } else { + this.default_text = this.options.placeholder_text_single || this.options.placeholder_text || "Select an Option"; + } + return this.results_none_found = this.form_field.getAttribute("data-no_results_text") || this.options.no_results_text || "No results match"; + }; + + AbstractChosen.prototype.mouse_enter = function() { + return this.mouse_on_container = true; + }; + + AbstractChosen.prototype.mouse_leave = function() { + return this.mouse_on_container = false; + }; + + AbstractChosen.prototype.input_focus = function(evt) { + var _this = this; + if (!this.active_field) { + return setTimeout((function() { + return _this.container_mousedown(); + }), 50); + } + }; + + AbstractChosen.prototype.input_blur = function(evt) { + var _this = this; + if (!this.mouse_on_container) { + this.active_field = false; + return setTimeout((function() { + return _this.blur_test(); + }), 100); + } + }; + + AbstractChosen.prototype.result_add_option = function(option) { + var classes, style; + if (!option.disabled) { + option.dom_id = this.container_id + "_o_" + option.array_index; + classes = option.selected && this.is_multiple ? [] : ["active-result"]; + if (option.selected) classes.push("result-selected"); + if (option.group_array_index != null) classes.push("group-option"); + if (option.classes !== "") classes.push(option.classes); + style = option.style.cssText !== "" ? " style=\"" + option.style + "\"" : ""; + return '
  • ' + option.html + '
  • '; + } else { + return ""; + } + }; + + AbstractChosen.prototype.results_update_field = function() { + if (!this.is_multiple) this.results_reset_cleanup(); + this.result_clear_highlight(); + this.result_single_selected = null; + return this.results_build(); + }; + + AbstractChosen.prototype.results_toggle = function() { + if (this.results_showing) { + return this.results_hide(); + } else { + return this.results_show(); + } + }; + + AbstractChosen.prototype.results_search = function(evt) { + if (this.results_showing) { + return this.winnow_results(); + } else { + return this.results_show(); + } + }; + + AbstractChosen.prototype.keyup_checker = function(evt) { + var stroke, _ref; + stroke = (_ref = evt.which) != null ? _ref : evt.keyCode; + this.search_field_scale(); + switch (stroke) { + case 8: + if (this.is_multiple && this.backstroke_length < 1 && this.choices > 0) { + return this.keydown_backstroke(); + } else if (!this.pending_backstroke) { + this.result_clear_highlight(); + return this.results_search(); + } + break; + case 13: + evt.preventDefault(); + if (this.results_showing) return this.result_select(evt); + break; + case 27: + if (this.results_showing) this.results_hide(); + return true; + case 9: + case 38: + case 40: + case 16: + case 91: + case 17: + break; + default: + return this.results_search(); + } + }; + + AbstractChosen.prototype.generate_field_id = function() { + var new_id; + new_id = this.generate_random_id(); + this.form_field.id = new_id; + return new_id; + }; + + AbstractChosen.prototype.generate_random_char = function() { + var chars, newchar, rand; + chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + rand = Math.floor(Math.random() * chars.length); + return newchar = chars.substring(rand, rand + 1); + }; + + return AbstractChosen; + + })(); + + root.AbstractChosen = AbstractChosen; + +}).call(this); + +/* +Chosen source: generate output using 'cake build' +Copyright (c) 2011 by Harvest +*/ + +(function() { + var $, Chosen, get_side_border_padding, root, + __hasProp = Object.prototype.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; }; + + root = this; + + $ = jQuery; + + $.fn.extend({ + chosen: function(options) { + if ($.browser.msie && ($.browser.version === "6.0" || $.browser.version === "7.0")) { + return this; + } + return this.each(function(input_field) { + var $this; + $this = $(this); + if (!$this.hasClass("chzn-done")) { + return $this.data('chosen', new Chosen(this, options)); + } + }); + } + }); + + Chosen = (function(_super) { + + __extends(Chosen, _super); + + function Chosen() { + Chosen.__super__.constructor.apply(this, arguments); + } + + Chosen.prototype.setup = function() { + this.form_field_jq = $(this.form_field); + this.current_value = this.form_field_jq.val(); + return this.is_rtl = this.form_field_jq.hasClass("chzn-rtl"); + }; + + Chosen.prototype.finish_setup = function() { + return this.form_field_jq.addClass("chzn-done"); + }; + + Chosen.prototype.set_up_html = function() { + var container_div, dd_top, dd_width, sf_width; + this.container_id = this.form_field.id.length ? this.form_field.id.replace(/[^\w]/g, '_') : this.generate_field_id(); + this.container_id += "_chzn"; + this.f_width = this.form_field_jq.outerWidth(); + container_div = $("
    ", { + id: this.container_id, + "class": "chzn-container" + (this.is_rtl ? ' chzn-rtl' : ''), + style: 'width: ' + this.f_width + 'px;' + }); + if (this.is_multiple) { + container_div.html('
      '); + } else { + container_div.html('' + this.default_text + '
        '); + } + this.form_field_jq.hide().after(container_div); + this.container = $('#' + this.container_id); + this.container.addClass("chzn-container-" + (this.is_multiple ? "multi" : "single")); + this.dropdown = this.container.find('div.chzn-drop').first(); + dd_top = this.container.height(); + dd_width = this.f_width - get_side_border_padding(this.dropdown); + this.dropdown.css({ + "width": dd_width + "px", + "top": dd_top + "px" + }); + this.search_field = this.container.find('input').first(); + this.search_results = this.container.find('ul.chzn-results').first(); + this.search_field_scale(); + this.search_no_results = this.container.find('li.no-results').first(); + if (this.is_multiple) { + this.search_choices = this.container.find('ul.chzn-choices').first(); + this.search_container = this.container.find('li.search-field').first(); + } else { + this.search_container = this.container.find('div.chzn-search').first(); + this.selected_item = this.container.find('.chzn-single').first(); + sf_width = dd_width - get_side_border_padding(this.search_container) - get_side_border_padding(this.search_field); + this.search_field.css({ + "width": sf_width + "px" + }); + } + this.results_build(); + this.set_tab_index(); + return this.form_field_jq.trigger("liszt:ready", { + chosen: this + }); + }; + + Chosen.prototype.register_observers = function() { + var _this = this; + this.container.mousedown(function(evt) { + return _this.container_mousedown(evt); + }); + this.container.mouseup(function(evt) { + return _this.container_mouseup(evt); + }); + this.container.mouseenter(function(evt) { + return _this.mouse_enter(evt); + }); + this.container.mouseleave(function(evt) { + return _this.mouse_leave(evt); + }); + this.search_results.mouseup(function(evt) { + return _this.search_results_mouseup(evt); + }); + this.search_results.mouseover(function(evt) { + return _this.search_results_mouseover(evt); + }); + this.search_results.mouseout(function(evt) { + return _this.search_results_mouseout(evt); + }); + this.form_field_jq.bind("liszt:updated", function(evt) { + return _this.results_update_field(evt); + }); + this.search_field.blur(function(evt) { + return _this.input_blur(evt); + }); + this.search_field.keyup(function(evt) { + return _this.keyup_checker(evt); + }); + this.search_field.keydown(function(evt) { + return _this.keydown_checker(evt); + }); + if (this.is_multiple) { + this.search_choices.click(function(evt) { + return _this.choices_click(evt); + }); + return this.search_field.focus(function(evt) { + return _this.input_focus(evt); + }); + } else { + return this.container.click(function(evt) { + return evt.preventDefault(); + }); + } + }; + + Chosen.prototype.search_field_disabled = function() { + this.is_disabled = this.form_field_jq[0].disabled; + if (this.is_disabled) { + this.container.addClass('chzn-disabled'); + this.search_field[0].disabled = true; + if (!this.is_multiple) { + this.selected_item.unbind("focus", this.activate_action); + } + return this.close_field(); + } else { + this.container.removeClass('chzn-disabled'); + this.search_field[0].disabled = false; + if (!this.is_multiple) { + return this.selected_item.bind("focus", this.activate_action); + } + } + }; + + Chosen.prototype.container_mousedown = function(evt) { + var target_closelink; + if (!this.is_disabled) { + target_closelink = evt != null ? ($(evt.target)).hasClass("search-choice-close") : false; + if (evt && evt.type === "mousedown" && !this.results_showing) { + evt.stopPropagation(); + } + if (!this.pending_destroy_click && !target_closelink) { + if (!this.active_field) { + if (this.is_multiple) this.search_field.val(""); + $(document).click(this.click_test_action); + this.results_show(); + } else if (!this.is_multiple && evt && (($(evt.target)[0] === this.selected_item[0]) || $(evt.target).parents("a.chzn-single").length)) { + evt.preventDefault(); + this.results_toggle(); + } + return this.activate_field(); + } else { + return this.pending_destroy_click = false; + } + } + }; + + Chosen.prototype.container_mouseup = function(evt) { + if (evt.target.nodeName === "ABBR" && !this.is_disabled) { + return this.results_reset(evt); + } + }; + + Chosen.prototype.blur_test = function(evt) { + if (!this.active_field && this.container.hasClass("chzn-container-active")) { + return this.close_field(); + } + }; + + Chosen.prototype.close_field = function() { + $(document).unbind("click", this.click_test_action); + if (!this.is_multiple) { + this.selected_item.attr("tabindex", this.search_field.attr("tabindex")); + this.search_field.attr("tabindex", -1); + } + this.active_field = false; + this.results_hide(); + this.container.removeClass("chzn-container-active"); + this.winnow_results_clear(); + this.clear_backstroke(); + this.show_search_field_default(); + return this.search_field_scale(); + }; + + Chosen.prototype.activate_field = function() { + if (!this.is_multiple && !this.active_field) { + this.search_field.attr("tabindex", this.selected_item.attr("tabindex")); + this.selected_item.attr("tabindex", -1); + } + this.container.addClass("chzn-container-active"); + this.active_field = true; + this.search_field.val(this.search_field.val()); + return this.search_field.focus(); + }; + + Chosen.prototype.test_active_click = function(evt) { + if ($(evt.target).parents('#' + this.container_id).length) { + return this.active_field = true; + } else { + return this.close_field(); + } + }; + + Chosen.prototype.results_build = function() { + var content, data, _i, _len, _ref; + this.parsing = true; + this.results_data = root.SelectParser.select_to_array(this.form_field); + if (this.is_multiple && this.choices > 0) { + this.search_choices.find("li.search-choice").remove(); + this.choices = 0; + } else if (!this.is_multiple) { + this.selected_item.addClass("chzn-default").find("span").text(this.default_text); + if (this.form_field.options.length <= this.disable_search_threshold) { + this.container.addClass("chzn-container-single-nosearch"); + } else { + this.container.removeClass("chzn-container-single-nosearch"); + } + } + content = ''; + _ref = this.results_data; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + data = _ref[_i]; + if (data.group) { + content += this.result_add_group(data); + } else if (!data.empty) { + content += this.result_add_option(data); + if (data.selected && this.is_multiple) { + this.choice_build(data); + } else if (data.selected && !this.is_multiple) { + this.selected_item.removeClass("chzn-default").find("span").text(data.text); + if (this.allow_single_deselect) this.single_deselect_control_build(); + } + } + } + this.search_field_disabled(); + this.show_search_field_default(); + this.search_field_scale(); + this.search_results.html(content); + return this.parsing = false; + }; + + Chosen.prototype.result_add_group = function(group) { + if (!group.disabled) { + group.dom_id = this.container_id + "_g_" + group.array_index; + return '
      • ' + $("
        ").text(group.label).html() + '
      • '; + } else { + return ""; + } + }; + + Chosen.prototype.result_do_highlight = function(el) { + var high_bottom, high_top, maxHeight, visible_bottom, visible_top; + if (el.length) { + this.result_clear_highlight(); + this.result_highlight = el; + this.result_highlight.addClass("highlighted"); + maxHeight = parseInt(this.search_results.css("maxHeight"), 10); + visible_top = this.search_results.scrollTop(); + visible_bottom = maxHeight + visible_top; + high_top = this.result_highlight.position().top + this.search_results.scrollTop(); + high_bottom = high_top + this.result_highlight.outerHeight(); + if (high_bottom >= visible_bottom) { + return this.search_results.scrollTop((high_bottom - maxHeight) > 0 ? high_bottom - maxHeight : 0); + } else if (high_top < visible_top) { + return this.search_results.scrollTop(high_top); + } + } + }; + + Chosen.prototype.result_clear_highlight = function() { + if (this.result_highlight) this.result_highlight.removeClass("highlighted"); + return this.result_highlight = null; + }; + + Chosen.prototype.results_show = function() { + var dd_top; + if (!this.is_multiple) { + this.selected_item.addClass("chzn-single-with-drop"); + if (this.result_single_selected) { + this.result_do_highlight(this.result_single_selected); + } + } else if (this.max_selected_options <= this.choices) { + this.form_field_jq.trigger("liszt:maxselected", { + chosen: this + }); + return false; + } + dd_top = this.is_multiple ? this.container.height() : this.container.height() - 1; + this.form_field_jq.trigger("liszt:showing_dropdown", { + chosen: this + }); + this.dropdown.css({ + "top": dd_top + "px", + "left": 0 + }); + this.results_showing = true; + this.search_field.focus(); + this.search_field.val(this.search_field.val()); + return this.winnow_results(); + }; + + Chosen.prototype.results_hide = function() { + if (!this.is_multiple) { + this.selected_item.removeClass("chzn-single-with-drop"); + } + this.result_clear_highlight(); + this.form_field_jq.trigger("liszt:hiding_dropdown", { + chosen: this + }); + this.dropdown.css({ + "left": "-9000px" + }); + return this.results_showing = false; + }; + + Chosen.prototype.set_tab_index = function(el) { + var ti; + if (this.form_field_jq.attr("tabindex")) { + ti = this.form_field_jq.attr("tabindex"); + this.form_field_jq.attr("tabindex", -1); + if (this.is_multiple) { + return this.search_field.attr("tabindex", ti); + } else { + this.selected_item.attr("tabindex", ti); + return this.search_field.attr("tabindex", -1); + } + } + }; + + Chosen.prototype.show_search_field_default = function() { + if (this.is_multiple && this.choices < 1 && !this.active_field) { + this.search_field.val(this.default_text); + return this.search_field.addClass("default"); + } else { + this.search_field.val(""); + return this.search_field.removeClass("default"); + } + }; + + Chosen.prototype.search_results_mouseup = function(evt) { + var target; + target = $(evt.target).hasClass("active-result") ? $(evt.target) : $(evt.target).parents(".active-result").first(); + if (target.length) { + this.result_highlight = target; + return this.result_select(evt); + } + }; + + Chosen.prototype.search_results_mouseover = function(evt) { + var target; + target = $(evt.target).hasClass("active-result") ? $(evt.target) : $(evt.target).parents(".active-result").first(); + if (target) return this.result_do_highlight(target); + }; + + Chosen.prototype.search_results_mouseout = function(evt) { + if ($(evt.target).hasClass("active-result" || $(evt.target).parents('.active-result').first())) { + return this.result_clear_highlight(); + } + }; + + Chosen.prototype.choices_click = function(evt) { + evt.preventDefault(); + if (this.active_field && !($(evt.target).hasClass("search-choice" || $(evt.target).parents('.search-choice').first)) && !this.results_showing) { + return this.results_show(); + } + }; + + Chosen.prototype.choice_build = function(item) { + var choice_id, link, + _this = this; + if (this.is_multiple && this.max_selected_options <= this.choices) { + this.form_field_jq.trigger("liszt:maxselected", { + chosen: this + }); + return false; + } + choice_id = this.container_id + "_c_" + item.array_index; + this.choices += 1; + this.search_container.before('
      • ' + item.html + '
      • '); + link = $('#' + choice_id).find("a").first(); + return link.click(function(evt) { + return _this.choice_destroy_link_click(evt); + }); + }; + + Chosen.prototype.choice_destroy_link_click = function(evt) { + evt.preventDefault(); + if (!this.is_disabled) { + this.pending_destroy_click = true; + return this.choice_destroy($(evt.target)); + } else { + return evt.stopPropagation; + } + }; + + Chosen.prototype.choice_destroy = function(link) { + this.choices -= 1; + this.show_search_field_default(); + if (this.is_multiple && this.choices > 0 && this.search_field.val().length < 1) { + this.results_hide(); + } + this.result_deselect(link.attr("rel")); + return link.parents('li').first().remove(); + }; + + Chosen.prototype.results_reset = function() { + this.form_field.options[0].selected = true; + this.selected_item.find("span").text(this.default_text); + if (!this.is_multiple) this.selected_item.addClass("chzn-default"); + this.show_search_field_default(); + this.results_reset_cleanup(); + this.form_field_jq.trigger("change"); + if (this.active_field) return this.results_hide(); + }; + + Chosen.prototype.results_reset_cleanup = function() { + return this.selected_item.find("abbr").remove(); + }; + + Chosen.prototype.result_select = function(evt) { + var high, high_id, item, position; + if (this.result_highlight) { + high = this.result_highlight; + high_id = high.attr("id"); + this.result_clear_highlight(); + if (this.is_multiple) { + this.result_deactivate(high); + } else { + this.search_results.find(".result-selected").removeClass("result-selected"); + this.result_single_selected = high; + this.selected_item.removeClass("chzn-default"); + } + high.addClass("result-selected"); + position = high_id.substr(high_id.lastIndexOf("_") + 1); + item = this.results_data[position]; + item.selected = true; + this.form_field.options[item.options_index].selected = true; + if (this.is_multiple) { + this.choice_build(item); + } else { + this.selected_item.find("span").first().text(item.text); + if (this.allow_single_deselect) this.single_deselect_control_build(); + } + if (!(evt.metaKey && this.is_multiple)) this.results_hide(); + this.search_field.val(""); + if (this.is_multiple || this.form_field_jq.val() !== this.current_value) { + this.form_field_jq.trigger("change", { + 'selected': this.form_field.options[item.options_index].value + }); + } + this.current_value = this.form_field_jq.val(); + return this.search_field_scale(); + } + }; + + Chosen.prototype.result_activate = function(el) { + return el.addClass("active-result"); + }; + + Chosen.prototype.result_deactivate = function(el) { + return el.removeClass("active-result"); + }; + + Chosen.prototype.result_deselect = function(pos) { + var result, result_data; + result_data = this.results_data[pos]; + result_data.selected = false; + this.form_field.options[result_data.options_index].selected = false; + result = $("#" + this.container_id + "_o_" + pos); + result.removeClass("result-selected").addClass("active-result").show(); + this.result_clear_highlight(); + this.winnow_results(); + this.form_field_jq.trigger("change", { + deselected: this.form_field.options[result_data.options_index].value + }); + return this.search_field_scale(); + }; + + Chosen.prototype.single_deselect_control_build = function() { + if (this.allow_single_deselect && this.selected_item.find("abbr").length < 1) { + return this.selected_item.find("span").first().after(""); + } + }; + + Chosen.prototype.winnow_results = function() { + var found, option, part, parts, regex, regexAnchor, result, result_id, results, searchText, startpos, text, zregex, _i, _j, _len, _len2, _ref; + this.no_results_clear(); + results = 0; + searchText = this.search_field.val() === this.default_text ? "" : $('
        ').text($.trim(this.search_field.val())).html(); + regexAnchor = this.search_contains ? "" : "^"; + regex = new RegExp(regexAnchor + searchText.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"), 'i'); + zregex = new RegExp(searchText.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"), 'i'); + _ref = this.results_data; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + option = _ref[_i]; + if (!option.disabled && !option.empty) { + if (option.group) { + $('#' + option.dom_id).css('display', 'none'); + } else if (!(this.is_multiple && option.selected)) { + found = false; + result_id = option.dom_id; + result = $("#" + result_id); + if (regex.test(option.html)) { + found = true; + results += 1; + } else if (option.html.indexOf(" ") >= 0 || option.html.indexOf("[") === 0) { + parts = option.html.replace(/\[|\]/g, "").split(" "); + if (parts.length) { + for (_j = 0, _len2 = parts.length; _j < _len2; _j++) { + part = parts[_j]; + if (regex.test(part)) { + found = true; + results += 1; + } + } + } + } + if (found) { + if (searchText.length) { + startpos = option.html.search(zregex); + text = option.html.substr(0, startpos + searchText.length) + '' + option.html.substr(startpos + searchText.length); + text = text.substr(0, startpos) + '' + text.substr(startpos); + } else { + text = option.html; + } + result.html(text); + this.result_activate(result); + if (option.group_array_index != null) { + $("#" + this.results_data[option.group_array_index].dom_id).css('display', 'list-item'); + } + } else { + if (this.result_highlight && result_id === this.result_highlight.attr('id')) { + this.result_clear_highlight(); + } + this.result_deactivate(result); + } + } + } + } + if (results < 1 && searchText.length) { + return this.no_results(searchText); + } else { + return this.winnow_results_set_highlight(); + } + }; + + Chosen.prototype.winnow_results_clear = function() { + var li, lis, _i, _len, _results; + this.search_field.val(""); + lis = this.search_results.find("li"); + _results = []; + for (_i = 0, _len = lis.length; _i < _len; _i++) { + li = lis[_i]; + li = $(li); + if (li.hasClass("group-result")) { + _results.push(li.css('display', 'auto')); + } else if (!this.is_multiple || !li.hasClass("result-selected")) { + _results.push(this.result_activate(li)); + } else { + _results.push(void 0); + } + } + return _results; + }; + + Chosen.prototype.winnow_results_set_highlight = function() { + var do_high, selected_results; + if (!this.result_highlight) { + selected_results = !this.is_multiple ? this.search_results.find(".result-selected.active-result") : []; + do_high = selected_results.length ? selected_results.first() : this.search_results.find(".active-result").first(); + if (do_high != null) return this.result_do_highlight(do_high); + } + }; + + Chosen.prototype.no_results = function(terms) { + var no_results_html; + no_results_html = $('
      • ' + this.results_none_found + ' ""
      • '); + no_results_html.find("span").first().html(terms); + return this.search_results.append(no_results_html); + }; + + Chosen.prototype.no_results_clear = function() { + return this.search_results.find(".no-results").remove(); + }; + + Chosen.prototype.keydown_arrow = function() { + var first_active, next_sib; + if (!this.result_highlight) { + first_active = this.search_results.find("li.active-result").first(); + if (first_active) this.result_do_highlight($(first_active)); + } else if (this.results_showing) { + next_sib = this.result_highlight.nextAll("li.active-result").first(); + if (next_sib) this.result_do_highlight(next_sib); + } + if (!this.results_showing) return this.results_show(); + }; + + Chosen.prototype.keyup_arrow = function() { + var prev_sibs; + if (!this.results_showing && !this.is_multiple) { + return this.results_show(); + } else if (this.result_highlight) { + prev_sibs = this.result_highlight.prevAll("li.active-result"); + if (prev_sibs.length) { + return this.result_do_highlight(prev_sibs.first()); + } else { + if (this.choices > 0) this.results_hide(); + return this.result_clear_highlight(); + } + } + }; + + Chosen.prototype.keydown_backstroke = function() { + if (this.pending_backstroke) { + this.choice_destroy(this.pending_backstroke.find("a").first()); + return this.clear_backstroke(); + } else { + this.pending_backstroke = this.search_container.siblings("li.search-choice").last(); + if (this.single_backstroke_delete) { + return this.keydown_backstroke(); + } else { + return this.pending_backstroke.addClass("search-choice-focus"); + } + } + }; + + Chosen.prototype.clear_backstroke = function() { + if (this.pending_backstroke) { + this.pending_backstroke.removeClass("search-choice-focus"); + } + return this.pending_backstroke = null; + }; + + Chosen.prototype.keydown_checker = function(evt) { + var stroke, _ref; + stroke = (_ref = evt.which) != null ? _ref : evt.keyCode; + this.search_field_scale(); + if (stroke !== 8 && this.pending_backstroke) this.clear_backstroke(); + switch (stroke) { + case 8: + this.backstroke_length = this.search_field.val().length; + break; + case 9: + if (this.results_showing && !this.is_multiple) this.result_select(evt); + this.mouse_on_container = false; + break; + case 13: + evt.preventDefault(); + break; + case 38: + evt.preventDefault(); + this.keyup_arrow(); + break; + case 40: + this.keydown_arrow(); + break; + } + }; + + Chosen.prototype.search_field_scale = function() { + var dd_top, div, h, style, style_block, styles, w, _i, _len; + if (this.is_multiple) { + h = 0; + w = 0; + style_block = "position:absolute; left: -1000px; top: -1000px; display:none;"; + styles = ['font-size', 'font-style', 'font-weight', 'font-family', 'line-height', 'text-transform', 'letter-spacing']; + for (_i = 0, _len = styles.length; _i < _len; _i++) { + style = styles[_i]; + style_block += style + ":" + this.search_field.css(style) + ";"; + } + div = $('
        ', { + 'style': style_block + }); + div.text(this.search_field.val()); + $('body').append(div); + w = div.width() + 25; + div.remove(); + if (w > this.f_width - 10) w = this.f_width - 10; + this.search_field.css({ + 'width': w + 'px' + }); + dd_top = this.container.height(); + return this.dropdown.css({ + "top": dd_top + "px" + }); + } + }; + + Chosen.prototype.generate_random_id = function() { + var string; + string = "sel" + this.generate_random_char() + this.generate_random_char() + this.generate_random_char(); + while ($("#" + string).length > 0) { + string += this.generate_random_char(); + } + return string; + }; + + return Chosen; + + })(AbstractChosen); + + get_side_border_padding = function(elmt) { + var side_border_padding; + return side_border_padding = elmt.outerWidth() - elmt.width(); + }; + + root.get_side_border_padding = get_side_border_padding; + +}).call(this); diff --git a/vendor/assets/javascripts/vendor.js b/vendor/assets/javascripts/vendor.js index 17db61668..fb26ee768 100644 --- a/vendor/assets/javascripts/vendor.js +++ b/vendor/assets/javascripts/vendor.js @@ -11,5 +11,8 @@ //= require bootstrap-button //= require bootstrap-dropdown //= require bootstrap-tab +// require bootstrap-tooltip +// require bootstrap-popover +//= require chosen.jquery // require html5shiv // require_tree . diff --git a/vendor/assets/stylesheets/chosen.scss b/vendor/assets/stylesheets/chosen.scss new file mode 100755 index 000000000..ddb4d7036 --- /dev/null +++ b/vendor/assets/stylesheets/chosen.scss @@ -0,0 +1,396 @@ +/* @group Base */ +.chzn-container { + font-size: 13px; + position: relative; + display: inline-block; + zoom: 1; + *display: inline; +} +.chzn-container .chzn-drop { + background: #fff; + border: 1px solid #aaa; + border-top: 0; + position: absolute; + top: 29px; + left: 0; + -webkit-box-shadow: 0 4px 5px rgba(0,0,0,.15); + -moz-box-shadow : 0 4px 5px rgba(0,0,0,.15); + -o-box-shadow : 0 4px 5px rgba(0,0,0,.15); + box-shadow : 0 4px 5px rgba(0,0,0,.15); + z-index: 1010; +} +/* @end */ + +/* @group Single Chosen */ +.chzn-container-single .chzn-single { + background-color: #ffffff; + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#eeeeee', GradientType=0 ); + background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(20%, #ffffff), color-stop(50%, #f6f6f6), color-stop(52%, #eeeeee), color-stop(100%, #f4f4f4)); + background-image: -webkit-linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%); + background-image: -moz-linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%); + background-image: -o-linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%); + background-image: -ms-linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%); + background-image: linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%); + -webkit-border-radius: 5px; + -moz-border-radius : 5px; + border-radius : 5px; + -moz-background-clip : padding; + -webkit-background-clip: padding-box; + background-clip : padding-box; + border: 1px solid #aaaaaa; + -webkit-box-shadow: 0 0 3px #ffffff inset, 0 1px 1px rgba(0,0,0,0.1); + -moz-box-shadow : 0 0 3px #ffffff inset, 0 1px 1px rgba(0,0,0,0.1); + box-shadow : 0 0 3px #ffffff inset, 0 1px 1px rgba(0,0,0,0.1); + display: block; + overflow: hidden; + white-space: nowrap; + position: relative; + height: 23px; + line-height: 24px; + padding: 0 0 0 8px; + color: #444444; + text-decoration: none; +} +.chzn-container-single .chzn-default { + color: #999; +} +.chzn-container-single .chzn-single span { + margin-right: 26px; + display: block; + overflow: hidden; + white-space: nowrap; + -o-text-overflow: ellipsis; + -ms-text-overflow: ellipsis; + text-overflow: ellipsis; +} +.chzn-container-single .chzn-single abbr { + display: block; + position: absolute; + right: 26px; + top: 6px; + width: 12px; + height: 13px; + font-size: 1px; + background: image-url('chosen/chosen-sprite.png') right top no-repeat; +} +.chzn-container-single .chzn-single abbr:hover { + background-position: right -11px; +} +.chzn-container-single.chzn-disabled .chzn-single abbr:hover { + background-position: right top; +} +.chzn-container-single .chzn-single div { + position: absolute; + right: 0; + top: 0; + display: block; + height: 100%; + width: 18px; +} +.chzn-container-single .chzn-single div b { + background: image-url('chosen/chosen-sprite.png') no-repeat 0 0; + display: block; + width: 100%; + height: 100%; +} +.chzn-container-single .chzn-search { + padding: 3px 4px; + position: relative; + margin: 0; + white-space: nowrap; + z-index: 1010; +} +.chzn-container-single .chzn-search input { + background: #fff image-url('chosen/chosen-sprite.png') no-repeat 100% -22px; + background: image-url('chosen/chosen-sprite.png') no-repeat 100% -22px, -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(1%, #eeeeee), color-stop(15%, #ffffff)); + background: image-url('chosen/chosen-sprite.png') no-repeat 100% -22px, -webkit-linear-gradient(top, #eeeeee 1%, #ffffff 15%); + background: image-url('chosen/chosen-sprite.png') no-repeat 100% -22px, -moz-linear-gradient(top, #eeeeee 1%, #ffffff 15%); + background: image-url('chosen/chosen-sprite.png') no-repeat 100% -22px, -o-linear-gradient(top, #eeeeee 1%, #ffffff 15%); + background: image-url('chosen/chosen-sprite.png') no-repeat 100% -22px, -ms-linear-gradient(top, #eeeeee 1%, #ffffff 15%); + background: image-url('chosen/chosen-sprite.png') no-repeat 100% -22px, linear-gradient(top, #eeeeee 1%, #ffffff 15%); + margin: 1px 0; + padding: 4px 20px 4px 5px; + outline: 0; + border: 1px solid #aaa; + font-family: sans-serif; + font-size: 1em; +} +.chzn-container-single .chzn-drop { + -webkit-border-radius: 0 0 4px 4px; + -moz-border-radius : 0 0 4px 4px; + border-radius : 0 0 4px 4px; + -moz-background-clip : padding; + -webkit-background-clip: padding-box; + background-clip : padding-box; +} +/* @end */ + +.chzn-container-single-nosearch .chzn-search input { + position: absolute; + left: -9000px; +} + +/* @group Multi Chosen */ +.chzn-container-multi .chzn-choices { + background-color: #fff; + background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(1%, #eeeeee), color-stop(15%, #ffffff)); + background-image: -webkit-linear-gradient(top, #eeeeee 1%, #ffffff 15%); + background-image: -moz-linear-gradient(top, #eeeeee 1%, #ffffff 15%); + background-image: -o-linear-gradient(top, #eeeeee 1%, #ffffff 15%); + background-image: -ms-linear-gradient(top, #eeeeee 1%, #ffffff 15%); + background-image: linear-gradient(top, #eeeeee 1%, #ffffff 15%); + border: 1px solid #aaa; + margin: 0; + padding: 0; + cursor: text; + overflow: hidden; + height: auto !important; + height: 1%; + position: relative; +} +.chzn-container-multi .chzn-choices li { + float: left; + list-style: none; +} +.chzn-container-multi .chzn-choices .search-field { + white-space: nowrap; + margin: 0; + padding: 0; +} +.chzn-container-multi .chzn-choices .search-field input { + color: #666; + background: transparent !important; + border: 0 !important; + font-family: sans-serif; + font-size: 100%; + height: 15px; + padding: 5px; + margin: 1px 0; + outline: 0; + -webkit-box-shadow: none; + -moz-box-shadow : none; + -o-box-shadow : none; + box-shadow : none; +} +.chzn-container-multi .chzn-choices .search-field .default { + color: #999; +} +.chzn-container-multi .chzn-choices .search-choice { + -webkit-border-radius: 3px; + -moz-border-radius : 3px; + border-radius : 3px; + -moz-background-clip : padding; + -webkit-background-clip: padding-box; + background-clip : padding-box; + background-color: #e4e4e4; + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f4f4f4', endColorstr='#eeeeee', GradientType=0 ); + background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), color-stop(100%, #eeeeee)); + background-image: -webkit-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%); + background-image: -moz-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%); + background-image: -o-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%); + background-image: -ms-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%); + background-image: linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%); + -webkit-box-shadow: 0 0 2px #ffffff inset, 0 1px 0 rgba(0,0,0,0.05); + -moz-box-shadow : 0 0 2px #ffffff inset, 0 1px 0 rgba(0,0,0,0.05); + box-shadow : 0 0 2px #ffffff inset, 0 1px 0 rgba(0,0,0,0.05); + color: #333; + border: 1px solid #aaaaaa; + line-height: 13px; + padding: 3px 20px 3px 5px; + margin: 3px 0 3px 5px; + position: relative; + cursor: default; +} +.chzn-container-multi .chzn-choices .search-choice-focus { + background: #d4d4d4; +} +.chzn-container-multi .chzn-choices .search-choice .search-choice-close { + display: block; + position: absolute; + right: 3px; + top: 4px; + width: 12px; + height: 13px; + font-size: 1px; + background: image-url('chosen/chosen-sprite.png') right top no-repeat; +} +.chzn-container-multi .chzn-choices .search-choice .search-choice-close:hover { + background-position: right -11px; +} +.chzn-container-multi .chzn-choices .search-choice-focus .search-choice-close { + background-position: right -11px; +} +/* @end */ + +/* @group Results */ +.chzn-container .chzn-results { + margin: 0 4px 4px 0; + max-height: 240px; + padding: 0 0 0 4px; + position: relative; + overflow-x: hidden; + overflow-y: auto; + -webkit-overflow-scrolling: touch; +} +.chzn-container-multi .chzn-results { + margin: -1px 0 0; + padding: 0; +} +.chzn-container .chzn-results li { + display: none; + line-height: 15px; + padding: 5px 6px; + margin: 0; + list-style: none; +} +.chzn-container .chzn-results .active-result { + cursor: pointer; + display: list-item; +} +.chzn-container .chzn-results .highlighted { + background-color: #3875d7; + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#3875d7', endColorstr='#2a62bc', GradientType=0 ); + background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(20%, #3875d7), color-stop(90%, #2a62bc)); + background-image: -webkit-linear-gradient(top, #3875d7 20%, #2a62bc 90%); + background-image: -moz-linear-gradient(top, #3875d7 20%, #2a62bc 90%); + background-image: -o-linear-gradient(top, #3875d7 20%, #2a62bc 90%); + background-image: -ms-linear-gradient(top, #3875d7 20%, #2a62bc 90%); + background-image: linear-gradient(top, #3875d7 20%, #2a62bc 90%); + color: #fff; +} +.chzn-container .chzn-results li em { + background: #feffde; + font-style: normal; +} +.chzn-container .chzn-results .highlighted em { + background: transparent; +} +.chzn-container .chzn-results .no-results { + background: #f4f4f4; + display: list-item; +} +.chzn-container .chzn-results .group-result { + cursor: default; + color: #999; + font-weight: bold; +} +.chzn-container .chzn-results .group-option { + padding-left: 15px; +} +.chzn-container-multi .chzn-drop .result-selected { + display: none; +} +.chzn-container .chzn-results-scroll { + background: white; + margin: 0 4px; + position: absolute; + text-align: center; + width: 321px; /* This should by dynamic with js */ + z-index: 1; +} +.chzn-container .chzn-results-scroll span { + display: inline-block; + height: 17px; + text-indent: -5000px; + width: 9px; +} +.chzn-container .chzn-results-scroll-down { + bottom: 0; +} +.chzn-container .chzn-results-scroll-down span { + background: image-url('chosen/chosen-sprite.png') no-repeat -4px -3px; +} +.chzn-container .chzn-results-scroll-up span { + background: image-url('chosen/chosen-sprite.png') no-repeat -22px -3px; +} +/* @end */ + +/* @group Active */ +.chzn-container-active .chzn-single { + -webkit-box-shadow: 0 0 5px rgba(0,0,0,.3); + -moz-box-shadow : 0 0 5px rgba(0,0,0,.3); + -o-box-shadow : 0 0 5px rgba(0,0,0,.3); + box-shadow : 0 0 5px rgba(0,0,0,.3); + border: 1px solid #5897fb; +} +.chzn-container-active .chzn-single-with-drop { + border: 1px solid #aaa; + -webkit-box-shadow: 0 1px 0 #fff inset; + -moz-box-shadow : 0 1px 0 #fff inset; + -o-box-shadow : 0 1px 0 #fff inset; + box-shadow : 0 1px 0 #fff inset; + background-color: #eee; + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#eeeeee', endColorstr='#ffffff', GradientType=0 ); + background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(20%, #eeeeee), color-stop(80%, #ffffff)); + background-image: -webkit-linear-gradient(top, #eeeeee 20%, #ffffff 80%); + background-image: -moz-linear-gradient(top, #eeeeee 20%, #ffffff 80%); + background-image: -o-linear-gradient(top, #eeeeee 20%, #ffffff 80%); + background-image: -ms-linear-gradient(top, #eeeeee 20%, #ffffff 80%); + background-image: linear-gradient(top, #eeeeee 20%, #ffffff 80%); + -webkit-border-bottom-left-radius : 0; + -webkit-border-bottom-right-radius: 0; + -moz-border-radius-bottomleft : 0; + -moz-border-radius-bottomright: 0; + border-bottom-left-radius : 0; + border-bottom-right-radius: 0; +} +.chzn-container-active .chzn-single-with-drop div { + background: transparent; + border-left: none; +} +.chzn-container-active .chzn-single-with-drop div b { + background-position: -18px 1px; +} +.chzn-container-active .chzn-choices { + -webkit-box-shadow: 0 0 5px rgba(0,0,0,.3); + -moz-box-shadow : 0 0 5px rgba(0,0,0,.3); + -o-box-shadow : 0 0 5px rgba(0,0,0,.3); + box-shadow : 0 0 5px rgba(0,0,0,.3); + border: 1px solid #5897fb; +} +.chzn-container-active .chzn-choices .search-field input { + color: #111 !important; +} +/* @end */ + +/* @group Disabled Support */ +.chzn-disabled { + cursor: default; + opacity:0.5 !important; +} +.chzn-disabled .chzn-single { + cursor: default; +} +.chzn-disabled .chzn-choices .search-choice .search-choice-close { + cursor: default; +} + +/* @group Right to Left */ +.chzn-rtl { text-align: right; } +.chzn-rtl .chzn-single { padding: 0 8px 0 0; overflow: visible; } +.chzn-rtl .chzn-single span { margin-left: 26px; margin-right: 0; direction: rtl; } + +.chzn-rtl .chzn-single div { left: 3px; right: auto; } +.chzn-rtl .chzn-single abbr { + left: 26px; + right: auto; +} +.chzn-rtl .chzn-choices .search-field input { direction: rtl; } +.chzn-rtl .chzn-choices li { float: right; } +.chzn-rtl .chzn-choices .search-choice { padding: 3px 5px 3px 19px; margin: 3px 5px 3px 0; } +.chzn-rtl .chzn-choices .search-choice .search-choice-close { left: 4px; right: auto; background-position: right top;} +.chzn-rtl.chzn-container-single .chzn-results { margin: 0 0 4px 4px; padding: 0 4px 0 0; } +.chzn-rtl .chzn-results .group-option { padding-left: 0; padding-right: 15px; } +.chzn-rtl.chzn-container-active .chzn-single-with-drop div { border-right: none; } +.chzn-rtl .chzn-search input { + background: #fff image-url('chosen/chosen-sprite.png') no-repeat -38px -22px; + background: image-url('chosen/chosen-sprite.png') no-repeat -38px -22px, -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(1%, #eeeeee), color-stop(15%, #ffffff)); + background: image-url('chosen/chosen-sprite.png') no-repeat -38px -22px, -webkit-linear-gradient(top, #eeeeee 1%, #ffffff 15%); + background: image-url('chosen/chosen-sprite.png') no-repeat -38px -22px, -moz-linear-gradient(top, #eeeeee 1%, #ffffff 15%); + background: image-url('chosen/chosen-sprite.png') no-repeat -38px -22px, -o-linear-gradient(top, #eeeeee 1%, #ffffff 15%); + background: image-url('chosen/chosen-sprite.png') no-repeat -38px -22px, -ms-linear-gradient(top, #eeeeee 1%, #ffffff 15%); + background: image-url('chosen/chosen-sprite.png') no-repeat -38px -22px, linear-gradient(top, #eeeeee 1%, #ffffff 15%); + padding: 4px 5px 4px 20px; + direction: rtl; +} +/* @end */ diff --git a/vendor/assets/stylesheets/vendor.scss b/vendor/assets/stylesheets/vendor.scss index 9bac4e7b0..8c3767f62 100644 --- a/vendor/assets/stylesheets/vendor.scss +++ b/vendor/assets/stylesheets/vendor.scss @@ -13,4 +13,5 @@ @import "codemirror/modes/rpm-spec"; @import "codemirror/modes/tiddlywiki"; -@import "bootstrap" +@import "bootstrap"; +@import "chosen.scss";