Page Menu
Home
GRNET
Search
Configure Global Search
Log In
Files
F461665
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, May 18, 12:06 PM
Size
17 KB
Mime Type
text/x-diff
Expires
Tue, May 20, 12:06 PM (1 d, 13 h)
Engine
blob
Format
Raw Data
Handle
220389
Attached To
rARCHIVING archiving
View Options
diff --git a/app/controllers/admin/clients_controller.rb b/app/controllers/admin/clients_controller.rb
index b3ed6bf..d954390 100644
--- a/app/controllers/admin/clients_controller.rb
+++ b/app/controllers/admin/clients_controller.rb
@@ -1,106 +1,120 @@
class Admin::ClientsController < Admin::BaseController
before_action :fetch_client, only: [:show, :jobs, :logs, :stats, :configuration,
:disable, :revoke, :block, :unblock]
before_action :fetch_logs, only: [:logs]
# Shows all available clients
#
# GET /admin/clients
def index
- @clients = Client.includes(:jobs).all
+ @clients = Client.joins(:host).includes(:jobs).distinct.all
@client_ids = @clients.map(&:id)
fetch_jobs_info
end
+ # Shows all clients that are not in Archiving but are still persisted to bacula
+ #
+ # GET /admin/clients/obsolete
+ def obsolete
+ in_archiving = Client.joins(:host).distinct.pluck(:ClientId)
+ @clients = Client.includes(:jobs).where.not(ClientId: in_archiving).
+ distinct.all
+ @client_ids = @clients.map(&:id)
+ fetch_jobs_info
+ @obsolete = true
+
+ render :index
+ end
+
# Shows a specific client
#
# GET /admin/clients/1
def show
if !@client.host.present?
flash[:alert] = 'Client not configured through Archiving'
return redirect_to admin_clients_path
end
get_charts
end
# GET /admin/clients/1/jobs
def jobs
@jobs = @client.recent_jobs.page(params[:page])
end
# GET /admin/clients/1/logs
def logs
end
# GET /admin/clients/1/stats
# POST /admin/clients/1/stats
def stats
get_charts
end
# GET /admin/clients/1/configuration
def configuration
end
# POST /admin/clients/1/disable
def disable
if @client.host.disable_jobs_and_update
flash[:success] = 'Client disabled'
else
flash[:error] = 'Something went wrong, try again later'
end
redirect_to admin_client_path(@client)
end
# POST /admin/clients/1/block
def block
if @client.host.disable_jobs_and_lock
flash[:success] = 'Client is disabled and locked'
else
flash[:error] = 'Something went wrong, try again'
end
redirect_to admin_client_path(@client)
end
# POST /admin/clients/1/unblock
def unblock
if @client.host.unblock
flash[:success] = 'Client can now be configured by users'
else
flash[:error] = 'Client is still locked'
end
redirect_to admin_client_path(@client)
end
# DELETE /admin/clients/1/revoke
def revoke
if @client.host.remove_from_bacula(true)
flash[:success] = 'Client removed. It will be visible to until its jobs get cleaned up'
else
flash[:error] = 'Something went wrong, try again later'
end
redirect_to admin_clients_path
end
private
# Fetches the client based on the given id
def fetch_client
@client = Client.find(params[:id])
@client_ids = [@client.id]
end
def fetch_jobs_info
@stats = JobStats.new
end
def get_charts
@job_status = ChartGenerator.job_statuses(@client_ids, days_ago)
@job_stats = ChartGenerator.job_stats(@client_ids, days_ago - 1)
end
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 963b0c4..0ac92a7 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -1,206 +1,206 @@
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))
+ def active_class(paths, partial = false)
+ if [paths].flatten.any? { |path| 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 name, path
end
}.inject { |result, element| result.concat(element) }
end
end
# Constructs a tabbed navigation menu out of the given options
#
# @param options[Hash] a hash containing the menu links in name: path sets
# @return an html ul menu
def tabs_with(options)
content_tag(:ul, class: 'nav nav-tabs', role: 'tablist') do
options.map { |title, path|
content_tag(:li, active_class(path).merge({ role: 'presentation' })) do
link_to title, path, 'role' => 'tab', 'aria-controls' => title.downcase
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
# Generates a percentage and adds some color coding for better visibility
#
# @param ratio [Numeric] the ratio
# @param quota [Integer] the client's space quota
#
# @return [String] an html label tag
def pretty_percentage(ratio, quota)
color = ([[ratio, 0.2].max, 0.99].min * 256).to_i.to_s(16) << '0000'
content_tag(:label, class: 'label', style: "background-color:##{color}") do
number_to_percentage(100 * ratio, precision: 1)
end
end
# Generates a button that may be disabled
#
# @param disabled[Boolean]
# @param display_text[String]
# @param url[String]
# @param opts[Hash]
def button_or_disabled_with_label(disabled, display_text, url, opts = {})
icon_class = opts.delete(:icon_class)
text_class = opts.delete(:text_class)
if disabled
url = '#'
opts.merge!(disabled: true)
opts.reverse_merge!(title: 'Client is blocked')
opts.delete(:method)
else
opts.delete(:title)
end
link_to url, opts do
[
content_tag(:label, class: [icon_class, text_class].join(' ')) { },
display_text
].join(' ').html_safe
end
end
# Generates a span that contains a text and a questionmark label.
# hovering on that questionmark will display a helper text
#
# @param text[String] the displayed text
# @param tooltip[String] the displayed helper text
def tooltip_label(text, tooltip)
content_tag(:span, class: "data-toggle", title: tooltip) do
[
text,
content_tag(:label, class: "glyphicon glyphicon-question-sign") { }
].join(' ').html_safe
end
end
# Generate a div that contains a helper text that is properly aligned with a form
#
# @param text[String] the displayed text
# @param label_class[String] the form's labe_col
# @param control_class[String] the form's control_col
def help_block(text, label_class, control_class)
content_tag(:div, class: 'form-group') do
[
content_tag(:label, class: "#{label_class} control-label") { },
content_tag(:div, class: control_class) do
content_tag(:p, class: 'form-control-static help-block') do
text
end
end
].join(' ').html_safe
end
end
end
diff --git a/app/views/admin/clients/index.html.erb b/app/views/admin/clients/index.html.erb
index bcfe5d5..df709bc 100644
--- a/app/views/admin/clients/index.html.erb
+++ b/app/views/admin/clients/index.html.erb
@@ -1,29 +1,36 @@
-<h1>Bacula Clients</h1>
+<% if @obsolete %>
+ <h1 data-toggle="tooltip" title="These clients are not in Archiving but they are present to the Bacula db">
+ Obsolete Clients
+ <small><label class="glyphicon glyphicon-question-sign"></label></small>
+ </h1>
+<% else %>
+ <h1>Bacula Clients</h1>
+<% end %>
<div class="datatable-wrapper">
<div class="table-responsive">
<table id="admin_clients" class="table table-striped table-bordered table-condensed">
<thead>
<tr>
<th class="no-search">ClientId</th>
<th>Name</th>
<th>Uname</th>
<th>Client Type</th>
<th class="no-search">Active Jobs</th>
<th class="no-search">Last Backup</th>
<th class="no-search">File Ret. (days)</th>
<th class="no-search">Job Ret. (days)</th>
<th class="no-search">Space Used</th>
<th class="no-search">Quota</th>
<th class="no-search">Space Used %</th>
<th class="no-search">Files</th>
<th class="neither-search-nor-order">AutoPrune</th>
</tr>
</thead>
<tbody>
<%= render partial: 'client', collection: @clients %>
</tbody>
</table>
</div>
</div>
diff --git a/app/views/shared/_admin.html.erb b/app/views/shared/_admin.html.erb
index 51d9e2e..367d2c1 100644
--- a/app/views/shared/_admin.html.erb
+++ b/app/views/shared/_admin.html.erb
@@ -1,55 +1,61 @@
<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 %>
+ <%= content_tag(:li, active_class(admin_clients_path)) do %>
<%= link_to 'Clients', admin_clients_path %>
<% end %>
<%= content_tag(:li,
- { class: "dropdown #{active_class(unverified_admin_hosts_path)[:class]}" }) do %>
+ { class: ['dropdown',
+ active_class([obsolete_admin_clients_path,
+ unverified_admin_hosts_path,
+ rejected_admin_hosts_path])[:class]].join(' ')}) do %>
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
- Unverified Clients <span class="caret"></span>
+ Inactive Clients <span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li>
+ <%= link_to 'Obsolete Clients', obsolete_admin_clients_path %>
+ </li>
+ <li>
<%= link_to 'Unverified Clients', unverified_admin_hosts_path %>
</li>
<li>
<%= link_to 'Rejected Clients', rejected_admin_hosts_path %>
</li>
</ul>
<%#= content_tag(:li, active_class(unverified_admin_hosts_path, true)) do %>
<%#= link_to 'Unverified Clients', 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_faqs_path)) do %>
<%= link_to 'FAQ', admin_faqs_path %>
<% 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>
diff --git a/config/routes.rb b/config/routes.rb
index 5babe46..82f3dbb 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,132 +1,136 @@
Rails.application.routes.draw do
root 'application#index'
get 'faq' => 'application#faq'
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
delete :remove_user
end
collection do
post :index
end
end
resources :clients, only: [], param: :client_id do
member do
get :tree
end
end
resources :invitations, only: [:create]
get '/invitations/:host_id/:verification_code/accept' => 'invitations#accept',
as: :accept_invitation
resources :hosts, only: [:new, :create, :show, :edit, :update, :destroy] do
member do
post :submit_config
post :disable
post :regenerate_token
delete :revoke
get :fd_config
end
collection do
get :fetch_vima_hosts, to: 'hosts#fetch_vima_hosts', as: :fetch_vima
end
resources :simple_configs, only: [:new, :create]
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
resources :users, only: :show do
member do
patch :generate_token
end
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
+
+ collection do
+ get :obsolete
+ end
end
resources :hosts, only: [] do
collection do
get :unverified
get :rejected
end
member do
post :verify
post :reject
put :set_quota
end
end
resources :users, only: [:index, :new, :create, :show, :edit, :update] do
member do
patch :ban
patch :unban
patch :revoke_admin
patch :grant_admin
end
end
resources :pools, only: [:index, :new, :create]
resources :faqs
end
namespace :api, defaults: { format: :json } do
scope module: :v1, constraints: ApiVersion.new(version: 1, default: true) do
resources :clients, only: [:index, :show] do
member do
post :backup
post :restore
end
end
end
end
end
Event Timeline
Log In to Comment