diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 1260d09..92a6469 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -1,91 +1,92 @@ // This is a manifest file that'll be compiled into application.js, which will include all the files // listed below. // // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. // // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the // compiled file. // // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details // about supported directives. // //= require jquery.min //= require jquery_ujs //= require jquery-ui.min //= require bootstrap.min //= require jstree //= require_tree . //= require highcharts //= require jobs //= require filesets +//= require clients $(document).ready(function() { if ($('.schedule_run_form_plus').size() > 0) { $(".schedule_run_form_plus").click(function() { addScheduleRun(); return false; }); }; if ($('.schedule_run_form_remove').size() > 0) { $(".schedule_run_form_remove").click(function() { removeScheduleRun(); return false; }); }; if ($('.schedule_run_form').size() > 1) { $('.schedule_run_form_remove').show(); }; }); function addScheduleRun() { var count = $('.schedule_run_form').size(); var scheduleRun = $('.schedule_run_form:last').clone(); $(scheduleRun).find('label').each(function() { var newLabel, oldLabel; oldLabel = $(this).attr('for'); newLabel = oldLabel.replace(new RegExp(/_[0-9]+_/), "_" + count + "_"); $(this).attr('for', newLabel); }); $(scheduleRun).find('select, input').each(function() { var newId, oldId, newName, oldName; oldId = $(this).attr('id'); newId = oldId.replace(new RegExp(/_[0-9]+_/), "_" + count + "_"); $(this).attr('id', newId); oldName = $(this).attr('name'); newName = oldName.replace(new RegExp(/[0-9]+/), "[" + count + "]"); $(this).attr('name', newName); }); scheduleRun.insertAfter('.schedule_run_form:last'); $('.schedule_run_form:last input').val(''); if (count > 0) { $(".schedule_run_form_remove").show(); }; $('.destroyer:last').remove(); } function removeScheduleRun() { var count = $('.schedule_run_form').size(); if (count > 1) { var last_id = count - 1; $('').attr({ type: 'hidden', class: 'destroyer', id: 'schedule_schedule_runs_attributes_' + last_id + '__destroy', name: 'schedule[schedule_runs_attributes][' + last_id + '][_destroy]', value: '1' }).appendTo('form'); $('.schedule_run_form:last').remove(); if ($('.schedule_run_form').size() == 1) { $(".schedule_run_form_remove").hide(); }; } else { alert('nothing to remove'); }; } diff --git a/app/assets/javascripts/clients.js b/app/assets/javascripts/clients.js new file mode 100644 index 0000000..6762bc5 --- /dev/null +++ b/app/assets/javascripts/clients.js @@ -0,0 +1,36 @@ +$(document).ready(function() { + if ($('#select-files').size() > 0) { + $('#file-selector').hide(); + $('#select-files').click(function() { + $('#file-selector').show(); + }); + } +}); + +$(document).ready(function() { + if ($('#file-submitter').size() > 0) { + $("#file-tree").on("select_node.jstree", + function(evt, data) { + add_input(data.node.id); + }); + $("#file-tree").on("deselect_node.jstree", + function(evt, data) { + remove_input(data.node.id); + }); + } +}); + +function add_input(id) { + $('#file-submitter'). + append(''); + if ($('.js-file-input').size() > 0 && $('#file-submitter > input[type="submit"]').attr('disabled') == 'disabled') { + $('#file-submitter > input[type="submit"]').attr('disabled', false); + } +} + +function remove_input(id) { + $('#js-file-id-' + id).remove(); + if ($('.js-file-input').size() == 0) { + $('#file-submitter > input[type="submit"]').attr('disabled', true); + } +} diff --git a/app/controllers/clients_controller.rb b/app/controllers/clients_controller.rb index 3c3ea10..3d703b0 100644 --- a/app/controllers/clients_controller.rb +++ b/app/controllers/clients_controller.rb @@ -1,95 +1,137 @@ class ClientsController < ApplicationController before_action :require_logged_in - before_action :fetch_client, only: [:show, :jobs, :logs, :stats, :users, :restore, :run_restore] + before_action :fetch_client, only: [:show, :jobs, :logs, :stats, :users, :restore, :run_restore, + :restore_selected] before_action :fetch_logs, only: [:logs] # GET /clients # POST /clients def index @client_ids = Client.for_user(current_user.id).pluck(:ClientId) @clients = Client.where(ClientId: @client_ids).includes(:jobs) @hosts = current_user.hosts.not_baculized fetch_jobs_info get_charts end # GET /clients/1 def show @schedules = @client.host.job_templates.map(&:schedule) @filesets = @client.host.job_templates.map(&:fileset) end # GET /clients/1/jobs def jobs @jobs = @client.recent_jobs.page(params[:page]) end # GET /clients/1/logs def logs; end # GET /clients/1/stats # POST /clients/1/stats def stats get_charts end # GET /clients/1/users def users @users = @client.host.users end # GET /clients/1/restore def restore return if @client.is_backed_up? flash[:error] = 'Can not issue a restore for this client' redirect_to client_path(@client) end # POST /clients/1/run_restore def run_restore - location = params[:restore_location].blank? ? '/tmp/bacula-restore' : params[:restore_location] + @location = params[:restore_location].blank? ? '/tmp/bacula_restore' : params[:restore_location] fileset = params[:fileset] restore_point = fetch_restore_point - if location.nil? || fileset.nil? || !@client.host.restore(fileset, location, restore_point) - flash[:error] = 'Something went wrong, try again later' + + if params[:commit] == 'Restore All Files' + if @location.nil? || fileset.nil? || !@client.host.restore(fileset, @location, restore_point) + flash[:error] = 'Something went wrong, try again later' + else + flash[:success] = + "Restore job issued successfully, files will be soon available in #{@location}" + end + render js: "window.location = '#{client_path(@client)}'" else - flash[:success] = - "Restore job issued successfully, files will be soon available in #{location}" + session[:job_ids] = @client.get_job_ids(fileset, restore_point) + Bvfs.new(@client, session[:job_ids]).update_cache + render 'select_files' end + end + # POST /clients/1/restore_selected + def restore_selected + Bvfs.new(@client, session[:job_ids]).restore_selected_files(params[:files], params[:location]) + session.delete(:job_ids) + flash[:success] = + "Restore job issued successfully, files will be soon available in #{params[:location]}" redirect_to client_path(@client) end + # GET /clients/1/tree?id=1 + def tree + @client = Client.for_user(current_user.id).find(params[:client_id]) + bvfs = Bvfs.new(@client, session[:job_ids]) + pathid = params[:id].to_i + + if pathid.nonzero? + bvfs.fetch_dirs(pathid) + else + bvfs.fetch_dirs + end + + tree = bvfs.extract_dir_id_and_name.map do |id, name| + { id: id, text: name, state: { checkbox_disabled: true }, children: true } + end + + if pathid.nonzero? + bvfs.fetch_files(pathid) + bvfs.extract_file_id_and_name.each do |id, name| + tree << { id: id, text: name, type: 'file' } + end + end + + render json: tree + end + private def fetch_client @client = Client.for_user(current_user.id).find(params[:id]) @client_ids = [@client.id] end def fetch_jobs_info @stats = JobStats.new(@client_ids) end def get_charts days_ago = params.fetch(:days_back, 7).to_i rescue 7 @job_status = ChartGenerator.job_statuses(@client_ids, days_ago) @job_stats = ChartGenerator.job_stats(@client_ids, days_ago - 1) end def fetch_restore_point if params['restore_time(4i)'].blank? || params['restore_time(5i)'].blank? || params[:restore_date].blank? return nil end restore_point = "#{params[:restore_date]} #{params['restore_time(4i)']}:#{params['restore_time(5i)']}:00" begin DateTime.strptime(restore_point, '%Y-%m-%d %H:%M:%S') rescue return nil end restore_point end end diff --git a/app/views/clients/_file_selector.html.erb b/app/views/clients/_file_selector.html.erb new file mode 100644 index 0000000..10f3e61 --- /dev/null +++ b/app/views/clients/_file_selector.html.erb @@ -0,0 +1,15 @@ +
+
+
+

Files

+
+
+

LOADING

+
+
+
+
+ +<%= form_tag(restore_selected_client_path, { id: 'file-submitter', style: 'display:none' }) do %> + <%= submit_tag 'Restore Selected Files', class: 'btn btn-default', disabled: true %> +<% end %> diff --git a/app/views/clients/restore.html.erb b/app/views/clients/restore.html.erb index 7b37c37..bb5bea1 100644 --- a/app/views/clients/restore.html.erb +++ b/app/views/clients/restore.html.erb @@ -1,57 +1,65 @@
<% if @client.is_backed_up? %>

Restore files for "<%= @client.name %>"


- <%= bootstrap_form_tag(url: run_restore_client_path(@client), layout: :horizontal, - label_col: 'col-xs-4', control_col: 'col-xs-7' ) do |f| %> + <%= bootstrap_form_tag(url: run_restore_client_path(@client), remote: true, + layout: :horizontal, label_col: 'col-xs-4', control_col: 'col-xs-7', + html: { id: 'basic-form' } ) do |f| %>

Restore to most recent backup by leaving date and time blank

<%= f.text_field :restore_date %> <%= f.time_select :restore_time, ignore_date: true, minute_step: 30, prompt: true %> <%= f.select(:fileset, options_from_collection_for_select(@client.file_sets, :id, :file_set)) %> - <%= f.text_field :restore_location, placeholder: '/tmp' %> + <%= f.text_field :restore_location, placeholder: '/tmp/bacula_restore' %>
-
- <%= f.submit 'Restore Files', class: 'btn btn-warning text-right', +
+ <%= f.submit 'Select Specific Files', id: 'select-files', class: 'btn btn-primary' %> +
+
+
+
+
+ <%= f.submit 'Restore All Files', class: 'btn btn-warning text-right', data: { confirm: "This will restore all your files" } %>
<% end %>
<% else %>

Can not issue a restore for this host. It has no successful backups

<% end %> - <%= link_to 'Cancel', client_path(@client), class: 'btn btn-danger', role: 'button' %> + <%= link_to 'Back to client', client_path(@client), class: 'btn btn-danger', role: 'button' %>
+ <%= render partial: 'file_selector' %>
diff --git a/app/views/clients/select_files.js.erb b/app/views/clients/select_files.js.erb new file mode 100644 index 0000000..c92a56e --- /dev/null +++ b/app/views/clients/select_files.js.erb @@ -0,0 +1,27 @@ +$('#basic-form input').attr('disabled', true); +$('#basic-form select').attr('disabled', true); +$('.loader').hide(); +$('#file-tree').jstree({ + 'core': { + 'data' : { + "url" : "<%= escape_javascript(tree_client_path(client_id: @client.id)) %>", + "data" : function(node) { + return { "id" : node.id }; + }, + "dataType" : "json" + } + }, + 'checkbox' : { + 'three_state': false + }, + "types" : { + "file" : { + "icon" : "glyphicon glyphicon-file" + } + }, + "plugins" : ["types", "checkbox"] +}); + +$('#file-submitter').show(); +$('#file-submitter'). + append(''); diff --git a/config/routes.rb b/config/routes.rb index d4a12c2..34d5ee1 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,84 +1,91 @@ Rails.application.routes.draw do root 'application#index' post 'grnet' => 'application#grnet' get 'institutional' => 'application#institutional' match 'vima', to: 'application#vima', :via => [:get, :post] get 'logout' => 'application#logout' resources :clients, only: [:index, :show] do member do get :jobs get :logs get :stats post :stats get :users get :restore post :run_restore + post :restore_selected end collection do post :index end end + resources :clients, only: [], param: :client_id do + member do + get :tree + end + end + resources :hosts, only: [:new, :create, :show, :edit, :update, :destroy] do member do post :submit_config post :disable delete :revoke end resources :jobs, only: [:new, :create, :show, :edit, :update, :destroy] do member do patch :toggle_enable post :backup_now end end resources :filesets, only: [:show, :new, :create, :edit, :update, :destroy] resources :schedules, only: [:show, :new, :edit, :create, :update, :destroy] end namespace :admin do match '/', to: 'base#index', via: [:get, :post] get '/login' => 'base#login', as: :login resources :settings, only: [:index, :new, :create, :edit, :update] do member do delete :reset end end resources :clients, only: [:index, :show] do member do get :jobs get :logs get :stats post :stats get :configuration post :disable post :block post :unblock delete :revoke end end resources :hosts, only: [:show] do collection do get :unverified end member do post :verify end end resources :users, only: [:index, :new, :create, :show, :edit, :update] do member do patch :ban patch :unban end end end end