Page MenuHomeGRNET

No OneTemporary

File Metadata

Created
Sun, Mar 1, 8:16 AM
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 @@
<tr>
<td><%= link_to client.name, client %></td>
<td><%= client.uname %></td>
<td><%= @active_jobs[client.id] || 0 %></td>
- <td><%= client.last_job_date %></td>
+ <td><%= client.last_job_date_formatted %></td>
<td><%= client.file_retention_days %></td>
<td><%= client.job_retention_days %></td>
<td><%= number_to_human_size(client.backup_jobs_size) %></td>
<td><%= number_by_magnitude(client.files_count) %></td>
<td><%= client.auto_prune_human %></td>
</tr>
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 @@
<div class="col-xs-4">
<div class="table-responsive">
<table class="table table-striped table-bordered table-condensed ">
<tr>
<td>Name</td>
<td><%= @client.name %></td>
</tr>
<tr>
<td>Uname</td>
<td><%= @client.uname %></td>
</tr>
<tr>
<td>Active Jobs</td>
<td><%= @client.running_jobs %></td>
</tr>
<tr>
<td>Last Backup</td>
- <td><%= @client.last_job_date %></td>
+ <td>
+ <%= @client.last_job_date_formatted %>
+ </td>
</tr>
<tr>
<td>File Retention</td>
<td><%= @client.file_retention_days %> days</td>
</tr>
<tr>
<td>Job Retention</td>
<td><%= @client.job_retention_days %> days</td>
</tr>
<tr>
<td>Total Space Used</td>
<td><%= number_to_human_size @client.backup_jobs_size %></td>
</tr>
<tr>
<td>Files count</td>
<td><%= number_by_magnitude(@client.files_count) %></td>
</tr>
<tr>
<td>Auto Prune</td>
<td><%= @client.auto_prune_human %></td>
</tr>
</table>
</div>
+ <div>
+ <%= link_to 'Restore Files', '#', class: "btn btn-warning", role: "button" %>
+ <%= link_to 'Take Backup', '#', class: "btn btn-success", role: "button" %>
+ </div>
</div>
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 @@
<tr>
<td><%= job.name %></td>
<td><%= job.job_type %></td>
<td><%= job.fileset.try(:name) %></td>
<td><%= job.restore_location %></td>
<td><%= job.schedule_human %></td>
<td><%= I18n.l(job.created_at, format: :long) %></td>
+ <td>
+ <% if job.enabled? && job.baculized? && job.backup? %>
+ <%= link_to 'Take Backup', '#', class: "btn btn-success", role: "button" %>
+ <% end %>
+ </td>
</tr>
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 @@
-<div class="col-xs-8">
+<div class="col-xs-12">
<div class="table-responsive">
<table class="table table-striped table-bordered table-condensed">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Fileset</th>
<th>Restore Location</th>
<th>Schedule</th>
<th>Created</th>
+ <th>Actions</th>
</tr>
</thead>
<tbody>
<%= render partial: 'clients/job', collection: @client.persisted_jobs, object: :job %>
</tbody>
</table>
</div>
</div>
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 @@
+<tr class="<%= success_class(recent_job.job_status) %>">
+ <td><%= recent_job.name %></td>
+ <td><%= recent_job.job_id %></td>
+ <td><%= recent_job.level_human %></td>
+ <td><%= recent_job.fileset %></td>
+ <td><%= recent_job.start_time_formatted %></td>
+ <td><%= recent_job.end_time_formatted %></td>
+ <td><%= number_to_human_size(recent_job.job_bytes) %></td>
+ <td><%= number_by_magnitude(recent_job.job_files) %></td>
+ <td><%= recent_job.status_human %>
+</tr>
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 @@
+<div class="col-xs-12">
+ <div class="table-responsive">
+ <table class="table table-striped table-bordered table-condensed">
+ <thead>
+ <tr>
+ <th>Name</th>
+ <th>JobId</th>
+ <th>Level</th>
+ <th>Fileset</th>
+ <th>Started At</th>
+ <th>Finished At</th>
+ <th>Bytes</th>
+ <th>Files</th>
+ <th>Status</th>
+ </tr>
+ </thead>
+ <tbody>
+ <%= render partial: 'clients/recent_job', collection: @client.recent_jobs %>
+ </tbody>
+ </table>
+ </div>
+</div>
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 @@
<p id="notice"><%= notice %></p>
<% if @client.host %>
<div class="row right">
<%= link_to 'Manage Client', host_path(@client.host), class: "btn btn-primary", role: "button" %>
</div>
<% end %>
<h2><%= @client.name %></h2>
<div class="row">
<div class="col-xs-4">
<h3>Client Details</h3>
</div>
<div class="col-xs-6">
<h3>Bacula Jobs</h3>
</div>
</div>
<div class="row">
<%= render partial: 'client_details' %>
- <%= render partial: 'jobs' %>
+ <div class="col-xs-8">
+ <div class="row">
+ <%= render partial: 'jobs' %>
+ </div>
+ <div class="row">
+ <div class="col-xs-6">
+ <h3>Recent Jobs</h3>
+ </div>
+ </div>
+ <div class="row">
+ <%= render partial: 'recent_jobs' %>
+ </div>
+ </div>
</div>
-<div>
- <%= link_to 'Restore Files', '#', class: "btn btn-warning", role: "button" %>
- <%= link_to 'Take Backup', '#', class: "btn btn-success", role: "button" %>
-</div>
<br/>
<%= link_to 'Back to clients', clients_path %>

Event Timeline