diff --git a/app/assets/javascripts/jobs.js b/app/assets/javascripts/jobs.js index 40a62e0..f899386 100644 --- a/app/assets/javascripts/jobs.js +++ b/app/assets/javascripts/jobs.js @@ -1,22 +1,23 @@ $(document).ready(function() { $("#job_template_schedule_id").change(function() { showSchedule(); }); $("#job_template_fileset_id").change(function() { showFileset(); }); }); function showSchedule() { var scheduleId = $("#job_template_schedule_id").val(); $.ajax({ - url: "/hosts/" + hostId + "/schedules/" + scheduleId + url: "/hosts/" + hostId + "/schedules/" + scheduleId, + data: jobIdParam }); } function showFileset() { var filesetId = $("#job_template_fileset_id").val(); $.ajax({ url: "/hosts/" + hostId + "/filesets/" + filesetId }); } diff --git a/app/controllers/jobs_controller.rb b/app/controllers/jobs_controller.rb index 5816f06..de15270 100644 --- a/app/controllers/jobs_controller.rb +++ b/app/controllers/jobs_controller.rb @@ -1,80 +1,81 @@ class JobsController < ApplicationController before_action :require_logged_in before_action :fetch_host, only: [:new, :edit, :show, :create, :update, :toggle_enable, :backup_now] before_action :fetch_job, only: [:show, :edit, :update, :destroy, :toggle_enable, :backup_now] # GET /jobs def new @job = @host.job_templates.new + @schedule = @host.schedules.find(params[:schedule_id]) if params[:schedule_id] end # POST /jobs def create @job = @host.job_templates.new(fetch_params) if @job.save flash[:success] = 'Job created successfully' redirect_to host_path(@host) else render :new end end # GET /jobs/1 def show; end # GET /jobs/1/edit def edit @fileset = @host.filesets.find(params[:fileset_id]) if params[:fileset_id] @schedule = @host.schedules.find(params[:schedule_id]) if params[:schedule_id] end # PUT /jobs/1 def update if @job.update_attributes(fetch_params) flash[:success] = 'Job updated' redirect_to host_job_path(@host, @job) else render :edit end end # DELETE /jobs/1 def destroy end # PATCH /hosts/1/jobs/1/enable def toggle_enable @job.enabled = !@job.enabled @job.save flash[:success] = @job.enabled? ? 'Job enabled' : 'Job disabled' redirect_to host_path(@host) end # POST /hosts/1/jobs/1/backup_now def backup_now if @job.backup_now flash[:success] = 'Backup directive was sent to bacula. Backup will be taken in a while' else flash[:error] = 'Backup was not sent, try again later' end redirect_to client_path(@host.client) end private def fetch_job @job = @host.job_templates.find(params[:id]) end def fetch_host @host = current_user.hosts.find(params[:host_id]) end def fetch_params params.require(:job_template). permit(:name, :fileset_id, :schedule_id, :client_before_run_file, :client_after_run_file) end end diff --git a/app/controllers/schedules_controller.rb b/app/controllers/schedules_controller.rb index fcfb33f..5345ba4 100644 --- a/app/controllers/schedules_controller.rb +++ b/app/controllers/schedules_controller.rb @@ -1,58 +1,72 @@ class SchedulesController < ApplicationController before_action :require_logged_in - before_action :fetch_host, only: [:new, :create, :show] - before_action :fetch_job_id, only: [:new, :create] + 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] def new @schedule = @host.schedules.new + @schedule.schedule_runs.build end def show - @schedule = @host.schedules.find(params[:id]) - respond_to do |format| format.js {} end end def edit end def update + if @schedule.update(fetch_params) + flash[:success] = 'Schedule updated successfully' + 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 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 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.require(:schedule). - permit(:name, { schedule_runs_attributes: [[:level, :month, :day, :time]] }) + permit(:name, { schedule_runs_attributes: [[:id, :level, :month, :day, :time]] }) end end diff --git a/app/models/schedule_run.rb b/app/models/schedule_run.rb index af2ef1a..5d60ebb 100644 --- a/app/models/schedule_run.rb +++ b/app/models/schedule_run.rb @@ -1,133 +1,137 @@ # 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 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 + # Composes the schedule line for the bacula configuration # # @return [String] def schedule_line [ level_to_config, 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) 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?(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 end diff --git a/app/views/jobs/_form.html.erb b/app/views/jobs/_form.html.erb index 652ea6f..d5524b5 100644 --- a/app/views/jobs/_form.html.erb +++ b/app/views/jobs/_form.html.erb @@ -1,33 +1,38 @@ <%= bootstrap_form_for(@job, url: @job.new_record? ? host_jobs_path : host_job_path(@host, @job), layout: :horizontal, label_col: 'col-xs-5', control_col: 'col-xs-5') do |f| %> <%= f.text_field :name %> <%= select_with_errors_and_button( @job, :job_template, :fileset_id, 'Fileset', options_from_collection_for_select( @host.filesets, :id, :name, params[:fileset_id] || @job.fileset_id), new_host_fileset_path(@host, job_id: @job.id)) %> <% if !@job.restore? %> <%= select_with_errors_and_button( @job, :job_template, :schedule_id, 'Schedule', options_from_collection_for_select( @host.schedules, :id, :name, params[:schedule_id] || @job.schedule_id), new_host_schedule_path(@host, job_id: @job.id)) %> <% end %> <%= f.text_field :client_before_run_file, label: 'Client Run Before Job' %> <%= f.text_field :client_after_run_file, label: 'Client Run After Job' %> <div class="form-group"> <div class="col-xs-offset-4 col-xs-8"> <%= f.submit class: "btn btn-success" %> </div> </div> <% end %> <script type='text/javascript'> var hostId=<%= @host.id %>; + var jobIdParam={}; + + <% if @job.id %> + jobIdParam= { job_id: <%= @job.id %> }; + <% end %> </script> diff --git a/app/views/jobs/_schedule.html.erb b/app/views/jobs/_schedule.html.erb index efe5fda..0b32487 100644 --- a/app/views/jobs/_schedule.html.erb +++ b/app/views/jobs/_schedule.html.erb @@ -1,8 +1,20 @@ +<% if job_id = (@job.try(:id) || @job_id) %> + <% url_options = { job_id: job_id } %> +<% else %> + <% url_options = {} %> +<% end %> + <div class="panel panel-default"> <div class="panel-heading"> - <h4>Schedule "<%= schedule.name %>"</h4> + <h4> + Schedule "<%= schedule.name %>" + <%= link_to edit_host_schedule_path(@host, schedule.id, url_options) do %> + <label class="glyphicon glyphicon-edit text-primary right"></label> + <% end %> + </h4> </div> + <br /> <pre> - <%= schedule.to_bacula_config_array.join("\n") %> +<%= schedule.to_bacula_config_array.join("\n") %> </pre> </div> diff --git a/app/views/jobs/new.html.erb b/app/views/jobs/new.html.erb index 5b822f8..0790b0a 100644 --- a/app/views/jobs/new.html.erb +++ b/app/views/jobs/new.html.erb @@ -1,27 +1,29 @@ <h1>New Job Template</h1> <div class="row"> <div class="col-xs-4"> <div class="panel panel-default"> <div class="panel-heading"> <h3> Job Attributes</h3> </div> <br /> <%= render 'form' %> </div> </div> <div class="col-xs-4"> <div id="fileset"> </div> <div id="schedule"> + <% schedule = @schedule || @job.schedule %> + <%= render partial: 'schedule', locals: { schedule: schedule } if schedule %> </div> </div> </div> <div class="row"> <div class="col-xs-1"> <%= link_to 'Cancel', host_path(@job.host), class: 'btn btn-danger', role: 'button' %> </div> </div> diff --git a/app/views/schedules/_form.html.erb b/app/views/schedules/_form.html.erb index f53d299..5926f30 100644 --- a/app/views/schedules/_form.html.erb +++ b/app/views/schedules/_form.html.erb @@ -1,49 +1,49 @@ -<%= bootstrap_form_for(@schedule, url: host_schedules_path(@host), layout: :horizontal, +<%= 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 %> <hr /> <% if ['schedule_runs.day','schedule_runs.month','scheduled_runs.time'] - @schedule.errors.keys %> <div id="error_explanation" class="has-error"> <ul> <% @schedule.errors.full_messages.each do |message| %> <li><span class="help-block"><%= message %></span></li> <% end %> </ul> </div> <% end %> - <%= f.fields_for :schedule_runs_attributes, index: nil do |r| %> + <%= f.fields_for :schedule_runs, @schedule.schedule_runs do |r| %> <div class="schedule_run_form"> - <%= r.select :level, options_for_select(ScheduleRun.levels.keys) %> + <%= r.select :level, options_for_select(ScheduleRun.options_for_select, r.object.level) %> <%= r.text_field :month, placeholder: '[month | month-range]' %> <p class="col-xs-10 col-xs-offset-1 help-block text-right">eg: jan-mar, feb, monthly</p> <%= r.text_field :day, placeholder: '[week | week-range] day | day-range', required: true %> <p class="col-xs-10 col-xs-offset-1 help-block text-right"> eg: first sun, second-fifth mon, mon-sat </p> - <%= r.time_field :time, placeholder: 'HH:MM', required: true %> + <%= r.text_field :time, placeholder: 'HH:MM', required: true %> </div> <hr /> <% end %> <div class="col-xs-1 col-xs-offset-10"> <%= link_to '#', class: 'schedule_run_form_remove', style: 'display:none;' do %> <span class="glyphicon glyphicon-remove text-danger"></span> <% end %> </div> <div class="col-xs-1 col-xs-offset-10"> <%= link_to '#', class: 'schedule_run_form_plus' do %> <span class="glyphicon glyphicon-plus text-success"></span> <% end %> </div> - <%= (hidden_field_tag :job_id, @job_id) if @job_id%> + <%= (hidden_field_tag :job_id, @job_id) if @job_id %> <div class="form-group"> <div class="col-xs-offset-3 col-xs-8"> <%= f.submit class: 'btn btn-success' %> </div> </div> <% end %> diff --git a/app/views/schedules/edit.html.erb b/app/views/schedules/edit.html.erb new file mode 100644 index 0000000..2f16826 --- /dev/null +++ b/app/views/schedules/edit.html.erb @@ -0,0 +1,9 @@ +<h1>Edit Schedule: <%= @schedule.name %></h1> + +<div class="container graybox"> + <%= render partial: 'form', + locals: { url: host_schedule_path(@host, @schedule.id, job_id: @job_id), method: :patch } %> +</div> + +<%= link_to 'Back to job', + @job_id.present? ? edit_host_job_path(@host, @job_id) : new_host_job_path(@host) %> diff --git a/app/views/schedules/new.html.erb b/app/views/schedules/new.html.erb index 4eab219..15112b5 100644 --- a/app/views/schedules/new.html.erb +++ b/app/views/schedules/new.html.erb @@ -1,8 +1,8 @@ <h1>New Schedule</h1> <div class="container graybox"> - <%= render 'form' %> + <%= render partial: 'form', locals: { url: host_schedules_path(@host), method: :post } %> </div> <%= link_to 'Back to job', @job_id.present? ? edit_host_job_path(@host, @job_id) : new_host_job_path(@host) %>