diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index 320c6f7..b0e1d28 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -1,87 +1,92 @@ /* * This is a manifest file that'll be compiled into application.css, which will include all the files * listed below. * * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. * * You're free to add application-wide styles to this file and they'll appear at the bottom of the * compiled file so the styles you add here take precedence over styles defined in any styles * defined in the other CSS/SCSS files in this directory. It is generally better to create a new * file per style scope. * *= require bootstrap.min *= require jstree-default *= require bootstrap-chosen *= require dataTables.bootstrap.min *= require jquery.timepicker.min *= require_tree . *= require_self */ /* Make sure navbar does not overlay body */ body { padding-top: 70px; } .right { text-align: right; margin-right: 0px; float: right; } .graybox { max-width: 500px; margin-right: 0; margin-left: 0; background-color: #F7F7F9; margin-bottom: 30px; } .graybox form { padding-top: 30px; } #_days_back { margin: auto 10px; } tr.fatal > td { background-color: #EA7272; } tr.fatal > td { background-color: #EA7272; } li.logo { } li.logo > a { padding-left: 60px; background: url('/images/logo.png') no-repeat scroll 4px 5px; padding-top: 5px; padding-bottom: 5px; } li.logo > a > p { font-size: 13px; margin-bottom: 0; color: #333333px; } .jstree-closed > a > i.jstree-checkbox, .jstree-open > a > i.jstree-checkbox { display: none; } .datatable-wrapper { margin-bottom: 20px; } .faq-link > svg { visibility: hidden; } +.time-selects { + width: 47%; + display: inline-block; +} + h2:hover > a.faq-link > svg { visibility: visible; } diff --git a/app/controllers/schedules_controller.rb b/app/controllers/schedules_controller.rb index 67fb13a..3855d09 100644 --- a/app/controllers/schedules_controller.rb +++ b/app/controllers/schedules_controller.rb @@ -1,84 +1,90 @@ class SchedulesController < ApplicationController before_action :require_logged_in before_action :fetch_host, only: [:new, :create, :show, :edit, :update] before_action :fetch_job_id, only: [:new, :create, :show, :edit, :update] before_action :fetch_schedule, only: [:show, :edit, :update] # GET /hosts/:host_id/schedules/new def new @schedule = @host.schedules.new @schedule.schedule_runs.build.default_run end # GET /hosts/:host_id/schedules/:id def show respond_to do |format| format.js {} end end # GET /hosts/:host_id/schedules/:id/edit def edit end # PATCH /hosts/:host_id/schedules/:id def update if @schedule.update(fetch_params) flash[:success] = 'Schedule updated successfully' participating_hosts = @schedule.participating_hosts if participating_hosts.size.nonzero? participating_hosts.each(&:recalculate) flash[:alert] = "You will need to redeploy the afffected clients: " + participating_hosts.map(&:name).join(', ') end if @job_id redirect_to edit_host_job_path(@host, @job_id, schedule_id: @schedule.id) else redirect_to new_host_job_path(@host, schedule_id: @schedule.id) end else render :edit end end # POST /hosts/:host_id/schedules def create @schedule = @host.schedules.new(fetch_params) @schedule.runtime = params[:schedule][:runtime] if params[:schedule][:runtime] if @schedule.save flash[:success] = 'Schedule created successfully' if @job_id.present? redirect_to edit_host_job_path(@host, @job_id, schedule_id: @schedule.id) else redirect_to new_host_job_path(@host, schedule_id: @schedule.id) end else render :new end end # DELETE /hosts/:host_id/schedules/:id def destroy end private def fetch_schedule @schedule = @host.schedules.find(params[:id]) end def fetch_host @host = current_user.hosts.find(params[:host_id]) end def fetch_job_id @job_id = @host.job_templates.find(params[:job_id]).id if params[:job_id].present? end def fetch_params + params[:schedule][:schedule_runs_attributes].each do |sched, attrs| + hour = attrs.delete('time_wrapper(4i)') + minute = attrs.delete('time_wrapper(5i)') + attrs[:time] = Time.new.change(hour: hour, min: minute).strftime('%H:%M') + params[:schedule][:schedule_runs_attributes][sched] = attrs + end params.require(:schedule). permit(:name, { schedule_runs_attributes: [[:id, :level, :month, :day, :time, :_destroy]] }) end end diff --git a/app/models/schedule_run.rb b/app/models/schedule_run.rb index 0416838..1895d3e 100644 --- a/app/models/schedule_run.rb +++ b/app/models/schedule_run.rb @@ -1,164 +1,169 @@ # ScheduleRun is a helper class that modelizes the run directives of a schedule # resource. # # It can have 3 levels: # # * full # * differential # * incremental # # Each ScheduleRun instance holds info about the execution time of the schedule: # # * month specification is optional and is described by: # - month: full name (eg april) | month name three first letters (eg jul) # - month_range: month-month # - monthly: 'monthly' # * day specification is required and is described by: # - week_number (optional): weeks number in full text (eg: third, fourth) # - week_range (optional): week_number-week_number # - day: first three letters of day (eg: mon, fri) # - day_range: day-day # * time specification is required and is described by: # - 24h time (eg: 03:00) # # Schedule Run examples: # # Level=Full monthly first mon at 12:21 # Level=Full first sun at 12:34 # Level=Differential second-fifth sat at 15:23 # Level=Incremental mon-sat at 08:00 class ScheduleRun < ActiveRecord::Base establish_connection ARCHIVING_CONF + attr_accessor :time_wrapper + enum level: { full: 0, differential: 1, incremental: 2 } MONTH_KW = %w{jan feb mar apr may jun jul aug sep oct nov dec january february march april may june july august september october november december} DAYS = (1..31).to_a WDAY_KW = %w{mon tue wed thu fri sat sun} WEEK_KW = %w{first second third fourth fifth} belongs_to :schedule validates :day, :time, :level, presence: true validate :correct_chars validate :month_valid, if: "month.present?" validate :time_valid validate :day_valid def self.options_for_select levels.keys.zip levels.keys end + def time_wrapper + time.to_time + end # Builds a sane default schedule_run # # @return [ScheduleRun] def default_run self.level = :full self.day = "first sun" self.time = '04:00' end # Composes the schedule line for the bacula configuration # # @return [String] def schedule_line [ level_to_config, pool_to_config, month, day, "at #{time}"].join(" ") end # Provides a human readable projection of the schedule run # # @return [String] def human_readable ["#{level.capitalize} backup", month, day, "at #{time}"].join(' ') end private def correct_chars [:month, :day, :time].each do |x| if self.send(x) && self.send(x).to_s.gsub(/[0-9a-zA-Z\-:,]/, '').present? self.errors.add(x, 'Invalid characters') end end end def month_valid if !month_format? && !valid_month_range? self.errors.add(:month, 'Invalid month') end end def month_format? month_regex = "^(#{MONTH_KW.join('|')}|monthly)$" month.match(month_regex).present? end def valid_month_range? months = month.split('-') return false if months.length != 2 MONTH_KW.index(months.last) % 12 - MONTH_KW.index(months.first) % 12 > 0 end def time_valid if !time.match(/^([01][0-9]|2[0-3]):[0-5][0-9]$/) self.errors.add(:time, 'Invalid time') end end def day_valid components = day.split(' ') if components.length < 1 || components.length > 2 self.errors.add(:day, 'Invalid day') return false end if !valid_day?(components.last) && !valid_day_range?(components.last) && !valid_day_listing?(components.last) self.errors.add(:day, 'Invalid day') return false end if components.length == 2 && !valid_week?(components.first) && !valid_week_range?(components.first) self.errors.add(:day, 'Invalid day') return false end true end def valid_day_listing?(listing) listing.split(',').all? { |d| WDAY_KW.include? d } end def valid_day?(a_day) WDAY_KW.include? a_day end def valid_day_range?(a_range) days = a_range.split('-') return false if days.length != 2 WDAY_KW.index(days.last) - WDAY_KW.index(days.first) > 0 end def valid_week?(a_week) WEEK_KW.include? a_week end def valid_week_range?(a_range) weeks = a_range.split('-') return false if weeks.length != 2 WEEK_KW.index(weeks.last) - WEEK_KW.index(weeks.first) > 0 end def level_to_config "Level=#{level.capitalize}" end def pool_to_config "Pool=#{ConfigurationSetting.current_pool_settings[level.to_sym]}" end end diff --git a/app/views/schedules/_form.html.erb b/app/views/schedules/_form.html.erb index 4c3700b..da86337 100644 --- a/app/views/schedules/_form.html.erb +++ b/app/views/schedules/_form.html.erb @@ -1,51 +1,59 @@
<%= bootstrap_form_for(@schedule, url: url, method: method, layout: :horizontal, label_col: 'col-xs-3', control_col: 'col-xs-8') do |f| %> <%= f.text_field :name, required: true %>
<% if ['schedule_runs.day','schedule_runs.month','scheduled_runs.time'] - @schedule.errors.keys %>
<% end %> <%= f.fields_for :schedule_runs, @schedule.schedule_runs do |r| %>
<%= r.select :level, options_for_select(ScheduleRun.options_for_select, r.object.level) %> <%= r.text_field :month, placeholder: '[month | month-range]' %>

eg: jan-mar, feb, monthly

<%= r.text_field :day, placeholder: '[week | week-range] day | day-range', required: true %>

eg: first sun, second-fifth mon, mon-sat

- <%= r.text_field :time, placeholder: 'HH:MM', class: 'timepicker', required: true %> + <%= r.time_select :time_wrapper, { ignore_date: true, required: true, label: :Time }, + { class: 'time-selects' } %>
<% end %>
<%= link_to '#', class: 'schedule_run_form_remove', style: 'display:none;' do %> <% end %>
<%= link_to '#', class: 'schedule_run_form_plus' do %> <% end %>
<%= (hidden_field_tag :job_id, @job_id) if @job_id %>
-
+
+ <%= link_to (@job_id.present? ? edit_host_job_path(@host, @job_id) : new_host_job_path(@host)), class: 'btn btn-default' do %> + + Back to job + <% end %> +
+ +
<%= f.submit class: 'btn btn-success' %>
<% end %>
diff --git a/app/views/schedules/_level_explanation.html.erb b/app/views/schedules/_level_explanation.html.erb index 0bf6082..9b94613 100644 --- a/app/views/schedules/_level_explanation.html.erb +++ b/app/views/schedules/_level_explanation.html.erb @@ -1,31 +1,24 @@

Level Explanation


Full

When the Level is set to Full all files in the FileSet whether or not they have changed will be backed up.

Differential

When the Level is set to Differential all files specified in the FileSet that have changed since the last successful Full backup of the same Job will be backed up.

If there is no successful previous Full backup, a Differential backup is upgraded to a Full backup.

Incremental

When the Level is set to Incremental all files specified in the FileSet that have changed since the last successful backup of the the same Job using the same FileSet and Client, will be backed up.

If there is no successful previous Full backup, an Incremental backup is upgraded to a Full backup.

- -
-
- <%= link_to 'Back to job', - @job_id.present? ? edit_host_job_path(@host, @job_id) : new_host_job_path(@host) %> -
-