Page Menu
Home
GRNET
Search
Configure Global Search
Log In
Files
F324176
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Subscribers
None
File Metadata
Details
File Info
Storage
Attached
Created
Sun, Nov 24, 3:58 PM
Size
19 KB
Mime Type
text/x-diff
Expires
Tue, Nov 26, 3:58 PM (20 h, 14 m)
Engine
blob
Format
Raw Data
Handle
156085
Attached To
rARCHIVING archiving
View Options
diff --git a/app/controllers/admin/pools_controller.rb b/app/controllers/admin/pools_controller.rb
new file mode 100644
index 0000000..dadb7c0
--- /dev/null
+++ b/app/controllers/admin/pools_controller.rb
@@ -0,0 +1,58 @@
+class Admin::PoolsController < Admin::BaseController
+ before_action :fetch_pool, only: [:show, :edit, :update]
+
+ # GET /admin/pools
+ def index
+ @pools = Pool.all
+ end
+
+ # GET /admin/pools/new
+ def new
+ @pool = Pool.new
+ end
+
+ # GET /admin/pools/:id/edit
+ def edit; end
+
+ # GET /admin/pools/:id
+ def show; end
+
+ # POST /admin/pools
+ def create
+ @pool = Pool.new(fetch_params)
+
+ if @pool.submit_to_bacula
+ flash[:success] = 'Pool created succesfully'
+ redirect_to admin_pools_path
+ else
+ flash[:alert] = 'Pool not created'
+ render :new
+ end
+ end
+
+ # PATCH /admin/pools/:id
+ def update
+ if @pool.update_attributes(fetch_params)
+ flash[:success] = 'Pool updated succesfully'
+ redirect_to admin_pools_path
+ else
+ flash[:alert] = 'Pool not updated'
+ render :edit
+ end
+ end
+
+ private
+
+ def fetch_pool
+ @pool = Pool.find(params[:id])
+ end
+
+ def fetch_params
+ params.require(:pool).permit(
+ [
+ :name, :name_confirmation, :vol_retention, :use_once, :auto_prune, :recycle,
+ :max_vols, :max_vol_jobs, :max_vol_files, :max_vol_bytes, :label_format
+ ]
+ )
+ end
+end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 86124f0..1fdf26a 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -1,109 +1,119 @@
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-5 required')
select_div = content_tag(:div, class: 'col-xs-5') do
select_part = select_tag([resource, attr].join('_').to_sym,
options,
include_blank: true,
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 path do
content_tag(:span, class: 'glyphicon glyphicon-plus text-success') {}
end
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'
when 'f' then 'fatal'
end
end
# Fetches the html class for a given path
#
# @param path[String] the path to check for
# @param partial[Boolean] forces a left partial match
#
# @return [Hash] { class: 'active' } if the given path is the current page
def active_class(path, partial = false)
if current_page?(path) || (partial && request.path.starts_with?(path))
{ class: 'active' }
else
{}
end
end
# Constructs a breadcrumb out the given options
#
# @param options[Hash] a hash containing the breadcrumb links in name: path sets
# @return an html ol breadcrumb
def breadcrumb_with(options)
content_tag(:ol, class: 'breadcrumb') do
options.map { |name, path|
content_tag(:li, active_class(path)) do
link_to_if !current_page?(path), name, path
end
}.inject { |result, element| result.concat(element) }
end
end
# Constructs a list with the given array elements
#
# @example:
# inline_list([:foo, :bar])
#
# <ul class="list-inline'>
# <li><span class="label label-default">foo</span></li>
# <li><span class="label label-default">bar</span></li>
# </ul>
#
# @param arr[Array]
# @return an html ul list
def inline_list(arr)
content_tag(:ul, class: 'list-inline') do
arr.map { |element|
content_tag(:li) do
content_tag(:span, class: 'label label-default') do
element
end
end
}.inject { |result, element| result.concat(element) }
end
end
+
+ # Generates a span with a yes or no and the corresponding formatting
+ # according to the value's falseness
+ #
+ # @param value[Integer]
+ def yes_no(value)
+ klass = value == 1 ? 'label label-success' : 'label label-danger'
+ text = value == 1 ? 'yes' : 'no'
+ content_tag(:span, class: klass) { text }
+ end
end
diff --git a/app/models/pool.rb b/app/models/pool.rb
index 8061ea9..8767743 100644
--- a/app/models/pool.rb
+++ b/app/models/pool.rb
@@ -1,45 +1,116 @@
# Bacula Pool
#
# The Pool table contains one entry for each media pool controlled by Bacula in
# this database. One media record exists for each of the NumVols contained in the Pool.
# The PoolType is a Bacula defined keyword.
# The MediaType is defined by the administrator, and corresponds to the MediaType
# specified in the Director's Storage definition record.
# The CurrentVol is the sequence number of the Media record for the current volume.
class Pool < ActiveRecord::Base
self.table_name = :Pool
self.primary_key = :PoolId
alias_attribute :pool_id, :PoolId
alias_attribute :name, :Name
alias_attribute :num_vols, :NumVols
alias_attribute :max_vols, :MaxVols
alias_attribute :use_once, :UseOnce
alias_attribute :use_catalog, :UseCatalog
alias_attribute :accept_any_volume, :AcceptAnyVolume
alias_attribute :vol_retention, :VolRetention
alias_attribute :vol_use_duration, :VolUseDuration
alias_attribute :max_vol_jobs, :MaxVolJobs
alias_attribute :max_vol_files, :MaxVolFiles
alias_attribute :max_vol_bytes, :MaxVolBytes
alias_attribute :auto_prune, :AutoPrune
alias_attribute :recycle, :Recycle
alias_attribute :action_on_purge, :ActionOnPurge
alias_attribute :pool_type, :PoolType
alias_attribute :label_type, :LabelType
alias_attribute :label_format, :LabelFormat
alias_attribute :enabled, :Enabled
alias_attribute :scratch_pool_id, :ScratchPoolId
alias_attribute :recycle_pool_id, :RecyclePoolId
alias_attribute :next_pool_id, :NextPoolId
alias_attribute :migration_high_bytes, :MigrationHighBytes
alias_attribute :migration_low_bytes, :MigrationLowBytes
alias_attribute :migration_time, :MigrationTime
has_many :jobs, foreign_key: :PoolId
has_many :media, foreign_key: :PoolId
+ validates_confirmation_of :name
+
+ BOOLEAN_OPTIONS = [['no', 0], ['yes', 1]]
+ POOL_OPTIONS = [:name, :vol_retention, :use_once, :auto_prune, :recycle,
+ :max_vols, :max_vol_jobs, :max_vol_files, :max_vol_bytes, :label_format]
+
+ # @return [Array] of all the available pools by name
def self.available_options
pluck(:Name)
end
+
+ # Persists the unpersisted record to bacula via its bacula handler
+ #
+ # @return [Boolean] according to the persist status
+ def submit_to_bacula
+ return false if !valid? || !ready_for_bacula?
+ sanitize_names
+ bacula_handler.deploy_config
+ end
+
+ # Constructs an array where each element is a line for the Job's bacula config
+ #
+ # @return [Array]
+ def to_bacula_config_array
+ ['Pool {'] +
+ options_array.map { |x| " #{x}" } +
+ ['}']
+ end
+
+ # Human readable volume retention
+ #
+ # @return [String] the volume's retention in days
+ def vol_retention_human
+ "#{vol_retention_days} days"
+ end
+
+ # volume retention in days
+ def vol_retention_days
+ vol_retention / 1.day.to_i
+ end
+
+ private
+
+ # proxy object for bacula pool handling
+ def bacula_handler
+ BaculaPoolHandler.new(self)
+ end
+
+ # pool names and label formats should only contain alphanumeric values
+ def sanitize_names
+ self.name = name.gsub(/[^a-zA-Z0-9]/, '_')
+ self.label_format = label_format.gsub(/[^a-zA-Z0-9]/, '_')
+ end
+
+ def options_array
+ boolean_options = Hash[BOOLEAN_OPTIONS].invert
+ [
+ "Name = \"#{name}\"",
+ "Volume Retention = #{vol_retention_human}",
+ "Use Volume Once = #{boolean_options[use_once.to_i]}",
+ "AutoPrune = #{boolean_options[auto_prune.to_i]}",
+ "Recycle = #{boolean_options[recycle.to_i]}",
+ "Maximum Volumes = #{max_vols}",
+ "Maximum Volume Jobs = #{max_vol_jobs}",
+ "Maximum Volume Files = #{max_vol_files}",
+ "Maximum Volume Bytes = #{max_vol_bytes}G",
+ "Label Format = \"#{label_format}\"",
+ "Pool Type = Backup"
+ ]
+ end
+
+ def ready_for_bacula?
+ POOL_OPTIONS.all? { |attr| self.send(attr).present? }
+ end
end
diff --git a/app/views/admin/pools/_form.html.erb b/app/views/admin/pools/_form.html.erb
new file mode 100644
index 0000000..6450261
--- /dev/null
+++ b/app/views/admin/pools/_form.html.erb
@@ -0,0 +1,25 @@
+<%= bootstrap_form_for(@pool, url: admin_pools_path, method: :post, layout: :horizontal,
+ label_col: 'col-xs-5', control_col: 'col-xs-6') do |f| %>
+ <%= f.text_field :name, required: true %>
+ <%= f.text_field :name_confirmation, required: true %>
+ <%= f.number_field :vol_retention, label: 'Volume Retention in days',
+ value: @pool.vol_retention_days || 180, required: true %>
+ <%= f.select :use_once, options_for_select(Pool::BOOLEAN_OPTIONS), required: true %>
+ <%= f.select :auto_prune, options_for_select(Pool::BOOLEAN_OPTIONS.reverse),
+ required: true %>
+ <%= f.select :recycle, options_for_select(Pool::BOOLEAN_OPTIONS.reverse),
+ required: true %>
+ <%= f.number_field :max_vols, required: true, value: 0 %>
+ <%= f.number_field :max_vol_jobs, min: 0, required: true %>
+ <%= f.number_field :max_vol_files, min: 0, required: true %>
+ <%= f.number_field :max_vol_bytes, label: 'Max Volume GB', min: 0, required: true %>
+ <%= f.text_field :label_format, required: true %>
+
+ <div class="form-group">
+ <div class="col-xs-offset-8 col-xs-3">
+ <%= f.submit class: 'btn btn-success' %>
+ </div>
+ </div>
+<% end %>
+
+<%= link_to 'Cancel', admin_pools_path, class: 'btn btn-danger', role: 'button' %>
diff --git a/app/views/admin/pools/_pool.html.erb b/app/views/admin/pools/_pool.html.erb
new file mode 100644
index 0000000..6300b7d
--- /dev/null
+++ b/app/views/admin/pools/_pool.html.erb
@@ -0,0 +1,14 @@
+<tr>
+ <td><%= link_to "##{pool.id}", admin_pool_path(pool) %></td>
+ <td><%= link_to pool.name, admin_pool_path(pool) %></td>
+ <td><%= pool.max_vols %></td>
+ <td><%= yes_no(pool.use_once) %></td>
+ <td><%= pool.vol_retention_human %></td>
+ <td><%= pool.max_vol_jobs %></td>
+ <td><%= pool.max_vol_files %></td>
+ <td><%= number_to_human_size pool.max_vol_bytes %></td>
+ <td><%= yes_no(pool.auto_prune) %></td>
+ <td><%= pool.label_format %></td>
+ <td><%= yes_no(pool.recycle) %></td>
+ <td><%= pool.pool_type %></td>
+</tr>
diff --git a/app/views/admin/pools/index.html.erb b/app/views/admin/pools/index.html.erb
new file mode 100644
index 0000000..3ee79de
--- /dev/null
+++ b/app/views/admin/pools/index.html.erb
@@ -0,0 +1,37 @@
+<div class="row right">
+ <%= link_to new_admin_pool_path, class: "btn btn-default", role: "button" do %>
+ <label class="glyphicon glyphicon-plus text-primary"></label>
+ New Pool
+ <% end %>
+</div>
+
+<h1>Pools</h1>
+
+<div class="row">
+ <div class="col-xs-12">
+ <div class="table-responsive">
+ <table class="table table-striped table-bordered table-condensed">
+ <thead>
+ <tr>
+ <th>id</th>
+ <th>Name</th>
+ <th>Max Volumes</th>
+ <th>Use Once</th>
+ <th>Volume Retention</th>
+ <th>Max Volume Jobs</th>
+ <th>Max Volume Files</th>
+ <th>Max Volume Bytes</th>
+ <th>Auto Prune</th>
+ <th>Label Format</th>
+ <th>Recycle</th>
+ <th>Pool Type</th>
+ </tr>
+ </thead>
+
+ <tbody>
+ <%= render partial: 'pool', collection: @pools %>
+ </tbody>
+ </table>
+ </div>
+ </div>
+</div>
diff --git a/app/views/admin/pools/new.html.erb b/app/views/admin/pools/new.html.erb
new file mode 100644
index 0000000..67d7b43
--- /dev/null
+++ b/app/views/admin/pools/new.html.erb
@@ -0,0 +1,11 @@
+<div class="row">
+ <div class="col-xs-4">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <h3>New Pool</h3>
+
+ <%= render partial: 'form' %>
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/app/views/shared/_nav.html.erb b/app/views/shared/_nav.html.erb
index bfacf33..ae04748 100644
--- a/app/views/shared/_nav.html.erb
+++ b/app/views/shared/_nav.html.erb
@@ -1,76 +1,79 @@
<!-- Fixed navbar -->
<nav class="navbar navbar-default navbar-fixed-top">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li class="logo">
<%= link_to root_path do %>
<p><b>GRNET NOC</b></p>
<p>Archiving</p>
<% end %>
</li>
<% if current_user %>
<%= content_tag(:li, active_class(clients_path, true)) do %>
<%= link_to 'Clients', clients_path %>
<% end %>
<li><%= link_to current_user.username, '#' %></li>
<% end %>
</ul>
<% if current_user %>
<ul class="nav navbar-nav navbar-right">
<li>
<%= link_to logout_path do %>
<label class="glyphicon glyphicon-log-out"></label>
Logout
<% end %>
</li>
</ul>
<% end %>
<% if current_user.try(:admin?) %>
<ul class="nav navbar-nav navbar-right">
<%= content_tag(:li, active_class(admin_path)) do %>
<%= link_to 'Admin', admin_path %>
<% end %>
<%= content_tag(:li, active_class(admin_clients_path, true)) do %>
<%= link_to 'Clients', admin_clients_path %>
<% end %>
<%= content_tag(:li, active_class(unverified_admin_hosts_path, true)) do %>
<%= link_to 'Hosts', unverified_admin_hosts_path %>
<% end %>
<%= content_tag(:li, { class: "dropdown #{active_class(admin_users_path)[:class]}" }) do %>
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
Users <span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li>
<%= link_to 'All Users', admin_users_path %>
</li>
<li class="divider"></li>
<li>
<%= link_to 'ViMa Users', admin_users_path(type: :vima) %>
</li>
<li>
<%= link_to 'Institutional Users', admin_users_path(type: :institutional) %>
</li>
<li>
<%= link_to 'Admins', admin_users_path(type: :admin) %>
</li>
<li>
</ul>
<% end %>
+ <%= content_tag(:li, active_class(admin_pools_path)) do %>
+ <%= link_to 'Pools', admin_pools_path %>
+ <% end %>
<%= content_tag(:li, active_class(admin_settings_path)) do %>
<%= link_to 'Settings', admin_settings_path %>
<% end %>
</ul>
<% end %>
</div><!--/.nav-collapse -->
</div>
</nav>
diff --git a/config/routes.rb b/config/routes.rb
index 34d5ee1..d652ea1 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,91 +1,93 @@
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
+
+ resources :pools, only: [:index, :new, :create, :show, :edit, :update]
end
end
diff --git a/spec/routing/admin/pools_routing_spec.rb b/spec/routing/admin/pools_routing_spec.rb
new file mode 100644
index 0000000..a49827d
--- /dev/null
+++ b/spec/routing/admin/pools_routing_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+describe Admin::PoolsController do
+ it 'routes GET /admin/pools' do
+ expect(get('/admin/pools')).to route_to(controller: 'admin/pools', action: 'index')
+ end
+
+ it 'routes GET /admin/pools/new' do
+ expect(get('/admin/pools/new')).
+ to route_to(controller: 'admin/pools', action: 'new')
+ end
+
+ it 'routes GET /admin/pools/1' do
+ expect(get('/admin/pools/1')).
+ to route_to(controller: 'admin/pools', action: 'show', id: '1')
+ end
+
+ it 'routes GET /admin/pools/1/edit' do
+ expect(get('/admin/pools/1/edit')).
+ to route_to(controller: 'admin/pools', action: 'edit', id: '1')
+ end
+
+ it 'routes POST /admin/pools' do
+ expect(post('/admin/pools')).
+ to route_to(controller: 'admin/pools', action: 'create')
+ end
+
+ it 'routes PATCH /admin/pools/1' do
+ expect(patch('/admin/pools/1')).
+ to route_to(controller: 'admin/pools', action: 'update', id: '1')
+ end
+end
Event Timeline
Log In to Comment