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 @@ -
+
+ <%= render partial: 'clients/job', collection: @client.persisted_jobs, object: :job %>
Name Type Fileset Restore Location Schedule CreatedActions
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 @@ +
+
+ + + + + + + + + + + + + + + + <%= render partial: 'clients/recent_job', collection: @client.recent_jobs %> + +
NameJobIdLevelFilesetStarted AtFinished AtBytesFilesStatus
+
+
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' %> +
+
+
+

Recent 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 %>