Merge pull request #612 from warpc/590-build_list_page_change
[refs #590] Add auto updatable log block in build list page. Added forecast assembly time in monitoring page.
This commit is contained in:
commit
656892afe3
Binary file not shown.
After Width: | Height: | Size: 450 B |
|
@ -1385,10 +1385,99 @@ hr.bootstrap {
|
|||
}
|
||||
}
|
||||
|
||||
time.js-relative-date {
|
||||
display: block;
|
||||
}
|
||||
|
||||
div.reloader {
|
||||
float: right;
|
||||
}
|
||||
|
||||
div.log-wrapper {
|
||||
width: 565px;
|
||||
|
||||
div.log-header {
|
||||
margin-bottom: 10px;
|
||||
|
||||
div.text-wrap, span {
|
||||
height: 16px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
div.text-wrap {
|
||||
width: 545px;
|
||||
float: left;
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
background: image-url("trigger.gif") no-repeat scroll left bottom transparent;
|
||||
margin: 1px;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
font-size: 0;
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
float: right;
|
||||
}
|
||||
span.closed {
|
||||
background-position: left top;
|
||||
}
|
||||
}
|
||||
|
||||
div.log-body {
|
||||
|
||||
div.reloader, .log {
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
div.reloader {
|
||||
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 {
|
||||
background: #f8f8f8;
|
||||
height: 500px;
|
||||
width: 543px;
|
||||
min-width: 543px;
|
||||
border-radius: 5px 0 5px 5px;
|
||||
overflow: auto;
|
||||
padding: 10px;
|
||||
margin: 0;
|
||||
font-family: monospace;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -43,4 +43,22 @@ module BuildListsHelper
|
|||
build_list.project_version
|
||||
end
|
||||
end
|
||||
|
||||
def container_url
|
||||
"http://#{request.host_with_port}/downloads#{@build_list.container_path}".html_safe
|
||||
end
|
||||
|
||||
def build_list_log_url(log_type)
|
||||
"http://#{request.host_with_port}/#{@build_list.fs_log_path(log_type)}".html_safe
|
||||
end
|
||||
|
||||
def log_reload_time_options
|
||||
t = I18n.t("layout.build_lists.log.reload_times").map { |i| i.reverse }
|
||||
|
||||
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
|
||||
|
|
|
@ -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|
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -128,6 +128,10 @@ class Project < ActiveRecord::Base
|
|||
I18n.t("layout.projects.human_average_build_time", {:hours => (average_build_time/3600).to_i, :minutes => (average_build_time%3600/60).to_i})
|
||||
end
|
||||
|
||||
def formatted_average_build_time
|
||||
"%02d:%02d" % [average_build_time / 3600, average_build_time % 3600 / 60]
|
||||
end
|
||||
|
||||
def xml_rpc_create(repository)
|
||||
result = BuildServer.create_project name, repository.platform.name, repository.name, path
|
||||
if result == BuildServer::SUCCESS
|
||||
|
|
|
@ -2,9 +2,14 @@
|
|||
%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.human_status
|
||||
%br
|
||||
- if BuildList::HUMAN_STATUSES[build_list.status].in? [:build_pending, :build_started, :build_publish]
|
||||
%time.js-relative-date{:datetime => build_list.updated_at.strftime("%FT%T%:z"), :title => build_list.updated_at.strftime("%F %T")}
|
||||
= build_list.updated_at.strftime "%F %T"
|
||||
- if build_list.build_started? && ((build_list.project.average_build_time || 0) > 0)
|
||||
\/
|
||||
%time
|
||||
= build_list.project.formatted_average_build_time
|
||||
- if build_list.project.present?
|
||||
%td= link_to build_list.project.name_with_owner, build_list.project
|
||||
%td= build_list_version_link(build_list)
|
||||
|
|
|
@ -0,0 +1,148 @@
|
|||
.hr
|
||||
%a{:name => 'log'}
|
||||
.log-wrapper
|
||||
.log-header
|
||||
.text-wrap
|
||||
= link_to({:anchor => :log}, {:id => 'log_anchor'}) do
|
||||
%h3= t("layout.build_lists.log.build_log")
|
||||
%span
|
||||
.both
|
||||
.log-body.hidden
|
||||
.reloader
|
||||
%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 => log_build_list_path(@build_list), :log_type => :build } }
|
||||
= t("layout.build_lists.log.not_available")
|
||||
|
||||
:javascript
|
||||
$(function() {
|
||||
(function() {
|
||||
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 $logHead = $wrapper.children('div.log-header').first();
|
||||
|
||||
var $trigger = $logHead.children('span').first();
|
||||
var $autoload = $('#autoreload');
|
||||
|
||||
var state = $logBody.is(':visible');
|
||||
var t = null; // timer
|
||||
var first_open = true;
|
||||
|
||||
if (state) {
|
||||
$trigger.removeClass('closed');
|
||||
$wrapper.removeClass('inactive')
|
||||
.addClass('active');
|
||||
} else {
|
||||
$trigger.addClass('closed');
|
||||
$logBody.addClass('hidden');
|
||||
$wrapper.removeClass('active')
|
||||
.addClass('inactive');
|
||||
}
|
||||
|
||||
function getLineHeight(element){
|
||||
var temp = document.createElement(element.nodeName);
|
||||
temp.setAttribute("style","margin:0px;padding:0px;font-family:"+element.style.fontFamily+";font-size:"+element.style.fontSize);
|
||||
temp.innerHTML = "test";
|
||||
temp = element.parentNode.appendChild(temp);
|
||||
var ret = temp.clientHeight;
|
||||
temp.parentNode.removeChild(temp);
|
||||
return ret;
|
||||
}
|
||||
|
||||
var loadLog = function() {
|
||||
$.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.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');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var reloadChange = function() {
|
||||
if ($(this).is(':checked')) {
|
||||
first_open = true;
|
||||
loadLog();
|
||||
$logCont.scrollTop($logCont[0].scrollHeight - $logCont[0].clientHeight);
|
||||
t = setInterval(function() {
|
||||
loadLog();
|
||||
}, $('#reload_interval').val());
|
||||
} else {
|
||||
clearInterval(t);
|
||||
}
|
||||
}
|
||||
|
||||
var toggleHandler = function() {
|
||||
state = !state;
|
||||
// if log opened
|
||||
if (state) {
|
||||
if ($autoload.is(':checked')) {
|
||||
$autoload.trigger('change');
|
||||
}
|
||||
} else {
|
||||
clearInterval(t);
|
||||
}
|
||||
$logBody.slideToggle('slow')
|
||||
.toggleClass('hidden');
|
||||
$logHead.toggleClass('active inactive');
|
||||
$trigger.toggleClass('closed');
|
||||
|
||||
window.location.href = $('a#log_anchor').attr('href');
|
||||
}
|
||||
|
||||
$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();
|
||||
|
||||
})();
|
||||
});
|
|
@ -13,7 +13,6 @@
|
|||
link_to("#{@build_list.save_to_platform.name}/#{@build_list.save_to_repository.name}",
|
||||
[@build_list.save_to_platform, @build_list.save_to_repository])]
|
||||
- elsif @build_list.container_path.present?
|
||||
- container_url = "http://#{request.host_with_port}/downloads#{@build_list.container_path}"
|
||||
= link_to container_url, container_url
|
||||
.both
|
||||
|
||||
|
@ -125,8 +124,14 @@
|
|||
var r = new Rosa.Routers.BuildListsAdvisoriesRouter();
|
||||
});
|
||||
|
||||
= submit_tag t("layout.publish"), :confirm => t("layout.confirm"), :name => 'publish' if @build_list.can_publish? && can?(:publish, @build_list)
|
||||
= submit_tag t("layout.reject_publish"), :confirm => t("layout.confirm"), :name => 'reject_publish' if @build_list.can_reject_publish? && can?(:reject_publish, @build_list)
|
||||
- if BuildList::HUMAN_STATUSES[@build_list.status].in? [:build_started, :build_error, :success]
|
||||
= render :partial => 'projects/build_lists/log'
|
||||
|
||||
|
||||
- if (can_publish = @build_list.can_publish? && can?(:publish, @build_list)) || (can_reject = @build_list.can_reject_publish? && can?(:reject_publish, @build_list))
|
||||
.hr
|
||||
= submit_tag t("layout.publish"), :confirm => t("layout.confirm"), :name => 'publish' if can_publish
|
||||
= submit_tag t("layout.reject_publish"), :confirm => t("layout.confirm"), :name => 'reject_publish' if can_reject
|
||||
|
||||
.hr
|
||||
%h3= t("layout.build_lists.items_header")
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
en:
|
||||
layout:
|
||||
autoreload_page: Update page automatically
|
||||
autoreload_log: Update log every
|
||||
word_wrap: Word wrap
|
||||
read_more: Read more
|
||||
turned_on: on
|
||||
turned_off: off
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
ru:
|
||||
layout:
|
||||
autoreload_page: Автоматически обновлять страницу
|
||||
word_wrap: Перенос строк
|
||||
autoreload_log: Обновлять лог каждые
|
||||
read_more: Читать дальше
|
||||
turned_on: включены
|
||||
turned_off: выключены
|
||||
|
|
|
@ -118,6 +118,21 @@ en:
|
|||
project_not_found: Project not found
|
||||
project_version_not_found: Project version not found
|
||||
|
||||
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"
|
||||
30000: "30 s"
|
||||
60000: "1 m"
|
||||
300000: "5 m"
|
||||
600000: "10 m"
|
||||
900000: "15 m"
|
||||
|
||||
flash:
|
||||
build_list:
|
||||
saved: Build list for project version '%{project_version}', platform '%{build_for_platform}' and architecture '%{arch}' has been created successfully
|
||||
|
|
|
@ -117,6 +117,21 @@ ru:
|
|||
project_not_found: проект не найден
|
||||
project_version_not_found: версия не найдена
|
||||
|
||||
log:
|
||||
build_log: Лог сборки
|
||||
not_available: В настоящий момент лог недоступен.
|
||||
download: Загрузить лог
|
||||
autoreload: Обновлять лог каждые
|
||||
load_lines: Загружать последние %{count} строк
|
||||
|
||||
reload_times:
|
||||
10000: "10 сек"
|
||||
30000: "30 сек"
|
||||
60000: "1 мин"
|
||||
300000: "5 мин"
|
||||
600000: "10 мин"
|
||||
900000: "15 мин"
|
||||
|
||||
flash:
|
||||
build_list:
|
||||
saved: Билд лист для версии '%{project_version}', платформы '%{build_for_platform}' и архитектуры '%{arch}' создан успешно
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue