diff --git a/app/assets/stylesheets/design/custom.scss b/app/assets/stylesheets/design/custom.scss index b0d328dad..ec5e20392 100644 --- a/app/assets/stylesheets/design/custom.scss +++ b/app/assets/stylesheets/design/custom.scss @@ -1434,6 +1434,37 @@ div.log-wrapper { padding: 5px 10px; border-bottom: none; border-radius: 5px 5px 0 0; + + table.options { + border-spacing: 5px 0; + + tr { + + td a { + display: inline-block; + height: 13px; + padding-left: 16px; + background: image-url('code.png') no-repeat; + padding-bottom: 2px; + margin: 2px 0 0 1px; + } + td.first { + padding-right: 5px; + } + td.last { + border-left: 1px solid #ccc; + } + } + + tr.bottom { + td { + padding-left: 5px; + } + td.first { + border-top: 1px solid #ccc; + } + } + } } .log { diff --git a/app/controllers/projects/build_lists_controller.rb b/app/controllers/projects/build_lists_controller.rb index 9045a49ed..e12eced17 100644 --- a/app/controllers/projects/build_lists_controller.rb +++ b/app/controllers/projects/build_lists_controller.rb @@ -5,9 +5,9 @@ class Projects::BuildListsController < Projects::BaseController before_filter :authenticate_user!, :except => CALLBACK_ACTIONS before_filter :authenticate_build_service!, :only => CALLBACK_ACTIONS - skip_before_filter :authenticate_user!, :only => [:show, :index, :search] if APP_CONFIG['anonymous_access'] + skip_before_filter :authenticate_user!, :only => [:show, :index, :search, :log] if APP_CONFIG['anonymous_access'] - before_filter :find_build_list, :only => [:show, :publish, :cancel, :update] + before_filter :find_build_list, :only => [:show, :publish, :cancel, :update, :log] before_filter :find_build_list_by_bs, :only => [:publish_build, :status_build, :pre_build, :post_build, :circle_build] load_and_authorize_resource :project, :only => NESTED_ACTIONS @@ -101,6 +101,15 @@ class Projects::BuildListsController < Projects::BaseController end end + def log + @log = `tail -n #{params[:load_lines].to_i} #{Rails.root + 'public' + @build_list.fs_log_path}` + @log = t("layout.build_lists.log.not_available") unless $?.success? + + respond_to do |format| + format.json { render :json => { :log => @log, :building => @build_list.build_started? } } + end + end + def publish_build if params[:status].to_i == 0 # ok @build_list.published diff --git a/app/helpers/build_lists_helper.rb b/app/helpers/build_lists_helper.rb index 13b238834..367ba78e7 100644 --- a/app/helpers/build_lists_helper.rb +++ b/app/helpers/build_lists_helper.rb @@ -49,7 +49,7 @@ module BuildListsHelper end def build_list_log_url(log_type) - "#{container_url}/log/#{@build_list.project.name}/#{log_type.to_s}.log".html_safe + "http://#{request.host_with_port}/#{@build_list.fs_log_path(log_type)}".html_safe end def log_reload_time_options @@ -57,4 +57,8 @@ module BuildListsHelper options_for_select(t, t.first).html_safe end + + def log_reload_lines_options + options_for_select([100, 200, 500, 1000, 1500, 2000], 1000).html_safe + end end diff --git a/app/models/ability.rb b/app/models/ability.rb index 6a0d2d96c..f9e470443 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -17,7 +17,7 @@ class Ability can :archive, Project, :visibility => 'open' can :read, Issue, :project => {:visibility => 'open'} can :search, BuildList - can [:read, :everything], BuildList, :project => {:visibility => 'open'} + can [:read, :log, :everything], BuildList, :project => {:visibility => 'open'} can :read, ProductBuildList#, :product => {:platform => {:visibility => 'open'}} # double nested hash don't work can :read, Advisory can(:advisories, Platform) {APP_CONFIG['anonymous_access']} @@ -62,10 +62,10 @@ class Ability can(:destroy, Project) {|project| project.owner_type == 'Group' and project.owner.actors.exists?(:actor_type => 'User', :actor_id => user.id, :role => 'admin')} can :remove_user, Project - can [:read, :owned, :everything], BuildList, :user_id => user.id - can [:read, :related, :everything], BuildList, :project => {:owner_type => 'User', :owner_id => user.id} - can [:read, :related, :everything], BuildList, :project => {:owner_type => 'Group', :owner_id => user.group_ids} - can([:read, :everything], BuildList, read_relations_for('build_lists', 'projects')) {|build_list| can? :read, build_list.project} + can [:read, :log, :owned, :everything], BuildList, :user_id => user.id + can [:read, :log, :related, :everything], BuildList, :project => {:owner_type => 'User', :owner_id => user.id} + can [:read, :log, :related, :everything], BuildList, :project => {:owner_type => 'Group', :owner_id => user.group_ids} + can([:read, :log, :everything], BuildList, read_relations_for('build_lists', 'projects')) {|build_list| can? :read, build_list.project} can([:create, :update], BuildList) {|build_list| build_list.project.is_package && can?(:write, build_list.project)} can(:publish, BuildList) do |build_list| diff --git a/app/models/build_list.rb b/app/models/build_list.rb index 07cc933b8..cf780d279 100644 --- a/app/models/build_list.rb +++ b/app/models/build_list.rb @@ -266,6 +266,10 @@ class BuildList < ActiveRecord::Base I18n.t("layout.build_lists.human_duration", {:hours => (duration/3600).to_i, :minutes => (duration%3600/60).to_i}) end + def fs_log_path(log_type = :build) + container_path? ? "downloads/#{container_path}/log/#{project.name}/#{log_type.to_s}.log" : nil + end + def in_work? status == BuildServer::BUILD_STARTED #[WAITING_FOR_RESPONSE, BuildServer::BUILD_PENDING, BuildServer::BUILD_STARTED].include?(status) diff --git a/app/views/projects/build_lists/_log.html.haml b/app/views/projects/build_lists/_log.html.haml index f06cdbc01..09929c7f3 100644 --- a/app/views/projects/build_lists/_log.html.haml +++ b/app/views/projects/build_lists/_log.html.haml @@ -9,19 +9,26 @@ .both .log-body.hidden .reloader - = label_tag :word_wrap do - = check_box_tag :word_wrap - = t("layout.word_wrap") -   - %span{ :class => @build_list.build_started? ? nil : :hidden } - = label_tag :autoreload do - = check_box_tag :autoreload, true, @build_list.build_started? - = t("layout.autoreload_log") - = select_tag :reload_interval, log_reload_time_options + %table.options + %tr.top + %td.first + = label_tag :word_wrap do + = check_box_tag :word_wrap + = t("layout.word_wrap") + %td.last{ :class => @build_list.build_started? ? nil : :hidden } + = label_tag :autoreload do + = check_box_tag :autoreload, true, @build_list.build_started? + = t("layout.build_lists.log.autoreload") + = select_tag :reload_interval, log_reload_time_options + %tr.bottom + %td.first + = link_to t("layout.build_lists.log.download"), build_list_log_url(:build), :id => :log_url + %td.last{ :class => @build_list.build_started? ? nil : :hidden } + = label_tag :load_lines do + = raw t("layout.build_lists.log.load_lines", :count => select_tag(:load_lines, log_reload_lines_options)) .both - %textarea.log{ :readonly => :readonly, - :wrap => 'off', - :data => {:url => build_list_log_url(:build)}} + %textarea.log{ :readonly => :readonly, :wrap => 'off', + :data => { :url => log_build_list_path(@build_list), :log_type => :build } } = t("layout.build_lists.log.not_available") :javascript @@ -30,8 +37,10 @@ var $wrapper = $('div.log-wrapper'); var $logBody = $wrapper.children('div.log-body').first(); var $logCont = $logBody.children('.log').first(); - var logUrl = $logCont.data('url'); + + var logUrl = $logCont.data('url'); var $logHead = $wrapper.children('div.log-header').first(); + var $trigger = $logHead.children('span').first(); var $autoload = $('#autoreload'); @@ -64,17 +73,24 @@ $.ajax({ url: logUrl, type: "GET", + dataType: 'json', + data: $logCont.data(), + beforeSend: function( xhr ) { + var token = $('meta[name="csrf-token"]').attr('content'); + if (token) xhr.setRequestHeader('X-CSRF-Token', token); + }, success: function(data, textStatus, jqXHR) { var l = $logCont[0]; var vScroll = l.scrollTop; var hScroll = l.scrollLeft; var onBottom = Math.abs((l.clientHeight + vScroll - l.scrollHeight)) < getLineHeight(l); - $logCont.text(data); + $logCont.text(data.log); $logCont.scrollLeft(hScroll); $logCont.scrollTop((onBottom || first_open) ? l.scrollHeight - l.clientHeight : vScroll); first_open = false; + if (!data.building) $autoload.attr({'checked': false}).trigger('change'); } }); } @@ -110,18 +126,23 @@ window.location.href = $('a#log_anchor').attr('href'); } - loadLog(); $wrapper.on('click', '.log-header > span', toggleHandler); $autoload.on('change', reloadChange); + $('#word_wrap').on('change', function() { $logCont.attr({'wrap': ($(this).is(':checked')) ? 'soft' : 'off'}); }); + $('#reload_interval').on('change', function() { clearInterval(t); if ($autoload.is(':checked')) { t = setInterval($(this).val()); } }); + $('#load_lines').on('change', function() { + $logCont.data('load_lines', $(this).val()); + }).trigger('change'); + loadLog(); })(); }); diff --git a/config/locales/models/build_list.en.yml b/config/locales/models/build_list.en.yml index ebd8694c7..14bc1da05 100644 --- a/config/locales/models/build_list.en.yml +++ b/config/locales/models/build_list.en.yml @@ -121,6 +121,9 @@ en: log: build_log: Build Log not_available: Log not available yet. + download: Download log + autoreload: Update log every + load_lines: Load last %{count} lines reload_times: 10000: "10 s" diff --git a/config/locales/models/build_list.ru.yml b/config/locales/models/build_list.ru.yml index 049fb938f..e93b3d490 100644 --- a/config/locales/models/build_list.ru.yml +++ b/config/locales/models/build_list.ru.yml @@ -120,6 +120,9 @@ ru: log: build_log: Лог сборки not_available: В настоящий момент лог недоступен. + download: Загрузить лог + autoreload: Обновлять лог каждые + load_lines: Загружать последние %{count} строк reload_times: 10000: "10 сек" diff --git a/config/routes.rb b/config/routes.rb index bada12bf9..f680bfbf0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -134,6 +134,7 @@ Rosa::Application.routes.draw do resources :build_lists, :only => [:index, :show, :update] do member do put :cancel + get :log end collection { post :search } end