Revision e8d6d2d6
Added by Shlomi Zadok over 8 years ago
app/controllers/api/v1/reports_controller.rb | ||
---|---|---|
module Api
|
||
module V1
|
||
class ReportsController < V1::BaseController
|
||
before_filter :deprecated
|
||
before_filter :find_resource, :only => %w{show destroy}
|
||
before_filter :setup_search_options, :only => [:index, :last]
|
||
|
||
... | ... | |
param :per_page, String, :desc => "number of entries per request"
|
||
|
||
def index
|
||
@reports = Report.
|
||
authorized(:view_reports).
|
||
@reports = ConfigReport.
|
||
authorized(:view_config_reports).
|
||
my_reports.
|
||
includes(:logs => [:source, :message]).
|
||
search_for(*search_options).paginate(paginate_options)
|
||
... | ... | |
|
||
private
|
||
|
||
def deprecated
|
||
Foreman::Deprecation.api_deprecation_warning("Reports were renamed to ConfigReports")
|
||
end
|
||
|
||
def resource_scope(options = {})
|
||
options.merge!(:permission => :view_config_reports)
|
||
super(options).my_reports
|
||
end
|
||
|
||
def resource_class
|
||
ConfigReport
|
||
end
|
||
|
||
def action_permission
|
||
case params[:action]
|
||
when 'last'
|
app/controllers/api/v2/config_reports_controller.rb | ||
---|---|---|
module Api
|
||
module V2
|
||
class ConfigReportsController < V2::BaseController
|
||
include Api::Version2
|
||
include Foreman::Controller::SmartProxyAuth
|
||
|
||
before_filter :find_resource, :only => %w{show destroy}
|
||
before_filter :setup_search_options, :only => [:index, :last]
|
||
|
||
add_smart_proxy_filters :create, :features => Proc.new { ConfigReportImporter.authorized_smart_proxy_features }
|
||
|
||
api :GET, "/config_reports/", N_("List all reports")
|
||
param_group :search_and_pagination, ::Api::V2::BaseController
|
||
|
||
def index
|
||
@config_reports = resource_scope_for_index.my_reports.includes(:logs => [:source, :message])
|
||
@total = ConfigReport.my_reports.count
|
||
end
|
||
|
||
api :GET, "/config_reports/:id/", N_("Show a report")
|
||
param :id, :identifier, :required => true
|
||
|
||
def show
|
||
end
|
||
|
||
def_param_group :config_report do
|
||
param :config_report, Hash, :required => true, :action_aware => true do
|
||
param :host, String, :required => true, :desc => N_("Hostname or certname")
|
||
param :reported_at, String, :required => true, :desc => N_("UTC time of report")
|
||
param :status, Hash, :required => true, :desc => N_("Hash of status type totals")
|
||
param :metrics, Hash, :required => true, :desc => N_("Hash of report metrics, can be just {}")
|
||
param :logs, Array, :desc => N_("Optional array of log hashes")
|
||
end
|
||
end
|
||
|
||
api :POST, "/config_reports/", N_("Create a report")
|
||
param_group :config_report, :as => :create
|
||
|
||
def create
|
||
@config_report = ConfigReport.import(params[:config_report], detected_proxy.try(:id))
|
||
process_response @config_report.errors.empty?
|
||
rescue ::Foreman::Exception => e
|
||
render_message(e.to_s, :status => :unprocessable_entity)
|
||
end
|
||
|
||
api :DELETE, "/config_reports/:id/", N_("Delete a report")
|
||
param :id, String, :required => true
|
||
|
||
def destroy
|
||
process_response @config_report.destroy
|
||
end
|
||
|
||
api :GET, "/hosts/:host_id/config_reports/last", N_("Show the last report for a host")
|
||
param :id, :identifier, :required => true
|
||
|
||
def last
|
||
conditions = { :host_id => Host.authorized(:view_hosts).find(params[:host_id]).try(:id) } if params[:host_id].present?
|
||
max_id = resource_scope.where(conditions).maximum(:id)
|
||
@config_report = resource_scope.includes(:logs => [:message, :source]).find(max_id)
|
||
render :show
|
||
end
|
||
|
||
private
|
||
|
||
def resource_scope(options = {})
|
||
options.merge!(:permission => :view_config_reports)
|
||
super(options).my_reports
|
||
end
|
||
|
||
def action_permission
|
||
case params[:action]
|
||
when 'last'
|
||
'view'
|
||
else
|
||
super
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|
app/controllers/api/v2/reports_controller.rb | ||
---|---|---|
class ReportsController < V2::BaseController
|
||
include Api::Version2
|
||
include Foreman::Controller::SmartProxyAuth
|
||
|
||
before_filter :deprecated
|
||
before_filter :find_resource, :only => %w{show destroy}
|
||
before_filter :setup_search_options, :only => [:index, :last]
|
||
|
||
add_smart_proxy_filters :create, :features => Proc.new { ReportImporter.report_features }
|
||
add_smart_proxy_filters :create, :features => Proc.new { ConfigReportImporter.authorized_smart_proxy_features }
|
||
|
||
api :GET, "/reports/", N_("List all reports")
|
||
param_group :search_and_pagination, ::Api::V2::BaseController
|
||
|
||
def index
|
||
@reports = resource_scope_for_index.my_reports.includes(:logs => [:source, :message])
|
||
@total = Report.my_reports.count
|
||
@total = resource_class.my_reports.count
|
||
end
|
||
|
||
api :GET, "/reports/:id/", N_("Show a report")
|
||
... | ... | |
param_group :report, :as => :create
|
||
|
||
def create
|
||
@report = Report.import(params[:report], detected_proxy.try(:id))
|
||
@report = resource_class.import(params[:report], detected_proxy.try(:id))
|
||
process_response @report.errors.empty?
|
||
rescue ::Foreman::Exception => e
|
||
render_message(e.to_s, :status => :unprocessable_entity)
|
||
... | ... | |
|
||
private
|
||
|
||
def deprecated
|
||
Foreman::Deprecation.api_deprecation_warning("The resources /reports were moved to /config_reports. Please use the new path instead")
|
||
end
|
||
|
||
def resource_class
|
||
ConfigReport
|
||
end
|
||
|
||
def resource_scope(options = {})
|
||
super(options).my_reports
|
||
super(options.merge(:permission => :view_config_reports)).my_reports
|
||
end
|
||
|
||
def action_permission
|
app/controllers/config_reports_controller.rb | ||
---|---|---|
class ConfigReportsController < ApplicationController
|
||
include Foreman::Controller::AutoCompleteSearch
|
||
|
||
before_filter :setup_search_options, :only => :index
|
||
|
||
def index
|
||
@config_reports = resource_base.search_for(params[:search], :order => params[:order]).paginate(:page => params[:page], :per_page => params[:per_page]).includes(:host)
|
||
end
|
||
|
||
def show
|
||
# are we searching for the last report?
|
||
if params[:id] == "last"
|
||
conditions = { :host_id => Host.authorized(:view_hosts).find(params[:host_id]).try(:id) } if params[:host_id].present?
|
||
params[:id] = resource_base.where(conditions).maximum(:id)
|
||
end
|
||
|
||
return not_found if params[:id].blank?
|
||
|
||
@config_report = resource_base.includes(:logs => [:message, :source]).find(params[:id])
|
||
@offset = @config_report.reported_at - @config_report.created_at
|
||
end
|
||
|
||
def destroy
|
||
@config_report = resource_base.find(params[:id])
|
||
if @config_report.destroy
|
||
process_success(:success_msg => _("Successfully deleted report."), :success_redirect => config_reports_path)
|
||
else
|
||
process_error
|
||
end
|
||
end
|
||
|
||
private
|
||
|
||
def resource_base
|
||
super.my_reports
|
||
end
|
||
end
|
app/controllers/hosts_controller.rb | ||
---|---|---|
format.html do
|
||
@hosts = search.includes(included_associations).paginate(:page => params[:page])
|
||
# SQL optimizations queries
|
||
@last_report_ids = Report.where(:host_id => @hosts.map(&:id)).group(:host_id).maximum(:id)
|
||
@last_reports = Report.where(:id => @last_report_ids.values)
|
||
@last_report_ids = ConfigReport.where(:host_id => @hosts.map(&:id)).group(:host_id).maximum(:id)
|
||
@last_reports = ConfigReport.where(:id => @last_report_ids.values)
|
||
# rendering index page for non index page requests (out of sync hosts etc)
|
||
@hostgroup_authorizer = Authorizer.new(User.current, :collection => @hosts.map(&:hostgroup_id).compact.uniq)
|
||
render :index if title and (@title = title)
|
||
... | ... | |
@range = (params["range"].empty? ? 7 : params["range"].to_i)
|
||
|
||
# summary report text
|
||
@report_summary = Report.summarise(@range.days.ago, @host)
|
||
@report_summary = ConfigReport.summarise(@range.days.ago, @host)
|
||
end
|
||
format.yaml { render :text => @host.info.to_yaml }
|
||
format.json
|
app/controllers/reports_controller.rb | ||
---|---|---|
class ReportsController < ApplicationController
|
||
include Foreman::Controller::AutoCompleteSearch
|
||
|
||
before_filter :setup_search_options, :only => :index
|
||
|
||
def index
|
||
@reports = resource_base.search_for(params[:search], :order => params[:order]).paginate(:page => params[:page], :per_page => params[:per_page]).includes(:host)
|
||
end
|
||
|
||
def show
|
||
# are we searching for the last report?
|
||
if params[:id] == "last"
|
||
conditions = { :host_id => Host.authorized(:view_hosts).find(params[:host_id]).try(:id) } if params[:host_id].present?
|
||
params[:id] = resource_base.where(conditions).maximum(:id)
|
||
end
|
||
@report = resource_base.includes(:logs => [:message, :source]).find(params[:id])
|
||
@offset = @report.reported_at - @report.created_at
|
||
end
|
||
|
||
def destroy
|
||
@report = resource_base.find(params[:id])
|
||
if @report.destroy
|
||
process_success(:success_msg => _("Successfully deleted report."), :success_redirect => reports_path)
|
||
else
|
||
process_error
|
||
end
|
||
end
|
||
|
||
private
|
||
|
||
def resource_base
|
||
super.my_reports
|
||
end
|
||
end
|
app/helpers/dashboard_helper.rb | ||
---|---|---|
|
||
def latest_events
|
||
# 6 reports + header fits the events box nicely...
|
||
Report.authorized(:view_reports).my_reports.interesting.search_for('reported > "7 days ago"').limit(6).includes(:host)
|
||
ConfigReport.authorized(:view_config_reports).my_reports.interesting.search_for('reported > "7 days ago"').limit(6).includes(:host)
|
||
end
|
||
|
||
def translated_header(shortname, longname)
|
app/helpers/hosts_helper.rb | ||
---|---|---|
def last_report_column(record)
|
||
time = record.last_report? ? _("%s ago") % time_ago_in_words(record.last_report): ""
|
||
link_to_if_authorized(time,
|
||
hash_for_host_report_path(:host_id => record.to_param, :id => "last"),
|
||
hash_for_host_config_report_path(:host_id => record.to_param, :id => "last"),
|
||
last_report_tooltip(record))
|
||
end
|
||
|
||
... | ... | |
def show_appropriate_host_buttons(host)
|
||
[ link_to_if_authorized(_("Audits"), hash_for_host_audits_path(:host_id => @host), :title => _("Host audit entries"), :class => 'btn btn-default'),
|
||
(link_to_if_authorized(_("Facts"), hash_for_host_facts_path(:host_id => host), :title => _("Browse host facts"), :class => 'btn btn-default') if host.fact_values.any?),
|
||
(link_to_if_authorized(_("Reports"), hash_for_host_reports_path(:host_id => host), :title => _("Browse host reports"), :class => 'btn btn-default') if host.reports.any?),
|
||
(link_to_if_authorized(_("Reports"), hash_for_host_config_reports_path(:host_id => host), :title => _("Browse host config management reports"), :class => 'btn btn-default') if host.reports.any?),
|
||
(link_to(_("YAML"), externalNodes_host_path(:name => host), :title => _("Puppet external nodes YAML dump"), :class => 'btn btn-default') if SmartProxy.with_features("Puppet").any?)
|
||
].compact
|
||
end
|
app/helpers/reports_helper.rb | ||
---|---|---|
require 'ostruct'
|
||
module ReportsHelper
|
||
def reported_at_column(record)
|
||
link_to(_("%s ago") % time_ago_in_words(record.reported_at), report_path(record))
|
||
link_to(_("%s ago") % time_ago_in_words(record.reported_at), config_report_path(record))
|
||
end
|
||
|
||
def report_event_column(event, style = "")
|
||
... | ... | |
end
|
||
|
||
def logs_show
|
||
return unless @report.logs.size > 0
|
||
form_tag @report, :id => 'level_filter', :method => :get, :class => "form form-horizontal" do
|
||
return unless @config_report.logs.size > 0
|
||
form_tag config_report_path(@config_report), :id => 'level_filter', :method => :get, :class => "form form-horizontal" do
|
||
content_tag(:span, _("Show log messages:") + ' ') +
|
||
select(nil, 'level', [[_('All messages'), 'info'],[_('Notices, warnings and errors'), 'notice'],[_('Warnings and errors'), 'warning'],[_('Errors only'), 'error']],
|
||
{}, {:class=>"col-md-1 form-control", :onchange =>"filter_by_level(this);"})
|
app/mailers/host_mailer.rb | ||
---|---|---|
raise ::Foreman::Exception.new(N_("Must specify a valid user with email enabled")) unless (user=User.find(options[:user]))
|
||
hosts = Host::Managed.authorized_as(user, :view_hosts, Host)
|
||
time = options[:time] || 1.day.ago
|
||
host_data = Report.summarise(time, hosts.all).sort
|
||
host_data = ConfigReport.summarise(time, hosts.all).sort
|
||
|
||
total_metrics = load_metrics(host_data)
|
||
total = 0
|
app/models/concerns/configuration_status_scoped_search.rb | ||
---|---|---|
|
||
module ClassMethods
|
||
def scoped_search_status(status, options)
|
||
options.merge!({ :offset => Report::METRIC.index(status.to_s), :word_size => Report::BIT_NUM })
|
||
options.merge!({ :offset => ConfigReport::METRIC.index(status.to_s), :word_size => ConfigReport::BIT_NUM })
|
||
scoped_search options
|
||
end
|
||
end
|
app/models/concerns/hostext/search.rb | ||
---|---|---|
scoped_search :on => :owner_type, :complete_value => true, :only_explicit => true
|
||
scoped_search :on => :owner_id, :complete_enabled => false, :only_explicit => true
|
||
|
||
scoped_search :in => :configuration_status_object, :on => :status, :offset => 0, :word_size => Report::BIT_NUM*4, :rename => :'status.interesting', :complete_value => {:true => true, :false => false}
|
||
scoped_search :in => :configuration_status_object, :on => :status, :offset => 0, :word_size => ConfigReport::BIT_NUM*4, :rename => :'status.interesting', :complete_value => {:true => true, :false => false}
|
||
scoped_search_status "applied", :in => :configuration_status_object, :on => :status, :rename => :'status.applied'
|
||
scoped_search_status "restarted", :in => :configuration_status_object, :on => :status, :rename => :'status.restarted'
|
||
scoped_search_status "failed", :in => :configuration_status_object, :on => :status, :rename => :'status.failed'
|
app/models/config_report.rb | ||
---|---|---|
class ConfigReport < Report
|
||
METRIC = %w[applied restarted failed failed_restarts skipped pending]
|
||
BIT_NUM = 6
|
||
MAX = (1 << BIT_NUM) -1 # maximum value per metric
|
||
|
||
scoped_search :on => :status, :offset => 0, :word_size => 4*BIT_NUM, :complete_value => {:true => true, :false => false}, :rename => :eventful
|
||
|
||
scoped_search_status 'applied', :on => :status, :rename => :applied
|
||
scoped_search_status 'restarted', :on => :status, :rename => :restarted
|
||
scoped_search_status 'failed', :on => :status, :rename => :failed
|
||
scoped_search_status 'failed_restarts', :on => :status, :rename => :failed_restarts
|
||
scoped_search_status 'skipped', :on => :status, :rename => :skipped
|
||
scoped_search_status 'pending', :on => :status, :rename => :pending
|
||
|
||
# search for a metric - e.g.:
|
||
# Report.with("failed") --> all reports which have a failed counter > 0
|
||
# Report.with("failed",20) --> all reports which have a failed counter > 20
|
||
scope :with, lambda { |*arg|
|
||
where("(#{report_status_column} >> #{HostStatus::ConfigurationStatus.bit_mask(arg[0].to_s)}) > #{arg[1] || 0}")
|
||
}
|
||
|
||
class << self
|
||
delegate :model_name, :to => :superclass
|
||
end
|
||
|
||
def self.import(report, proxy_id = nil)
|
||
ConfigReportImporter.import(report, proxy_id)
|
||
end
|
||
|
||
# puppet report status table column name
|
||
def self.report_status_column
|
||
"status"
|
||
end
|
||
|
||
# a method that save the report values (e.g. values from METRIC)
|
||
# it is not supported to edit status values after it has been written once.
|
||
def status=(st)
|
||
s = case st
|
||
when Integer, Fixnum
|
||
st
|
||
when Hash
|
||
ConfigReportStatusCalculator.new(:counters => st).calculate
|
||
else
|
||
raise Foreman::Exception(N_('Unsupported report status format'))
|
||
end
|
||
write_attribute(:status, s)
|
||
end
|
||
|
||
def config_retrieval
|
||
metrics[:time][:config_retrieval].round(2) rescue 0
|
||
end
|
||
|
||
def runtime
|
||
(metrics[:time][:total] || metrics[:time].values.sum).round(2) rescue 0
|
||
end
|
||
|
||
# returns a hash of hosts and their recent reports metric counts which have values
|
||
# e.g. non zero metrics.
|
||
# first argument is time range, everything afterwards is a host list.
|
||
# TODO: improve SQL query (so its not N+1 queries)
|
||
def self.summarise(time = 1.day.ago, *hosts)
|
||
list = {}
|
||
raise ::Foreman::Exception.new(N_("invalid host list")) unless hosts
|
||
hosts.flatten.each do |host|
|
||
# set default of 0 per metric
|
||
metrics = {}
|
||
METRIC.each {|m| metrics[m] = 0 }
|
||
host.reports.recent(time).select(:status).each do |r|
|
||
metrics.each_key do |m|
|
||
metrics[m] += r.status_of(m)
|
||
end
|
||
end
|
||
list[host.name] = {:metrics => metrics, :id => host.id} if metrics.values.sum > 0
|
||
end
|
||
list
|
||
end
|
||
|
||
def summary_status
|
||
return _("Failed") if error?
|
||
return _("Modified") if changes?
|
||
_("Success")
|
||
end
|
||
|
||
delegate :error?, :changes?, :pending?, :status, :status_of, :to => :calculator
|
||
delegate(*METRIC, :to => :calculator)
|
||
|
||
def calculator
|
||
ConfigReportStatusCalculator.new(:bit_field => read_attribute(self.class.report_status_column))
|
||
end
|
||
end
|
app/models/host/managed.rb | ||
---|---|---|
has_many :host_classes, :foreign_key => :host_id
|
||
has_many :puppetclasses, :through => :host_classes, :dependent => :destroy
|
||
belongs_to :hostgroup
|
||
has_many :reports, :foreign_key => :host_id
|
||
has_one :last_report_object, :foreign_key => :host_id, :order => "#{Report.table_name}.id DESC", :class_name => 'Report'
|
||
has_many :reports, :foreign_key => :host_id, :class_name => 'ConfigReport'
|
||
has_one :last_report_object, :foreign_key => :host_id, :order => "#{Report.table_name}.id DESC", :class_name => 'ConfigReport'
|
||
has_many :host_parameters, :dependent => :destroy, :foreign_key => :reference_id, :inverse_of => :host
|
||
has_many :parameters, :dependent => :destroy, :foreign_key => :reference_id, :class_name => "HostParameter"
|
||
accepts_nested_attributes_for :host_parameters, :allow_destroy => true
|
||
... | ... | |
self.owner = oid
|
||
end
|
||
|
||
def clearReports
|
||
def clear_reports
|
||
# Remove any reports that may be held against this host
|
||
Report.where("host_id = #{id}").delete_all
|
||
end
|
||
|
||
def clearFacts
|
||
def clear_facts
|
||
FactValue.where("host_id = #{id}").delete_all
|
||
end
|
||
|
||
def clear_data_on_build
|
||
return unless respond_to?(:old) && old && build? && !old.build?
|
||
clearFacts
|
||
clearReports
|
||
clear_facts
|
||
clear_reports
|
||
end
|
||
|
||
def set_token
|
||
... | ... | |
end
|
||
|
||
def puppet_status
|
||
Foreman::Deprecation.deprecation_warning('1.12', 'Host#puppet_status has been deprecated, you should use configuration_status')
|
||
Foreman::Deprecation.deprecation_warning('1.13', 'Host#puppet_status has been deprecated, you should use configuration_status')
|
||
configuration_status
|
||
end
|
||
|
app/models/host_status/configuration_status.rb | ||
---|---|---|
module HostStatus
|
||
class ConfigurationStatus < Status
|
||
delegate :error?, :changes?, :pending?, :to => :calculator
|
||
delegate(*Report::METRIC, :to => :calculator)
|
||
delegate(*ConfigReport::METRIC, :to => :calculator)
|
||
|
||
def last_report
|
||
self.last_report = host.last_report_object unless @last_report_set
|
||
... | ... | |
end
|
||
|
||
def self.bit_mask(config_status)
|
||
"#{Report::BIT_NUM * Report::METRIC.index(config_status)} & #{Report::MAX}"
|
||
"#{ConfigReport::BIT_NUM * ConfigReport::METRIC.index(config_status)} & #{ConfigReport::MAX}"
|
||
end
|
||
|
||
private
|
||
... | ... | |
end
|
||
|
||
def calculator
|
||
ReportStatusCalculator.new(:bit_field => status)
|
||
ConfigReportStatusCalculator.new(:bit_field => status)
|
||
end
|
||
end
|
||
end
|
app/models/report.rb | ||
---|---|---|
class Report < ActiveRecord::Base
|
||
METRIC = %w[applied restarted failed failed_restarts skipped pending]
|
||
BIT_NUM = 6
|
||
MAX = (1 << BIT_NUM) -1 # maximum value per metric
|
||
LOG_LEVELS = %w[debug info notice warning err alert emerg crit]
|
||
|
||
include Foreman::STI
|
||
include Authorizable
|
||
include ConfigurationStatusScopedSearch
|
||
|
||
... | ... | |
has_one :hostgroup, :through => :host
|
||
|
||
validates :host_id, :status, :presence => true
|
||
validates :reported_at, :presence => true, :uniqueness => {:scope => :host_id}
|
||
validates :reported_at, :presence => true, :uniqueness => {:scope => [:host_id, :type]}
|
||
|
||
scoped_search :in => :host, :on => :name, :complete_value => true, :rename => :host
|
||
scoped_search :in => :environment, :on => :name, :complete_value => true, :rename => :environment
|
||
... | ... | |
scoped_search :in => :hostgroup, :on => :title, :complete_value => true, :rename => :hostgroup_title
|
||
|
||
scoped_search :on => :reported_at, :complete_value => true, :default_order => :desc, :rename => :reported, :only_explicit => true
|
||
scoped_search :on => :status, :offset => 0, :word_size => 4*BIT_NUM, :complete_value => {:true => true, :false => false}, :rename => :eventful
|
||
|
||
scoped_search_status 'applied', :on => :status, :rename => :applied
|
||
scoped_search_status 'restarted', :on => :status, :rename => :restarted
|
||
scoped_search_status 'failed', :on => :status, :rename => :failed
|
||
scoped_search_status 'failed_restarts', :on => :status, :rename => :failed_restarts
|
||
scoped_search_status 'skipped', :on => :status, :rename => :skipped
|
||
scoped_search_status 'pending', :on => :status, :rename => :pending
|
||
|
||
# search for a metric - e.g.:
|
||
# Report.with("failed") --> all reports which have a failed counter > 0
|
||
# Report.with("failed",20) --> all reports which have a failed counter > 20
|
||
scope :with, lambda { |*arg|
|
||
where("(#{report_status} >> #{HostStatus::ConfigurationStatus.bit_mask(arg[0].to_s)}) > #{arg[1] || 0}")
|
||
}
|
||
|
||
# returns reports for hosts in the User's filter set
|
||
scope :my_reports, lambda {
|
||
... | ... | |
# with_changes
|
||
scope :interesting, -> { where("status <> 0") }
|
||
|
||
# a method that save the report values (e.g. values from METRIC)
|
||
# it is not supported to edit status values after it has been written once.
|
||
def status=(st)
|
||
s = case st
|
||
when Integer, Fixnum
|
||
st
|
||
when Hash
|
||
ReportStatusCalculator.new(:counters => st).calculate
|
||
else
|
||
raise Foreman::Exception(N_('Unsupported report status format'))
|
||
end
|
||
write_attribute(:status, s)
|
||
end
|
||
|
||
# extracts serialized metrics and keep them as a hash_with_indifferent_access
|
||
def metrics
|
||
return {} if read_attribute(:metrics).nil?
|
||
YAML.load(read_attribute(:metrics)).with_indifferent_access
|
||
end
|
||
|
||
... | ... | |
"#{host.name} / #{reported_at}"
|
||
end
|
||
|
||
def config_retrieval
|
||
metrics[:time][:config_retrieval].round(2) rescue 0
|
||
end
|
||
|
||
def runtime
|
||
(metrics[:time][:total] || metrics[:time].values.sum).round(2) rescue 0
|
||
end
|
||
|
||
def self.import(report, proxy_id = nil)
|
||
ReportImporter.import(report, proxy_id)
|
||
end
|
||
|
||
# returns a hash of hosts and their recent reports metric counts which have values
|
||
# e.g. non zero metrics.
|
||
# first argument is time range, everything afterwards is a host list.
|
||
# TODO: improve SQL query (so its not N+1 queries)
|
||
def self.summarise(time = 1.day.ago, *hosts)
|
||
list = {}
|
||
raise ::Foreman::Exception.new(N_("invalid host list")) unless hosts
|
||
hosts.flatten.each do |host|
|
||
# set default of 0 per metric
|
||
metrics = {}
|
||
METRIC.each {|m| metrics[m] = 0 }
|
||
host.reports.recent(time).select(:status).each do |r|
|
||
metrics.each_key do |m|
|
||
metrics[m] += r.status_of(m)
|
||
end
|
||
end
|
||
list[host.name] = {:metrics => metrics, :id => host.id} if metrics.values.sum > 0
|
||
end
|
||
list
|
||
Foreman::Deprecation.deprecation_warning('1.13', "Report model has turned to be STI, please use child classes")
|
||
ConfigReportImporter.import(report, proxy_id)
|
||
end
|
||
|
||
# add sort by report time
|
||
... | ... | |
cond = "reports.created_at < \'#{(Time.now.utc - timerange).to_formatted_s(:db)}\'"
|
||
cond += " and reports.status = #{status}" unless status.nil?
|
||
|
||
Log.joins(:report).where(:report_id => Report.where(cond)).delete_all
|
||
Log.joins(:report).where(:report_id => where(cond)).delete_all
|
||
Message.where("id not IN (#{Log.unscoped.select('DISTINCT message_id').to_sql})").delete_all
|
||
Source.where("id not IN (#{Log.unscoped.select('DISTINCT source_id').to_sql})").delete_all
|
||
count = Report.where(cond).delete_all
|
||
logger.info Time.now.to_s + ": Expired #{count} Reports"
|
||
count = where(cond).delete_all
|
||
logger.info Time.now.to_s + ": Expired #{count} #{to_s.underscore.humanize.pluralize}"
|
||
count
|
||
end
|
||
|
||
... | ... | |
def no_report
|
||
false
|
||
end
|
||
|
||
def summaryStatus
|
||
return _("Failed") if error?
|
||
return _("Modified") if changes?
|
||
_("Success")
|
||
end
|
||
|
||
# puppet report status table column name
|
||
def self.report_status
|
||
"status"
|
||
end
|
||
|
||
delegate :error?, :changes?, :pending?, :status, :status_of, :to => :calculator
|
||
delegate(*METRIC, :to => :calculator)
|
||
|
||
def calculator
|
||
ReportStatusCalculator.new(:bit_field => read_attribute(self.class.report_status))
|
||
end
|
||
end
|
app/services/config_report_importer.rb | ||
---|---|---|
class ConfigReportImporter < ReportImporter
|
||
def self.authorized_smart_proxy_features
|
||
@authorized_smart_proxy_features ||= super + ['Puppet']
|
||
end
|
||
|
||
def report_name_class
|
||
ConfigReport
|
||
end
|
||
|
||
private
|
||
|
||
def create_report_and_logs
|
||
super
|
||
return report unless report.persisted?
|
||
# we update our host record, so we won't need to lookup the report information just to display the host list / info
|
||
host.update_attribute(:last_report, time) if host.last_report.nil? or host.last_report.utc < time
|
||
# Store all Puppet message logs
|
||
import_log_messages
|
||
# Check for errors
|
||
notify_on_report_error(:puppet_error_state)
|
||
end
|
||
|
||
def report_status
|
||
ConfigReportStatusCalculator.new(:counters => raw['status']).calculate
|
||
end
|
||
end
|
app/services/config_report_status_calculator.rb | ||
---|---|---|
class ConfigReportStatusCalculator
|
||
# converts a counters hash into a bit field
|
||
# expects a metrics_to_hash kind of counters
|
||
# see the report_processor for the implementation
|
||
def initialize(options = {})
|
||
@counters = options[:counters] || {}
|
||
@raw_status = options[:bit_field] || 0
|
||
end
|
||
|
||
# calculates the raw_status based on counters
|
||
def calculate
|
||
@raw_status = 0
|
||
counters.each do |type, value|
|
||
value = value.to_i # JSON does everything as strings
|
||
value = ConfigReport::MAX if value > ConfigReport::MAX # we store up to 2^BIT_NUM -1 values as we want to use only BIT_NUM bits.
|
||
@raw_status |= value << (ConfigReport::BIT_NUM * ConfigReport::METRIC.index(type))
|
||
end
|
||
raw_status
|
||
end
|
||
|
||
# returns metrics (counters) based on raw_status (aka bit field)
|
||
# to get status of specific metric, @see #status_of
|
||
def status
|
||
@status ||= begin
|
||
calculate if raw_status == 0
|
||
counters = Hash.new(0)
|
||
ConfigReport::METRIC.each do |m|
|
||
counters[m] = (raw_status || 0) >> (ConfigReport::BIT_NUM * ConfigReport::METRIC.index(m)) & ConfigReport::MAX
|
||
end
|
||
counters
|
||
end
|
||
end
|
||
|
||
def status_of(counter)
|
||
raise(Foreman::Exception.new(N_("invalid type %s"), counter)) unless ConfigReport::METRIC.include?(counter)
|
||
status[counter]
|
||
end
|
||
|
||
# returns true if total error metrics are > 0
|
||
def error?
|
||
status_of('failed') + status_of('failed_restarts') > 0
|
||
end
|
||
|
||
# returns true if total action metrics are > 0
|
||
def changes?
|
||
status_of('applied') + status_of('restarted') > 0
|
||
end
|
||
|
||
# returns true if there are any changes pending
|
||
def pending?
|
||
status_of('pending') > 0
|
||
end
|
||
|
||
# generate dynamically methods for all metrics
|
||
# e.g. applied failed ...
|
||
ConfigReport::METRIC.each do |method|
|
||
define_method method do
|
||
status_of(method)
|
||
end
|
||
end
|
||
|
||
private
|
||
|
||
attr_reader :raw_status, :counters
|
||
end
|
app/services/foreman/access_permissions.rb | ||
---|---|---|
}
|
||
end
|
||
|
||
permission_set.security_block :reports do |map|
|
||
map.permission :view_reports, {:reports => [:index, :show, :auto_complete_search],
|
||
:"api/v1/reports" => [:index, :show, :last],
|
||
:"api/v2/reports" => [:index, :show, :last]
|
||
}
|
||
map.permission :destroy_reports, {:reports => [:destroy],
|
||
:"api/v1/reports" => [:destroy],
|
||
:"api/v2/reports" => [:destroy]
|
||
}
|
||
map.permission :upload_reports, {:"api/v2/reports" => [:create] }
|
||
permission_set.security_block :config_reports do |map|
|
||
map.permission :view_config_reports, {:"api/v1/reports" => [:index, :show, :last],
|
||
:"api/v2/reports" => [:index, :show, :last],
|
||
:config_reports => [:index, :show, :auto_complete_search],
|
||
:"api/v2/config_reports" => [:index, :show, :last]
|
||
}
|
||
map.permission :destroy_config_reports, {:config_reports => [:destroy],
|
||
:"api/v1/reports" => [:destroy],
|
||
:"api/v2/reports" => [:destroy],
|
||
:"api/v2/config_reports" => [:destroy]
|
||
}
|
||
map.permission :upload_config_reports, {:"api/v2/reports" => [:create],
|
||
:"api/v2/config_reports" => [:create]}
|
||
end
|
||
|
||
permission_set.security_block :facts do |map|
|
app/services/menu/loader.rb | ||
---|---|---|
Manager.map :top_menu do |menu|
|
||
menu.sub_menu :monitor_menu, :caption => N_('Monitor') do
|
||
menu.item :dashboard, :caption => N_('Dashboard')
|
||
menu.item :reports, :caption => N_('Reports'),
|
||
:url_hash => {:controller => '/reports', :action => 'index', :search => 'eventful = true'}
|
||
menu.item :fact_values, :caption => N_('Facts')
|
||
menu.item :statistics, :caption => N_('Statistics')
|
||
menu.item :trends, :caption => N_('Trends')
|
||
menu.item :audits, :caption => N_('Audits')
|
||
menu.divider :caption => N_('Reports')
|
||
menu.item :reports, :caption => N_('Config management'),
|
||
:url_hash => {:controller => '/config_reports', :action => 'index', :search => 'eventful = true'}
|
||
menu.divider
|
||
end
|
||
|
||
menu.sub_menu :hosts_menu, :caption => N_('Hosts') do
|
app/services/report_importer.rb | ||
---|---|---|
delegate :logger, :to => :Rails
|
||
attr_reader :report
|
||
|
||
# When writing your own Report importer, provide feature(s) of authorized Smart Proxies
|
||
def self.authorized_smart_proxy_features
|
||
@authorized_smart_proxy_features ||= []
|
||
end
|
||
|
||
def self.register_smart_proxy_feature(feature)
|
||
@authorized_smart_proxy_features = (authorized_smart_proxy_features + [ feature ]).uniq
|
||
end
|
||
|
||
def self.unregister_smart_proxy_feature(feature)
|
||
@authorized_smart_proxy_features -= [ feature ]
|
||
end
|
||
|
||
def self.import(raw, proxy_id = nil)
|
||
importer = new(raw, proxy_id)
|
||
importer.import
|
||
importer.report
|
||
end
|
||
|
||
def self.report_features
|
||
['Puppet', 'Chef Proxy']
|
||
# to be overriden in children
|
||
def report_name_class
|
||
Foreman::Deprecation.deprecation_warning('1.13', "Report model has turned to be STI, please use child classes")
|
||
Report
|
||
end
|
||
|
||
def initialize(raw, proxy_id = nil)
|
||
... | ... | |
start_time = Time.now
|
||
logger.info "processing report for #{name}"
|
||
logger.debug { "Report: #{raw.inspect}" }
|
||
|
||
if host.new_record? && !Setting[:create_new_host_when_report_is_uploaded]
|
||
logger.info("skipping report for #{name} as its an unknown host and create_new_host_when_report_is_uploaded setting is disabled")
|
||
return Report.new
|
||
create_report_and_logs
|
||
if report.persisted?
|
||
logger.info("Imported report for #{name} in #{(Time.now - start_time).round(2)} seconds")
|
||
host.refresh_statuses
|
||
end
|
||
|
||
# convert report status to bit field
|
||
st = ReportStatusCalculator.new(:counters => raw['status']).calculate
|
||
|
||
# we update our host record, so we won't need to lookup the report information just to display the host list / info
|
||
host.last_report = time if host.last_report.nil? or host.last_report.utc < time
|
||
|
||
# if proxy authentication is enabled and we have no puppet proxy set, use it.
|
||
host.puppet_proxy_id ||= proxy_id
|
||
|
||
# we save the host without validation for two reasons:
|
||
# 1. It might be auto imported, therefore might not be valid (e.g. missing partition table etc)
|
||
# 2. We want this to be fast and light on the db.
|
||
# at this point, the report is important, not the host
|
||
host.save(:validate => false)
|
||
|
||
# and save our report
|
||
@report = Report.new(:host => host, :reported_at => time, :status => st, :metrics => raw['metrics'])
|
||
return report unless report.save
|
||
# Store all Puppet message logs
|
||
import_log_messages
|
||
# Check for errors
|
||
inspect_report
|
||
logger.info("Imported report for #{name} in #{(Time.now - start_time).round(2)} seconds")
|
||
|
||
host.refresh_statuses
|
||
end
|
||
|
||
private
|
||
... | ... | |
end
|
||
end
|
||
|
||
def inspect_report
|
||
def report_status
|
||
raise NotImplementedError
|
||
end
|
||
|
||
def notify_on_report_error(mail_error_state)
|
||
if report.error?
|
||
# found a report with errors
|
||
# notify via email IF enabled is set to true
|
||
... | ... | |
owners = host.owner.present? ? host.owner.recipients_for(:puppet_error_state) : []
|
||
if owners.present?
|
||
logger.debug "sending alert to #{owners.map(&:login).join(',')}"
|
||
MailNotification[:puppet_error_state].deliver(report, :users => owners)
|
||
MailNotification[mail_error_state].deliver(report, :users => owners)
|
||
else
|
||
logger.debug "no owner or recipients for alert on #{name}"
|
||
end
|
||
end
|
||
end
|
||
|
||
def create_report_and_logs
|
||
if host.new_record? && !Setting[:create_new_host_when_report_is_uploaded]
|
||
logger.info("skipping report for #{name} as its an unknown host and create_new_host_when_report_is_uploaded setting is disabled")
|
||
@report = report_name_class.new
|
||
return @report
|
||
end
|
||
|
||
# we save the host without validation for two reasons:
|
||
# 1. It might be auto imported, therefore might not be valid (e.g. missing partition table etc)
|
||
# 2. We want this to be fast and light on the db.
|
||
# at this point, the report is important, not the host
|
||
host.save(:validate => false)
|
||
|
||
status = report_status
|
||
|
||
# and save our report
|
||
@report = report_name_class.new(:host => host, :reported_at => time, :status => status, :metrics => raw['metrics'])
|
||
@report.save
|
||
@report
|
||
end
|
||
end
|
app/services/report_status_calculator.rb | ||
---|---|---|
class ReportStatusCalculator
|
||
# converts a counters hash into a bit field
|
||
# expects a metrics_to_hash kind of counters
|
||
# see the report_processor for the implementation
|
||
def initialize(options = {})
|
||
@counters = options[:counters] || {}
|
||
@raw_status = options[:bit_field] || 0
|
||
end
|
||
|
||
# calculates the raw_status based on counters
|
||
def calculate
|
||
@raw_status = 0
|
||
counters.each do |type, value|
|
||
value = value.to_i # JSON does everything as strings
|
||
value = Report::MAX if value > Report::MAX # we store up to 2^BIT_NUM -1 values as we want to use only BIT_NUM bits.
|
||
@raw_status |= value << (Report::BIT_NUM * Report::METRIC.index(type))
|
||
end
|
||
raw_status
|
||
end
|
||
|
||
# returns metrics (counters) based on raw_status (aka bit field)
|
||
# to get status of specific metric, @see #status_of
|
||
def status
|
||
@status ||= begin
|
||
calculate if raw_status == 0
|
||
counters = Hash.new(0)
|
||
Report::METRIC.each do |m|
|
||
counters[m] = (raw_status || 0) >> (Report::BIT_NUM * Report::METRIC.index(m)) & Report::MAX
|
||
end
|
||
counters
|
||
end
|
||
end
|
||
|
||
def status_of(counter)
|
||
raise(Foreman::Exception.new(N_("invalid type %s"), counter)) unless Report::METRIC.include?(counter)
|
||
status[counter]
|
||
end
|
||
|
||
# returns true if total error metrics are > 0
|
||
def error?
|
||
status_of('failed') + status_of('failed_restarts') > 0
|
||
end
|
||
|
||
# returns true if total action metrics are > 0
|
||
def changes?
|
||
status_of('applied') + status_of('restarted') > 0
|
||
end
|
||
|
||
# returns true if there are any changes pending
|
||
def pending?
|
||
status_of('pending') > 0
|
||
end
|
||
|
||
# generate dynamically methods for all metrics
|
||
# e.g. applied failed ...
|
||
Report::METRIC.each do |method|
|
||
define_method method do
|
||
status_of(method)
|
||
end
|
||
end
|
||
|
||
private
|
||
|
||
attr_reader :raw_status, :counters
|
||
end
|
app/views/api/v1/reports/show.json.rabl | ||
---|---|---|
end
|
||
|
||
node :summary do |report|
|
||
report.summaryStatus
|
||
end
|
||
report.summary_status
|
||
end
|
app/views/api/v2/config_reports/base.json.rabl | ||
---|---|---|
object @config_report
|
||
|
||
attributes :id, :host_id, :host_name, :reported_at, :status
|
app/views/api/v2/config_reports/create.json.rabl | ||
---|---|---|
object @config_report
|
||
|
||
extends "api/v2/config_reports/show"
|
app/views/api/v2/config_reports/index.json.rabl | ||
---|---|---|
collection @config_reports
|
||
|
||
extends "api/v2/config_reports/main"
|
app/views/api/v2/config_reports/main.json.rabl | ||
---|---|---|
object @config_report
|
||
|
||
extends "api/v2/config_reports/base"
|
||
|
||
attributes :metrics, :created_at, :updated_at
|
app/views/api/v2/config_reports/show.json.rabl | ||
---|---|---|
object @config_report
|
||
|
||
extends "api/v2/config_reports/main"
|
||
|
||
child :logs do
|
||
child :source do
|
||
attribute :value => :source
|
||
end
|
||
child :message do
|
||
attribute :value => :message
|
||
end
|
||
attribute :level
|
||
end
|
||
|
||
node :summary do |config_report|
|
||
config_report.summary_status
|
||
end
|
app/views/api/v2/config_reports/update.json.rabl | ||
---|---|---|
object @config_report
|
||
|
||
extends "api/v2/config_reports/show"
|
app/views/api/v2/reports/show.json.rabl | ||
---|---|---|
end
|
||
|
||
node :summary do |report|
|
||
report.summaryStatus
|
||
report.summary_status
|
||
end
|
app/views/config_reports/_list.html.erb | ||
---|---|---|
<table class="table table-bordered table-striped ellipsis">
|
||
<thead>
|
||
<tr>
|
||
<% unless params[:host_id] %>
|
||
<th><%= sort :host, :as => _("Host") %></th>
|
||
<% end %>
|
||
<th><%= sort :reported, :as => _("Last report") %></th>
|
||
<th class="col-md-1"><%= sort :applied, :as => _("Applied") %></th>
|
||
<th class="col-md-1"><%= sort :restarted, :as => _("Restarted") %></th>
|
||
<th class="col-md-1"><%= sort :failed, :as => _("Failed") %></th>
|
||
<th class="col-md-1"><%= sort :failed_restarts, :as => _("Restart<br>Failures").html_safe %></th>
|
||
<th class="col-md-1"><%= sort :skipped, :as => _("Skipped") %></th>
|
||
<th class="col-md-1"><%= sort :pending, :as => _("Pending") %></th>
|
||
<th class="col-md-1"></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<% for report in @config_reports %>
|
||
<tr>
|
||
<% if params[:host_id].nil? %>
|
||
<% if report.host.nil? %>
|
||
<td></td>
|
||
<% else %>
|
||
<td><%= link_to report.host, host_config_reports_path(report.host) %></td>
|
||
<% end %>
|
||
<% end %>
|
||
<td><%= reported_at_column(report) %></td>
|
||
<td><%= report_event_column(report.applied, "label-info") %></td>
|
||
<td><%= report_event_column(report.restarted, "label-info") %></td>
|
||
<td><%= report_event_column(report.failed, "label-danger") %></td>
|
||
<td><%= report_event_column(report.failed_restarts, "label-warning") %></td>
|
||
<td><%= report_event_column(report.skipped, "label-info") %></td>
|
||
<td><%= report_event_column(report.pending, "label-info") %></td>
|
||
<td align="right">
|
||
<%= display_delete_if_authorized hash_for_config_report_path(:id => report).merge(:auth_object => report, :authorizer => authorizer),
|
||
:confirm => _("Delete report for %s?") % report.host.try(:name) %>
|
||
</td>
|
||
</tr>
|
||
<% end %>
|
||
</tbody>
|
||
</table>
|
||
<%= will_paginate_with_info @config_reports %>
|
app/views/config_reports/_metrics.html.erb | ||
---|---|---|
<% @totaltime = metrics.delete('total') %>
|
||
<% metrics.delete_if{ |k,v| v < 0.001 } %>
|
||
<div class="row">
|
||
<div class="col-md-4">
|
||
<div class="stats-well">
|
||
<h4 class="ca" ><%= _('Report Metrics') %></h4>
|
||
<div style="margin-top:50px;padding-bottom: 40px;">
|
||
<%= flot_pie_chart("metrics" ,_("Report Metrics"), metrics, :class => "statistics-pie small")%>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-4">
|
||
<div class="stats-well">
|
||
<h4 class="ca" ><%= _('Report Status') %></h4>
|
||
<%= flot_bar_chart("status" ,"", _("Number of Events"), status, :class => "statistics-bar")%>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-4">
|
||
<table class='table table-bordered table-striped' style="height: 398px;">
|
||
<tbody>
|
||
<% metrics.sort.each do |title, value|%>
|
||
<tr>
|
||
<td class="break-me">
|
||
<%= title %>
|
||
</td>
|
||
<td>
|
||
<%= metric value %>
|
||
</td>
|
||
</tr>
|
||
<% end %>
|
||
</tbody>
|
||
<tfoot>
|
||
<tr>
|
||
<th><%= _("Total") %></th><th><%= metric (@totaltime || @config_report.runtime) %></th>
|
||
</tr>
|
||
</tfoot>
|
||
</table>
|
||
</div>
|
||
</div>
|
app/views/config_reports/_output.html.erb | ||
---|---|---|
<table id='report_log' class="table table-bordered table-striped">
|
||
<thead>
|
||
<tr>
|
||
<th><%= _("Level") %></th>
|
||
<th><%= _("Resource") %></th>
|
||
<th><%= _("message") %></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<% logs.each do |log| %>
|
||
<tr>
|
||
<td><span <%= report_tag log.level %>><%= h log.level %></span></td>
|
||
<td class="break-me"><%= h log.source %></td>
|
||
<td class="break-me"><%= h log.message %></td>
|
||
</tr>
|
||
<% end %>
|
||
<tr id='ntsh' <%= "style='display: none;'".html_safe if logs.size > 0%>><td colspan="3">
|
||
<%= _("Nothing to show") %>
|
||
</td></tr>
|
||
</tbody>
|
||
</table>
|
app/views/config_reports/index.html.erb | ||
---|---|---|
<% title _("Reports") %>
|
||
<% title_actions documentation_button('3.5.4PuppetReports') %>
|
||
<%= render :partial => 'list' %>
|
app/views/config_reports/show.html.erb | ||
---|---|---|
<%= javascript 'reports', 'ace/ace' %>
|
||
<% title "#{@config_report.host}"%>
|
||
|
||
<p class='ra'> <%= _("Reported at %s ") % @config_report.reported_at %> </p>
|
||
<% if @offset > 10 %>
|
||
<div class="alert alert-block alert-danger alert-dismissable">
|
||
<%= alert_close %>
|
||
<h3><%= _("Host times seems to be adrift!") %></h3>
|
||
<%= (_("Host reported time is <em>%s</em>") % @config_report.reported_at).html_safe %> <br/>
|
||
<%= (_("Foreman report creation time is <em>%s</em>") % @config_report.created_at).html_safe %> <br/>
|
||
<%= (_("Which is an offset of <b>%s</b>") % distance_of_time_in_words(@config_report.reported_at, @config_report.created_at, true)).html_safe %>
|
||
</div>
|
||
<% end %>
|
||
|
||
<% content_for(:search_bar) {logs_show} %>
|
||
|
||
<%= render 'output', :logs => @config_report.logs%>
|
||
<%= render 'metrics', :status => @config_report.status, :metrics => @config_report.metrics["time"] if @config_report.metrics["time"] %>
|
||
|
||
<%= title_actions link_to(_('Back'), :back),
|
||
display_delete_if_authorized(hash_for_config_report_path(:id => @config_report), :class=> "btn btn-danger"),
|
||
link_to(_("Host details"), @config_report.host),
|
||
link_to(_("Other reports for this host"), host_config_reports_path(@config_report.host))
|
||
%>
|
||
|
||
<div id="diff-modal" class="modal fade">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||
<h4 class="modal-title"><%= _("Diff View") %></h4>
|
||
</div>
|
||
<div id="diff-modal-editor" class="modal-body">
|
||
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-default" data-dismiss="modal"><%= _("Close") %></button>
|
||
</div>
|
||
</div><!-- /.modal-content -->
|
||
</div><!-- /.modal-dialog -->
|
||
</div><!-- /.modal -->
|
app/views/config_reports/welcome.html.erb | ||
---|---|---|
<% title _("Puppet Reports") %>
|
||
<div id="welcome">
|
||
<p>
|
||
<%= _("You don't seem to have any reports, if you wish to configure Puppet to forward it reports to Foreman, please follow") %>
|
||
<%= link_to _("setting up reporting"), "http://www.theforeman.org/manuals/#{SETTINGS[:version].short}/index.html#3.5.4PuppetReports", :rel => "external" %>
|
||
<%= _("and") %>
|
||
<%= link_to _("e-mail reporting"), "http://theforeman.org/projects/foreman/wiki/Mail_Notifications", :rel => "external" %>.
|
||
</p>
|
||
|
||
<p><%= _('This page will self destruct once a report comes in.') %></p>
|
||
</div>
|
app/views/dashboard/_reports_widget.html.erb | ||
---|---|---|
<tbody>
|
||
<% events.each do |report| %>
|
||
<tr>
|
||
<td><%= link_to trunc_with_tooltip(report.host, 14), host_reports_path(report.host) %></td>
|
||
<td><%= link_to trunc_with_tooltip(report.host, 14), host_config_reports_path(report.host) %></td>
|
||
<td><%= report_event_column(report.applied, "label-info") %></td>
|
||
<td><%= report_event_column(report.restarted, "label-info") %></td>
|
||
<td><%= report_event_column(report.failed, "label-danger") %></td>
|
app/views/host_mailer/_active_hosts.html.erb | ||
---|---|---|
<td style="<%= td_style %>">
|
||
<% end %>
|
||
<% if v > 0 %>
|
||
<%= link_to v, reports_path(:search=>"host = #{host} and #{m} > 0", :host => @url.host, :port => @url.port, :protocol => @url.scheme, :only_path => false) %>
|
||
<%= link_to v, config_reports_path(:search=>"host = #{host} and #{m} > 0", :host => @url.host, :port => @url.port, :protocol => @url.scheme, :only_path => false) %>
|
||
<% else %>
|
||
<%= v %>
|
||
<% end %>
|
app/views/reports/_list.html.erb | ||
---|---|---|
<table class="table table-bordered table-striped ellipsis">
|
||
<thead>
|
||
<tr>
|
||
<% unless params[:host_id] %>
|
||
<th><%= sort :host, :as => _("Host") %></th>
|
||
<% end %>
|
||
<th><%= sort :reported, :as => _("Last report") %></th>
|
||
<th class="col-md-1"><%= sort :applied, :as => _("Applied") %></th>
|
||
<th class="col-md-1"><%= sort :restarted, :as => _("Restarted") %></th>
|
||
<th class="col-md-1"><%= sort :failed, :as => _("Failed") %></th>
|
||
<th class="col-md-1"><%= sort :failed_restarts, :as => _("Restart<br>Failures").html_safe %></th>
|
||
<th class="col-md-1"><%= sort :skipped, :as => _("Skipped") %></th>
|
||
<th class="col-md-1"><%= sort :pending, :as => _("Pending") %></th>
|
||
<th class="col-md-1"></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<% for report in @reports %>
|
||
<tr>
|
||
<% if params[:host_id].nil? %>
|
||
<% if report.host.nil? %>
|
||
<td></td>
|
||
<% else %>
|
||
<td><%= link_to report.host, host_reports_path(report.host) %></td>
|
||
<% end %>
|
||
<% end %>
|
||
<td><%= reported_at_column(report) %></td>
|
||
<td><%= report_event_column(report.applied, "label-info") %></td>
|
||
<td><%= report_event_column(report.restarted, "label-info") %></td>
|
||
<td><%= report_event_column(report.failed, "label-danger") %></td>
|
||
<td><%= report_event_column(report.failed_restarts, "label-warning") %></td>
|
||
<td><%= report_event_column(report.skipped, "label-info") %></td>
|
||
<td><%= report_event_column(report.pending, "label-info") %></td>
|
||
<td align="right">
|
||
<%= display_delete_if_authorized hash_for_report_path(:id => report).merge(:auth_object => report, :authorizer => authorizer),
|
||
:confirm => _("Delete report for %s?") % report.host.try(:name) %>
|
||
</td>
|
||
</tr>
|
||
<% end %>
|
||
</tbody>
|
||
</table>
|
||
<%= will_paginate_with_info @reports %>
|
app/views/reports/_metrics.html.erb | ||
---|---|---|
<% @totaltime = metrics.delete('total') %>
|
||
<% metrics.delete_if{ |k,v| v < 0.001 } %>
|
||
<div class="row">
|
||
<div class="col-md-4">
|
||
<div class="stats-well">
|
||
<h4 class="ca" ><%= _('Report Metrics') %></h4>
|
||
<div style="margin-top:50px;padding-bottom: 40px;">
|
||
<%= flot_pie_chart("metrics" ,_("Report Metrics"), metrics, :class => "statistics-pie small")%>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-4">
|
||
<div class="stats-well">
|
||
<h4 class="ca" ><%= _('Report Status') %></h4>
|
||
<%= flot_bar_chart("status" ,"", _("Number of Events"), status, :class => "statistics-bar")%>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-4">
|
||
<table class='table table-bordered table-striped' style="height: 398px;">
|
||
<tbody>
|
||
<% metrics.sort.each do |title, value|%>
|
||
<tr>
|
||
<td class="break-me">
|
||
<%= title %>
|
||
</td>
|
||
<td>
|
||
<%= metric value %>
|
||
</td>
|
||
</tr>
|
||
<% end %>
|
||
</tbody>
|
||
<tfoot>
|
||
<tr>
|
||
<th><%= _("Total") %></th><th><%= metric (@totaltime || @report.runtime) %></th>
|
||
</tr>
|
||
</tfoot>
|
||
</table>
|
||
</div>
|
||
</div>
|
app/views/reports/_output.html.erb | ||
---|---|---|
<table id='report_log' class="table table-bordered table-striped">
|
||
<thead>
|
||
<tr>
|
||
<th><%= _("Level") %></th>
|
||
<th><%= _("Resource") %></th>
|
||
<th><%= _("message") %></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<% logs.each do |log| %>
|
||
<tr>
|
||
<td><span <%= report_tag log.level %>><%= h log.level %></span></td>
|
||
<td class="break-me"><%= h log.source %></td>
|
||
<td class="break-me"><%= h log.message %></td>
|
||
</tr>
|
||
<% end %>
|
||
<tr id='ntsh' <%= "style='display: none;'".html_safe if logs.size > 0%>><td colspan="3">
|
Also available in: Unified diff
fixes #4151 - enable reports STI
Permits subclassing of ReportImporter and Report to import and store
new types of reports associated to hosts.