diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 975cb36..ddda4a8 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -1,43 +1,53 @@
module ApplicationHelper
# Custom helper for better display of big numbers
# @example number_by_magnitude(4242)
# "4.2K"
#
# @param number[Numeric]
# @return [String] human friendly respresentation
def number_by_magnitude(number)
number_to_human(number, units: { thousand: :K, million: :M, billion: :G })
end
# Creates a bootstrap form-group div with an additional 'Add' button next to the select field
#
# @param object[ActiveRecord::Object] the form's subject
# @param resource[Symbol] the objects class
# @param attr[Symbol] the select box's attribute
# @param attr_name[String] the attribute's display name
# @param options[Array] the select box options
# @param path[String] the add button's path
def select_with_errors_and_button(object, resource, attr, attr_name, options, path)
has_errors = object.errors[attr].present?
content_tag(:div, class: "form-group #{' has-error' if has_errors }") do
attr_label = label(resource, attr, attr_name, class: 'control-label col-xs-4 required')
select_div = content_tag(:div, class: 'col-xs-6') do
select_part = select_tag([resource, attr].join('_').to_sym,
options,
name: "#{resource}[#{attr}]",
class: 'form-control'
)
if has_errors
select_part.concat(content_tag(:span, class: 'help-block') { object.errors[attr].first })
end
select_part
end
button_part = content_tag(:div, class: 'col-xs-1') do
link_to 'Add', path, class: 'btn btn-primary', role: 'button'
end
attr_label.concat(select_div).concat(button_part)
end
end
+
+ # Returns a style class depending on the given parameter
+ #
+ # @param status[Char]
+ def success_class(status)
+ case status
+ when 'T' then 'success'
+ when 'E' then 'danger'
+ end
+ end
end
diff --git a/app/models/client.rb b/app/models/client.rb
index 730f61e..e9d5bbd 100644
--- a/app/models/client.rb
+++ b/app/models/client.rb
@@ -1,73 +1,84 @@
class Client < ActiveRecord::Base
self.table_name = :Client
self.primary_key = :ClientId
alias_attribute :name, :Name
alias_attribute :uname, :Uname
alias_attribute :auto_prune, :AutoPrune
alias_attribute :file_retention, :FileRetention
alias_attribute :job_retention, :JobRetention
has_many :jobs, foreign_key: :ClientId
has_one :host, foreign_key: :name, primary_key: :Name
scope :for_user, ->(user_id) { joins(host: :users).where(users: { id: user_id }) }
DAY_SECS = 60 * 60 * 24
+ RECENT_JOBS_COUNT = 5
# Fetches the client's job_templates that are already persisted to
# Bacula's configuration
#
# @return [ActiveRecord::Relation] of `JobTemplate`
def persisted_jobs
host.job_templates.where(baculized: true).includes(:fileset, :schedule)
end
+ # Fetches the client's performed jobs in reverse chronological order
+ #
+ # @return [ActiveRecord::Relation] of `Job`
+ def recent_jobs
+ jobs.order(EndTime: :desc).limit(RECENT_JOBS_COUNT).includes(:file_set)
+ end
+
# Helper method. It shows the client's job retention,
# (which is expressed in seconds) in days.
#
# @return [Integer]
def job_retention_days
job_retention / DAY_SECS
end
# Helper method. It shows the client's file retention,
# (which is expressed in seconds) in days.
#
# @return [Integer]
def file_retention_days
file_retention / DAY_SECS
end
# Helper method for auto_prune
#
# @return [String] 'yes' or 'no'
def auto_prune_human
auto_prune == 1 ? 'yes' : 'no'
end
- def last_job_date
- jobs.maximum(:EndTime)
+ # Helper method for displayin the last job's datetime in a nice format.
+ def last_job_date_formatted
+ if job_time = jobs.backup_type.last.try(:end_time)
+ I18n.l(job_time, format: :long)
+ end
end
# Shows the total file size of the jobs that run for a specific client
#
# @return [Integer] Size in Bytes
def backup_jobs_size
jobs.backup_type.map(&:job_bytes).sum
end
# Shows the total files' count for the jobs that run for a specific client
#
# @return [Integer] File count
def files_count
jobs.map(&:job_files).sum
end
# Fetches the client's jobs that are running at the moment
#
# @return [Integer]
def running_jobs
jobs.running.count
end
end
diff --git a/app/models/job.rb b/app/models/job.rb
index 1403f47..7f5fc83 100644
--- a/app/models/job.rb
+++ b/app/models/job.rb
@@ -1,45 +1,97 @@
class Job < ActiveRecord::Base
self.table_name = :Job
self.primary_key = :JobId
alias_attribute :job_id, :JobId
alias_attribute :job, :Job
alias_attribute :name, :Name
alias_attribute :type, :Type
alias_attribute :level, :Level
alias_attribute :client_id, :ClientId
alias_attribute :job_status, :JobStatus
alias_attribute :sched_time, :SchedTime
alias_attribute :start_time, :StartTime
alias_attribute :end_time, :EndTime
alias_attribute :real_end_time, :RealEndTime
alias_attribute :job_t_date, :JobTDate
alias_attribute :vol_session_id, :VolSessionId
alias_attribute :vol_session_time, :VolSessionTime
alias_attribute :job_files, :JobFiles
alias_attribute :job_bytes, :JobBytes
alias_attribute :read_bytes, :ReadBytes
alias_attribute :job_errors, :JobErrors
alias_attribute :job_missing_files, :JobMissingFiles
alias_attribute :pool_id, :PoolId
alias_attribute :file_set_id, :FileSetId
alias_attribute :prior_job_id, :PriorJobId
alias_attribute :purged_files, :PurgedFiles
alias_attribute :has_base, :HasBase
alias_attribute :has_cache, :HasCache
alias_attribute :reviewed, :Reviewed
alias_attribute :comment, :Comment
belongs_to :pool, foreign_key: :PoolId
belongs_to :file_set, foreign_key: :FileSetId
belongs_to :client, foreign_key: :ClientId
has_many :bacula_files, foreign_key: :JobId
has_many :base_files, foreign_key: :BaseJobId
has_many :job_media, foreign_key: :JobId
has_many :logs, foreign_key: :JobId
scope :running, -> { where(job_status: 'R') }
scope :backup_type, -> { where(type: 'B') }
scope :restore_type, -> { where(type: 'R') }
+
+ HUMAN_STATUS = {
+ 'A' => 'Canceled by user',
+ 'B' => 'Blocked',
+ 'C' => 'Created, not yet running',
+ 'D' => 'Verify found differences',
+ 'E' => 'Terminated with errors',
+ 'F' => 'Waiting for Client',
+ 'M' => 'Waiting for media mount',
+ 'R' => 'Running',
+ 'S' => 'Waiting for Storage daemon',
+ 'T' => 'Completed successfully',
+ 'a' => 'SD despooling attributes',
+ 'c' => 'Waiting for client resource',
+ 'd' => 'Waiting on maximum jobs',
+ 'e' => 'Non-fatal error',
+ 'f' => 'Fatal error',
+ 'i' => 'Doing batch insert file records',
+ 'j' => 'Waiting for job resource',
+ 'm' => 'Waiting for new media',
+ 'p' => 'Waiting on higher priority jobs',
+ 's' => 'Waiting for storage resource',
+ 't' => 'Waiting on start time'
+ }
+
+ def level_human
+ {
+ 'F' => 'Full',
+ 'D' => 'Differential',
+ 'I' => 'Incremental'
+ }[level]
+ end
+
+ def status_human
+ HUMAN_STATUS[job_status]
+ end
+
+ def fileset
+ file_set.try(:file_set) || '-'
+ end
+
+ def start_time_formatted
+ if start_time
+ I18n.l(start_time, format: :long)
+ end
+ end
+
+ def end_time_formatted
+ if end_time
+ I18n.l(end_time, format: :long)
+ end
+ end
end
diff --git a/app/views/clients/_client.html.erb b/app/views/clients/_client.html.erb
index 3f5a2eb..3e5337b 100644
--- a/app/views/clients/_client.html.erb
+++ b/app/views/clients/_client.html.erb
@@ -1,11 +1,11 @@
<%= link_to client.name, client %> |
<%= client.uname %> |
<%= @active_jobs[client.id] || 0 %> |
- <%= client.last_job_date %> |
+ <%= client.last_job_date_formatted %> |
<%= client.file_retention_days %> |
<%= client.job_retention_days %> |
<%= number_to_human_size(client.backup_jobs_size) %> |
<%= number_by_magnitude(client.files_count) %> |
<%= client.auto_prune_human %> |
diff --git a/app/views/clients/_client_details.html.erb b/app/views/clients/_client_details.html.erb
index 94cbb7d..7da5967 100644
--- a/app/views/clients/_client_details.html.erb
+++ b/app/views/clients/_client_details.html.erb
@@ -1,42 +1,48 @@
Name |
<%= @client.name %> |
Uname |
<%= @client.uname %> |
Active Jobs |
<%= @client.running_jobs %> |
Last Backup |
- <%= @client.last_job_date %> |
+
+ <%= @client.last_job_date_formatted %>
+ |
File Retention |
<%= @client.file_retention_days %> days |
Job Retention |
<%= @client.job_retention_days %> days |
Total Space Used |
<%= number_to_human_size @client.backup_jobs_size %> |
Files count |
<%= number_by_magnitude(@client.files_count) %> |
Auto Prune |
<%= @client.auto_prune_human %> |
+
+ <%= link_to 'Restore Files', '#', class: "btn btn-warning", role: "button" %>
+ <%= link_to 'Take Backup', '#', class: "btn btn-success", role: "button" %>
+
diff --git a/app/views/clients/_job.html.erb b/app/views/clients/_job.html.erb
index a254cd5..7e65907 100644
--- a/app/views/clients/_job.html.erb
+++ b/app/views/clients/_job.html.erb
@@ -1,8 +1,13 @@
<%= job.name %> |
<%= job.job_type %> |
<%= job.fileset.try(:name) %> |
<%= job.restore_location %> |
<%= job.schedule_human %> |
<%= I18n.l(job.created_at, format: :long) %> |
+
+ <% if job.enabled? && job.baculized? && job.backup? %>
+ <%= link_to 'Take Backup', '#', class: "btn btn-success", role: "button" %>
+ <% end %>
+ |
diff --git a/app/views/clients/_jobs.html.erb b/app/views/clients/_jobs.html.erb
index aa1630e..f1f2c09 100644
--- a/app/views/clients/_jobs.html.erb
+++ b/app/views/clients/_jobs.html.erb
@@ -1,19 +1,20 @@
-
+
Name |
Type |
Fileset |
Restore Location |
Schedule |
Created |
+ Actions |
<%= render partial: 'clients/job', collection: @client.persisted_jobs, object: :job %>
diff --git a/app/views/clients/_recent_job.html.erb b/app/views/clients/_recent_job.html.erb
new file mode 100644
index 0000000..109626b
--- /dev/null
+++ b/app/views/clients/_recent_job.html.erb
@@ -0,0 +1,11 @@
+
+ <%= recent_job.name %> |
+ <%= recent_job.job_id %> |
+ <%= recent_job.level_human %> |
+ <%= recent_job.fileset %> |
+ <%= recent_job.start_time_formatted %> |
+ <%= recent_job.end_time_formatted %> |
+ <%= number_to_human_size(recent_job.job_bytes) %> |
+ <%= number_by_magnitude(recent_job.job_files) %> |
+ <%= recent_job.status_human %>
+ |
diff --git a/app/views/clients/_recent_jobs.html.erb b/app/views/clients/_recent_jobs.html.erb
new file mode 100644
index 0000000..d29f972
--- /dev/null
+++ b/app/views/clients/_recent_jobs.html.erb
@@ -0,0 +1,22 @@
+
+
+
+
+
+ Name |
+ JobId |
+ Level |
+ Fileset |
+ Started At |
+ Finished At |
+ Bytes |
+ Files |
+ Status |
+
+
+
+ <%= render partial: 'clients/recent_job', collection: @client.recent_jobs %>
+
+
+
+
diff --git a/app/views/clients/show.html.erb b/app/views/clients/show.html.erb
index c3efec2..8d04039 100644
--- a/app/views/clients/show.html.erb
+++ b/app/views/clients/show.html.erb
@@ -1,30 +1,38 @@
<%= notice %>
<% if @client.host %>
<%= link_to 'Manage Client', host_path(@client.host), class: "btn btn-primary", role: "button" %>
<% end %>
<%= @client.name %>
Client Details
Bacula Jobs
<%= render partial: 'client_details' %>
- <%= render partial: 'jobs' %>
+
+
+ <%= render partial: 'jobs' %>
+
+
+
+ <%= render partial: 'recent_jobs' %>
+
+
-
- <%= link_to 'Restore Files', '#', class: "btn btn-warning", role: "button" %>
- <%= link_to 'Take Backup', '#', class: "btn btn-success", role: "button" %>
-
<%= link_to 'Back to clients', clients_path %>