bacuview 1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/AUTHORS +2 -0
- data/ChangeLog +454 -0
- data/README.bacuview +7 -0
- data/README.rails +153 -0
- data/Rakefile +94 -0
- data/app/controllers/application.rb +11 -0
- data/app/controllers/client_controller.rb +27 -0
- data/app/controllers/job_controller.rb +100 -0
- data/app/controllers/jobmedia_controller.rb +2 -0
- data/app/controllers/media_controller.rb +44 -0
- data/app/controllers/misc_controller.rb +14 -0
- data/app/controllers/pool_controller.rb +38 -0
- data/app/helpers/application_helper.rb +88 -0
- data/app/helpers/client_finder.rb +112 -0
- data/app/helpers/client_helper.rb +45 -0
- data/app/helpers/job_helper.rb +56 -0
- data/app/helpers/jobmedia_helper.rb +2 -0
- data/app/helpers/media_helper.rb +14 -0
- data/app/helpers/misc_helper.rb +15 -0
- data/app/helpers/pool_helper.rb +2 -0
- data/app/models/client.rb +14 -0
- data/app/models/job.rb +21 -0
- data/app/models/jobmedia.rb +9 -0
- data/app/models/media.rb +23 -0
- data/app/models/pool.rb +13 -0
- data/app/views/client/_spec.rhtml +1 -0
- data/app/views/client/check.rhtml +1 -0
- data/app/views/client/index.rhtml +27 -0
- data/app/views/client/show.rhtml +8 -0
- data/app/views/job/_spec.rhtml +7 -0
- data/app/views/job/index.rhtml +38 -0
- data/app/views/job/last.rhtml +52 -0
- data/app/views/job/show.rhtml +19 -0
- data/app/views/job/spec.rhtml +38 -0
- data/app/views/layouts/bacuview-layout.rhtml +50 -0
- data/app/views/media/_spec.rhtml +1 -0
- data/app/views/media/index.rhtml +30 -0
- data/app/views/media/show.rhtml +8 -0
- data/app/views/misc/about.rhtml +2 -0
- data/app/views/misc/help.rhtml +1 -0
- data/app/views/pool/_spec.rhtml +1 -0
- data/app/views/pool/index.rhtml +29 -0
- data/app/views/pool/show.rhtml +8 -0
- data/bin/bacuview +3 -0
- data/config/bacuview.yml.template +2 -0
- data/config/boot.rb +19 -0
- data/config/database.yml.template +13 -0
- data/config/environment.rb +87 -0
- data/config/environments/development.rb +19 -0
- data/config/environments/production.rb +19 -0
- data/config/environments/test.rb +19 -0
- data/config/routes.rb +61 -0
- data/log/development.log +0 -0
- data/log/production.log +0 -0
- data/log/test.log +0 -0
- data/public/404.html +8 -0
- data/public/500.html +8 -0
- data/public/bacu-bat.png +0 -0
- data/public/bacuweb.css +7 -0
- data/public/client-sophie-thumb.png +0 -0
- data/public/client-sophie.png +0 -0
- data/public/clients-thumb.png +0 -0
- data/public/clients.png +0 -0
- data/public/dispatch.cgi +10 -0
- data/public/dispatch.fcgi +24 -0
- data/public/dispatch.rb +10 -0
- data/public/favicon.ico +0 -0
- data/public/home.html +108 -0
- data/public/images/bacu-bat.png +0 -0
- data/public/images/busy.png +0 -0
- data/public/images/dunno.png +0 -0
- data/public/images/error.png +0 -0
- data/public/images/okay.png +0 -0
- data/public/images/pool_pie.png +0 -0
- data/public/install.html +156 -0
- data/public/javascripts/application.js +2 -0
- data/public/javascripts/client_check.js +24 -0
- data/public/javascripts/controls.js +815 -0
- data/public/javascripts/dragdrop.js +724 -0
- data/public/javascripts/effects.js +953 -0
- data/public/javascripts/prototype.js +1985 -0
- data/public/job-1449-thumb.png +0 -0
- data/public/job-1449.png +0 -0
- data/public/job-last-thumb.png +0 -0
- data/public/job-last.png +0 -0
- data/public/jobs-thumb.png +0 -0
- data/public/jobs.png +0 -0
- data/public/media-39-thumb.png +0 -0
- data/public/media-39.png +0 -0
- data/public/media-thumb.png +0 -0
- data/public/media.png +0 -0
- data/public/news.html +144 -0
- data/public/pool-lto3-thumb.png +0 -0
- data/public/pool-lto3.png +0 -0
- data/public/pools-thumb.png +0 -0
- data/public/pools.png +0 -0
- data/public/robots.txt +1 -0
- data/public/stylesheets/bacuview.css +37 -0
- data/script/about +3 -0
- data/script/breakpointer +3 -0
- data/script/console +3 -0
- data/script/destroy +3 -0
- data/script/generate +3 -0
- data/script/performance/benchmarker +3 -0
- data/script/performance/profiler +3 -0
- data/script/plugin +3 -0
- data/script/process/reaper +3 -0
- data/script/process/spawner +3 -0
- data/script/process/spinner +3 -0
- data/script/runner +3 -0
- data/script/server +3 -0
- metadata +168 -0
data/Rakefile
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Add your own tasks in files placed in lib/tasks ending in .rake, for
|
|
2
|
+
# example lib/tasks/switchtower.rake, and they will automatically be
|
|
3
|
+
# available to Rake.
|
|
4
|
+
|
|
5
|
+
require(File.join(File.dirname(__FILE__), 'config', 'boot'))
|
|
6
|
+
require 'rake'
|
|
7
|
+
require 'rake/testtask'
|
|
8
|
+
require 'rake/rdoctask'
|
|
9
|
+
require 'tasks/rails'
|
|
10
|
+
|
|
11
|
+
desc "Truncate log files."
|
|
12
|
+
task :log_trunc do
|
|
13
|
+
sh ">log/development.log >log/test.log >log/production.log"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
desc "Clean up junk files"
|
|
17
|
+
task :clean => :log_trunc do
|
|
18
|
+
sh "rm -f tmp/sessions/ruby_sess.* svn-commit*.tmp"
|
|
19
|
+
sh "find . -name \\*~ -exec rm -f \\{\\} \\;"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# This voodoo generates an invisible "rake package" target.
|
|
23
|
+
require 'rubygems'
|
|
24
|
+
require 'rake/gempackagetask'
|
|
25
|
+
Gem::manage_gems
|
|
26
|
+
|
|
27
|
+
view_spec = Gem::Specification.new do |s|
|
|
28
|
+
s.platform = Gem::Platform::RUBY
|
|
29
|
+
s.name = "bacuview"
|
|
30
|
+
s.version = "1.5"
|
|
31
|
+
s.author = "John Kodis"
|
|
32
|
+
s.email = "john@kodis.org"
|
|
33
|
+
s.summary = "A web app for monitoring a Bacula backup system."
|
|
34
|
+
s.description = <<-EOF
|
|
35
|
+
Bacuview is a web application that provides a view into the
|
|
36
|
+
current state of a Bacula backup system.
|
|
37
|
+
EOF
|
|
38
|
+
s.files = FileList[ '[A-Z]*',
|
|
39
|
+
'log/*.log',
|
|
40
|
+
'app/**/*.rb',
|
|
41
|
+
'app/**/*.rhtml',
|
|
42
|
+
'script/**/*',
|
|
43
|
+
'public/**/*',
|
|
44
|
+
'config/*.rb',
|
|
45
|
+
'config/*.template',
|
|
46
|
+
'config/environments/*',
|
|
47
|
+
].to_a
|
|
48
|
+
s.executables << 'bacuview'
|
|
49
|
+
end
|
|
50
|
+
view_spec.add_dependency('rails')
|
|
51
|
+
Rake::GemPackageTask.new(view_spec) do |pkg|
|
|
52
|
+
pkg.need_tar = true
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
watch_spec = Gem::Specification.new do |s|
|
|
56
|
+
s.platform = Gem::Platform::RUBY
|
|
57
|
+
s.name = "bacuwatch"
|
|
58
|
+
s.version = "1.5"
|
|
59
|
+
s.author = "John Kodis"
|
|
60
|
+
s.email = "john@kodis.org"
|
|
61
|
+
s.summary = "An app to periodically report on a Bacula backup system."
|
|
62
|
+
s.description = <<-EOF
|
|
63
|
+
Bacuwatch is an application normally run from a cron job to
|
|
64
|
+
email out reports on the status of a series of bacula jobs.
|
|
65
|
+
EOF
|
|
66
|
+
s.files = FileList[ 'bacuwatch/bacuwatch',
|
|
67
|
+
'bacuwatch/*.template',
|
|
68
|
+
'bacuwatch/README.bacuwatch',
|
|
69
|
+
].to_a
|
|
70
|
+
s.executables << 'bacuwatch'
|
|
71
|
+
end
|
|
72
|
+
Rake::GemPackageTask.new(watch_spec) do |pkg|
|
|
73
|
+
pkg.need_tar = true
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
desc "Prepare tar and gem files for distribution"
|
|
77
|
+
task :dist => [ :log_trunc,
|
|
78
|
+
"pkg/#{view_spec.name}-#{view_spec.version}.tgz",
|
|
79
|
+
"pkg/#{view_spec.name}-#{view_spec.version}.gem",
|
|
80
|
+
"pkg/#{watch_spec.name}-#{watch_spec.version}.tgz",
|
|
81
|
+
"pkg/#{watch_spec.name}-#{watch_spec.version}.gem" ]
|
|
82
|
+
|
|
83
|
+
#desc "Prepare a distribution tar file"
|
|
84
|
+
#task :dist do
|
|
85
|
+
# package_name = 'bacuview'
|
|
86
|
+
# package_version = '1.4'
|
|
87
|
+
# package = package_name + '-' + package_version
|
|
88
|
+
#
|
|
89
|
+
# sh "svn checkout svn://rubyforge.org//var/svn/bacuview && " +
|
|
90
|
+
# "(echo #{package}; svn info | grep ^Revision:) >bacuview/VERSION && " +
|
|
91
|
+
# "mv bacuview #{package} && " +
|
|
92
|
+
# "find #{package} -type d -name .svn | xargs rm -rf && " +
|
|
93
|
+
# "tar czf #{package}.tar.gz #{package}"
|
|
94
|
+
#end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Filters added to this controller will be run for all controllers in
|
|
2
|
+
# the application. Likewise, all the methods added will be available
|
|
3
|
+
# for all controllers.
|
|
4
|
+
|
|
5
|
+
class ApplicationController < ActionController::Base
|
|
6
|
+
session :disabled => true
|
|
7
|
+
|
|
8
|
+
def sort_key(key_sym, key_sql)
|
|
9
|
+
params[key_sym].split(".").map{ |k| key_sql[k] }.join(",")
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
class ClientController < ApplicationController
|
|
2
|
+
layout "bacuview-layout"
|
|
3
|
+
|
|
4
|
+
def key_sql
|
|
5
|
+
{ "name" => "name", "name-" => "name desc",
|
|
6
|
+
"prune" => "autoprune", "prune-" => "autoprune desc",
|
|
7
|
+
"fretain" => "fileretention", "fretain-" => "fileretention desc",
|
|
8
|
+
"jretain" => "jobretention", "jretain-" => "jobretention desc",
|
|
9
|
+
"uname" => "uname", "uname-" => "uname desc" }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def index
|
|
13
|
+
@page_title = "Clients"
|
|
14
|
+
@clients = Client.find(:all, :order => sort_key(:csort, key_sql))
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def show
|
|
18
|
+
cid = params[:cid]
|
|
19
|
+
@client = Client.find(cid)
|
|
20
|
+
@page_title = "Client " + @client.name
|
|
21
|
+
params[:action] = params[:cid] = nil
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def check
|
|
25
|
+
render(:layout => false)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
class JobController < ApplicationController
|
|
2
|
+
layout "bacuview-layout"
|
|
3
|
+
|
|
4
|
+
def spec_to_params
|
|
5
|
+
if params["commit"]
|
|
6
|
+
params.delete("commit")
|
|
7
|
+
if params["spec"]
|
|
8
|
+
params["name"] = params["spec"]["name"] if params["spec"]["name"]
|
|
9
|
+
params["days"] = params["spec"]["days"] if params["spec"]["days"]
|
|
10
|
+
params.delete("spec")
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def key_sql
|
|
16
|
+
{ "id" => "jobid", "id-" => "jobid desc",
|
|
17
|
+
"name" => "name", "name-" => "name desc",
|
|
18
|
+
"type" => "type", "type-" => "type desc",
|
|
19
|
+
"level" => "level", "level-" => "level desc",
|
|
20
|
+
"status" => "jobstatus", "status-" => "jobstatus desc",
|
|
21
|
+
"bytes" => "jobbytes", "bytes-" => "jobbytes desc",
|
|
22
|
+
"rate" => "rate", "rate-" => "rate desc",
|
|
23
|
+
"time" => "time", "time-" => "time desc",
|
|
24
|
+
"sched" => "schedtime", "sched-" => "schedtime desc",
|
|
25
|
+
"start" => "starttime", "start-" => "starttime desc",
|
|
26
|
+
"finish" => "endtime", "finish-" => "endtime desc", }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def index
|
|
30
|
+
@page_title = "Jobs"
|
|
31
|
+
where = []
|
|
32
|
+
wargs = []
|
|
33
|
+
if params[:days].to_i != 0
|
|
34
|
+
today = Time::parse(TODAY || "now")
|
|
35
|
+
where << "schedtime > ?"
|
|
36
|
+
wargs << params[:days].to_i.day.ago(today).midnight.tomorrow
|
|
37
|
+
end
|
|
38
|
+
if params[:name] != '0'
|
|
39
|
+
where << "name = ?"
|
|
40
|
+
wargs << params[:name]
|
|
41
|
+
end
|
|
42
|
+
if params[:err].to_i == 1
|
|
43
|
+
where << "jobstatus != 'T'"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
fargs = {}
|
|
47
|
+
fargs[:select] = "*, " +
|
|
48
|
+
"case when (endtime > starttime) then " +
|
|
49
|
+
( MYSQL ?
|
|
50
|
+
"jobbytes / (endtime - starttime) " :
|
|
51
|
+
"jobbytes / extract(epoch from endtime - starttime) " ) +
|
|
52
|
+
"else null end as rate, " +
|
|
53
|
+
"cast(endtime-starttime as time) as time"
|
|
54
|
+
fargs[:order] = sort_key(:jsort, key_sql)
|
|
55
|
+
if where.size != 0
|
|
56
|
+
fargs[:conditions] = [ where.join(' and '), wargs ].flatten!
|
|
57
|
+
end
|
|
58
|
+
@jobs = Job.find(:all, fargs)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def show
|
|
62
|
+
jid = params[:jid]
|
|
63
|
+
@page_title = "Job " + jid
|
|
64
|
+
params[:action] = params[:jid] = nil
|
|
65
|
+
@job = Job.find(jid)
|
|
66
|
+
@jobmedia = Jobmedia.find_by_sql(
|
|
67
|
+
'select mediaid,volumename from Media where mediaid in ' +
|
|
68
|
+
' (select distinct mediaid from JobMedia where jobid = ' + jid +
|
|
69
|
+
' ) order by mediaid' )
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def gen_client_lists
|
|
73
|
+
@client_list = []
|
|
74
|
+
@platform_list = []
|
|
75
|
+
Client.find(:all, :select => 'name, uname').each { |client|
|
|
76
|
+
@client_list << client[:name]
|
|
77
|
+
platform = client[:uname].split(',')[1]
|
|
78
|
+
platform and platform.capitalize!
|
|
79
|
+
@platform_list.include?(platform) or @platform_list << platform
|
|
80
|
+
}
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def last_job(name, level)
|
|
84
|
+
Job.find(:first,
|
|
85
|
+
:select => "StartTime, JobFiles, JobBytes",
|
|
86
|
+
:order => "starttime desc",
|
|
87
|
+
:conditions =>
|
|
88
|
+
[ "type='B' and jobstatus='T' and level=? and name=? ",
|
|
89
|
+
level, name ] )
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def last
|
|
93
|
+
name = params[:name]
|
|
94
|
+
@page_title = name
|
|
95
|
+
@full = last_job(name, "F")
|
|
96
|
+
@diff = last_job(name, "D")
|
|
97
|
+
@incr = last_job(name, "I")
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
class MediaController < ApplicationController
|
|
2
|
+
layout "bacuview-layout"
|
|
3
|
+
|
|
4
|
+
# FIXTHIS: sorting on pool.volretention sorts by media.volretention,
|
|
5
|
+
# and vise versa. This is way wrong, but easy to work around.
|
|
6
|
+
# @media = Media.find_by_sql(
|
|
7
|
+
# 'SELECT * FROM media ' +
|
|
8
|
+
# 'JOIN pool ON pool.poolid=media.poolid ' +
|
|
9
|
+
# 'ORDER BY pool.volretention')
|
|
10
|
+
|
|
11
|
+
def key_sql
|
|
12
|
+
{ "name" => "volumename", "name-" => "volumename desc",
|
|
13
|
+
"slot" => "inchanger,storageid,slot",
|
|
14
|
+
"slot-" => "inchanger desc,storageid desc,slot desc",
|
|
15
|
+
"stat" => "volstatus", "stat-" => "volstatus desc",
|
|
16
|
+
"jobs" => "voljobs", "jobs-" => "voljobs desc",
|
|
17
|
+
"files" => "volfiles", "files-" => "volfiles desc",
|
|
18
|
+
"bytes" => "volbytes", "bytes-" => "volbytes desc",
|
|
19
|
+
"expire" => "expire", "expire-" => "expire desc",
|
|
20
|
+
"retain" => "Pool.volretention", "retain-" => "Pool.volretention desc",
|
|
21
|
+
"pool" => "name", "pool-" => "name desc",
|
|
22
|
+
"ptype" => "pooltype", "ptype-" => "pooltype desc",
|
|
23
|
+
"mtype" => "mediatype", "mtype-" => "mediatype desc" }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def index
|
|
27
|
+
@page_title = "Media"
|
|
28
|
+
@media = Media.find(:all,
|
|
29
|
+
:order => sort_key(:msort, key_sql),
|
|
30
|
+
:joins => 'join Pool on Pool.PoolId=Media.PoolId',
|
|
31
|
+
:select => "*, " +
|
|
32
|
+
( MYSQL ?
|
|
33
|
+
"from_unixtime(unix_timestamp(lastwritten) + Media.volretention) " :
|
|
34
|
+
"lastwritten + media.volretention * interval '1 second'" ) +
|
|
35
|
+
" as expire")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def show
|
|
39
|
+
@media = Media.find(params[:mid])
|
|
40
|
+
@page_title = "Media " + @media.volumename
|
|
41
|
+
params[:action] = params[:mid] = nil
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
class PoolController < ApplicationController
|
|
2
|
+
layout "bacuview-layout"
|
|
3
|
+
|
|
4
|
+
def key_sql
|
|
5
|
+
{ "name" => "name", "name-" => "name desc",
|
|
6
|
+
"type" => "pooltype", "type-" => "pooltype desc",
|
|
7
|
+
"retain" => "volretention", "retain-" => "volretention desc",
|
|
8
|
+
"vols" => "numvols", "vols-" => "numvols desc" }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def index
|
|
12
|
+
@page_title = "Pools"
|
|
13
|
+
@pool = Pool.find(:all, :order => sort_key(:psort, key_sql))
|
|
14
|
+
volume_counts = @pool.map{ |pool| pool.numvols }
|
|
15
|
+
@max_vols = volume_counts.max
|
|
16
|
+
@total_vols = volume_counts.inject(0) {|sum, i| sum + i}
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def show
|
|
20
|
+
@pool = Pool.find(params[:pid])
|
|
21
|
+
@page_title = "Pool " + @pool.name
|
|
22
|
+
params[:action] = params[:pid] = nil
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def dist
|
|
26
|
+
return unless BACUVIEW['have_gruff']
|
|
27
|
+
g = Gruff::Pie.new(500)
|
|
28
|
+
g.theme_37signals
|
|
29
|
+
g.title = "Media Distribution"
|
|
30
|
+
@pool = Pool.find(:all,
|
|
31
|
+
:select => "name, numvols", :order => sort_key(:psort, key_sql))
|
|
32
|
+
for pool in @pool do
|
|
33
|
+
g.data pool.name, [ pool.numvols ]
|
|
34
|
+
end
|
|
35
|
+
send_data(g.to_blob, :disposition => 'inline', :type => 'image/png')
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
module ApplicationHelper
|
|
2
|
+
|
|
3
|
+
def column_header(controller, param_key, title, sort_key)
|
|
4
|
+
s = params[param_key].scan(/([a-z]+)(-?)\.?/)
|
|
5
|
+
header = sort_key
|
|
6
|
+
if s[0][0] == sort_key
|
|
7
|
+
header += ((s[0][1] == "") ? "-" : "")
|
|
8
|
+
if s[1] then header += ("." + s[1][0] + s[1][1]); end
|
|
9
|
+
else
|
|
10
|
+
header += ("." + s[0][0] + s[0][1])
|
|
11
|
+
end
|
|
12
|
+
content_tag('td',
|
|
13
|
+
link_to(title, :controller => controller, param_key => header),
|
|
14
|
+
(s[0][0] == sort_key) ? { :class => 'skey' } : nil)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def client_header(title, sort_key)
|
|
18
|
+
column_header("client", :csort, title, sort_key)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def media_header(title, sort_key)
|
|
22
|
+
column_header("media", :msort, title, sort_key)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def pool_header(title, sort_key)
|
|
26
|
+
column_header("pool", :psort, title, sort_key)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def job_header(title, sort_key)
|
|
30
|
+
column_header("job", :jsort, title, sort_key)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def pretty_key(key)
|
|
34
|
+
key = key.downcase
|
|
35
|
+
oops = {
|
|
36
|
+
'uname' => 'Unix Name',
|
|
37
|
+
'numvols' => 'Number of Volumes',
|
|
38
|
+
'volumename' => 'Volume Name',
|
|
39
|
+
'voluseduration' => 'Volume Use Duration',
|
|
40
|
+
}
|
|
41
|
+
return oops[key] if oops[key]
|
|
42
|
+
|
|
43
|
+
s = key.dup
|
|
44
|
+
s.gsub!(/^vol/, 'Volume ')
|
|
45
|
+
s.gsub!(/poolid/, ' Pool Id')
|
|
46
|
+
s.gsub!(/maxvol/, 'Maximum Volume')
|
|
47
|
+
s.gsub!(/^(media|storage|label|job|migration|pool|client|use|accept|first|end)/, '\1 ')
|
|
48
|
+
s.gsub!(/(time|base|id|retention|files?|bytes?|jobs?|written|changer|volume)$/, ' \1')
|
|
49
|
+
s.split(' ').map{|s| s.capitalize}.join(' ')
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def pretty_val(key, val)
|
|
53
|
+
case key.downcase
|
|
54
|
+
when 'jobtdate', 'volsessiontime'
|
|
55
|
+
val == 0 ? 'None' : Time.at(val)
|
|
56
|
+
when 'type'
|
|
57
|
+
type_name(val).capitalize
|
|
58
|
+
when 'level'
|
|
59
|
+
level_name(val).capitalize
|
|
60
|
+
when 'jobstatus'
|
|
61
|
+
status_name(val).capitalize
|
|
62
|
+
when /retention/
|
|
63
|
+
(val / 86400).to_s + ' days'
|
|
64
|
+
when /bytes/
|
|
65
|
+
number_to_human_size(val)
|
|
66
|
+
else
|
|
67
|
+
val.is_a?(Fixnum) ? number_with_delimiter(val) : val
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def day_names
|
|
72
|
+
[ [ "day", "1" ], [ "week", "7" ], [ "month", "35"], [ "year", "372"],
|
|
73
|
+
[ "All dates", "0" ] ]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def job_names
|
|
77
|
+
jn = Job.find(:all, :order=>'name', :select=>'distinct Name,Type')
|
|
78
|
+
jn.delete_if { |job| job.type != 'B' }
|
|
79
|
+
jn.map { |job| [ job.name, job.name ] }.unshift([ "All jobs", "0" ])
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def selection(hash, key, choices, selected=nil)
|
|
83
|
+
content_tag("select",
|
|
84
|
+
options_for_select(choices, selected),
|
|
85
|
+
:id=>"#{hash}_#{key}", :name=>"#{hash}[#{key}]")
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
def get_token(fd)
|
|
2
|
+
if (r = fd.gets)
|
|
3
|
+
r.strip!
|
|
4
|
+
doc = r.sub!(/\s*(#.*)/, '') ? $1 : nil
|
|
5
|
+
case r
|
|
6
|
+
when /\s*(\w+)\s*\{/
|
|
7
|
+
return [ '{', $1, doc ]
|
|
8
|
+
when /\s*(\w+(\s+\w+)?)\s*=\s*(.+)/
|
|
9
|
+
return [ $1.downcase.tr(' ', ''), $3, doc ]
|
|
10
|
+
when /\s*\}/
|
|
11
|
+
return [ '}', nil, doc ]
|
|
12
|
+
when /^\s*@(.*)/
|
|
13
|
+
return [ '@', $1, doc ]
|
|
14
|
+
when /^$/
|
|
15
|
+
return [ nil, nil, doc ]
|
|
16
|
+
else
|
|
17
|
+
p [ :ELSE, r ]
|
|
18
|
+
return []
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def get_element(fd)
|
|
24
|
+
t = get_token(fd)
|
|
25
|
+
if !t or t[0] != '{'
|
|
26
|
+
return t
|
|
27
|
+
else
|
|
28
|
+
element = [ t ]
|
|
29
|
+
begin
|
|
30
|
+
element << ( t = get_element(fd) )
|
|
31
|
+
end until t[0] == '}'
|
|
32
|
+
return element
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def config_file_ingest(path)
|
|
37
|
+
files = 0
|
|
38
|
+
cfg = []
|
|
39
|
+
Dir.glob(path).each { |fn|
|
|
40
|
+
begin
|
|
41
|
+
fd = File.open(fn)
|
|
42
|
+
while (e = get_element(fd))
|
|
43
|
+
cfg << e
|
|
44
|
+
end
|
|
45
|
+
rescue
|
|
46
|
+
print "Error processing config file ", fn, "\n"
|
|
47
|
+
end
|
|
48
|
+
files = files + 1
|
|
49
|
+
}
|
|
50
|
+
print "Read ", files, " configuration files from ", path, "\n"
|
|
51
|
+
return cfg
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def build_client_map(path, glob)
|
|
55
|
+
path = path || '/etc/bacula'
|
|
56
|
+
glob = glob || '*.conf'
|
|
57
|
+
conf = config_file_ingest(File.join(path, glob))
|
|
58
|
+
map = {}
|
|
59
|
+
begin
|
|
60
|
+
for e in conf
|
|
61
|
+
if e[0] and e[0][1] == 'client'
|
|
62
|
+
name = e.assoc('name')
|
|
63
|
+
addr = e.assoc('address')
|
|
64
|
+
port = e.assoc('port')
|
|
65
|
+
if name and addr
|
|
66
|
+
map[name[1]] = addr[1] + (port ? (':' + port[1]) : '')
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
rescue
|
|
71
|
+
printf "Error extracting client name/host address pairs\n"
|
|
72
|
+
return {}
|
|
73
|
+
end
|
|
74
|
+
return map
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# def put_token(t, i)
|
|
78
|
+
# if t[0]
|
|
79
|
+
# print ' ' * i
|
|
80
|
+
# case t[0]
|
|
81
|
+
# when '{'
|
|
82
|
+
# print t[1], ' {'
|
|
83
|
+
# when '}'
|
|
84
|
+
# print '}'
|
|
85
|
+
# when '@'
|
|
86
|
+
# print '@', t[1]
|
|
87
|
+
# else
|
|
88
|
+
# print t[0], ' = ', t[1]
|
|
89
|
+
# end
|
|
90
|
+
# print ' '
|
|
91
|
+
# end
|
|
92
|
+
# puts t[2] ? t[2] : ''
|
|
93
|
+
# end
|
|
94
|
+
#
|
|
95
|
+
# def put_element(e, i)
|
|
96
|
+
# if ! e[0].is_a?(Array)
|
|
97
|
+
# put_token(e, i)
|
|
98
|
+
# else
|
|
99
|
+
# put_token(e.shift, i)
|
|
100
|
+
# last = e.pop
|
|
101
|
+
# for t in e
|
|
102
|
+
# put_element(t, i+1)
|
|
103
|
+
# end
|
|
104
|
+
# put_token(last, i)
|
|
105
|
+
# end
|
|
106
|
+
# end
|
|
107
|
+
#
|
|
108
|
+
# require 'pp'
|
|
109
|
+
# pp c
|
|
110
|
+
# for e in c
|
|
111
|
+
# put_element(e, 0)
|
|
112
|
+
# end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module ClientHelper
|
|
2
|
+
require 'socket'
|
|
3
|
+
|
|
4
|
+
def host_for_client(client)
|
|
5
|
+
host = CLIENT_MAP[client] or client.gsub(/-fd$/, '')
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def check_interminably(host, port)
|
|
9
|
+
begin
|
|
10
|
+
s = TCPSocket.new(host, port)
|
|
11
|
+
s.close
|
|
12
|
+
s ? "okay.png" : "error.png"
|
|
13
|
+
rescue SocketError
|
|
14
|
+
"dunno.png"
|
|
15
|
+
rescue
|
|
16
|
+
"error.png"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def client_check(host, port = 9102)
|
|
21
|
+
t = Thread.new { check_interminably(host, port) }
|
|
22
|
+
t.join(5) ? t.value : "error.png"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def client_image
|
|
26
|
+
@client = Client.find(params[:id])
|
|
27
|
+
host = host_for_client(@client.name)
|
|
28
|
+
@image = client_check(host)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def check_tag(cid)
|
|
32
|
+
if cid == 0
|
|
33
|
+
content_tag('td',
|
|
34
|
+
content_tag('a', "OK?",
|
|
35
|
+
:href => "#", :onclick => "return check_through(#{@clients.size})"))
|
|
36
|
+
else
|
|
37
|
+
content_tag('td',
|
|
38
|
+
content_tag('a',
|
|
39
|
+
content_tag('div', image_tag('dunno.png', :class => "em1"),
|
|
40
|
+
:class => "center", :id => "client-#{cid}"),
|
|
41
|
+
:href => "#", :onclick => "return check(#{cid})"))
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module JobHelper
|
|
2
|
+
|
|
3
|
+
def when_full(s)
|
|
4
|
+
!s ? '-' : s.strftime('%a %b %d %H:%M')
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def when_tiny(s)
|
|
8
|
+
!s ? '-' : s.strftime('%H:%M %a')
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def human_size_if_nz(val)
|
|
12
|
+
val <= 0 ? '-' : number_to_human_size(val)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def secs_to_hms(sec)
|
|
16
|
+
return '-' if sec <= 0
|
|
17
|
+
s = sec.divmod(60)
|
|
18
|
+
m = s[0].divmod(60)
|
|
19
|
+
sprintf '%d:%02d:%02d', m[0], m[1], s[1]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def type_name(c)
|
|
23
|
+
name = {
|
|
24
|
+
'B' => 'Backup', 'D' => 'Admin', 'V' => 'Verify', 'R' => 'Restore',
|
|
25
|
+
}
|
|
26
|
+
name[c] || c
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def level_name(c)
|
|
30
|
+
name = {
|
|
31
|
+
'F' => 'Full', 'I' => 'Incr', 'D' => 'Diff', 'O' => 'Catalog',
|
|
32
|
+
}
|
|
33
|
+
name[c] || c
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def status_name(c)
|
|
37
|
+
name = {
|
|
38
|
+
'A' => 'Cancel', 'B' => 'Blocked', 'C' => 'Created',
|
|
39
|
+
'D' => 'Diffs', 'R' => 'Running', 'T' => 'Okay',
|
|
40
|
+
'E' => 'Error', 'e' => 'Non-fatal', 'f' => 'Fatal',
|
|
41
|
+
'F' => 'FD Wait', 'M' => 'Mount Wait', 'S' => 'SD Wait',
|
|
42
|
+
'c' => 'FR Wait', 'd' => 'Jmax Wait', 'j' => 'JR Wait',
|
|
43
|
+
'm' => 'Media Wait', 'p' => 'Prio Wait', 's' => 'SR Wait',
|
|
44
|
+
't' => 'Start Wait',
|
|
45
|
+
}
|
|
46
|
+
name[c] || c
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def status_style(s)
|
|
50
|
+
stat = {
|
|
51
|
+
'T' => 'normal', 'R' => 'okay', 'C' => 'pending',
|
|
52
|
+
'A' => 'cancel', 'E' => 'error', 'f' => 'error', }
|
|
53
|
+
content_tag('td', status_name(s), 'class' => stat[s] || 'oops')
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module MediaHelper
|
|
2
|
+
def status_style(s)
|
|
3
|
+
stat = {
|
|
4
|
+
'Append' => 'normal', 'Purged' => 'normal', 'Recycle' => 'normal',
|
|
5
|
+
'Error' => 'error', 'Full' => 'okay', }
|
|
6
|
+
content_tag('td', s, 'class' => stat[s] || 'oops')
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def slot_display(in_changer, storage_id, slot_number)
|
|
10
|
+
return "-" if in_changer == 0
|
|
11
|
+
return slot_number.to_s if CHANGERS <= 1
|
|
12
|
+
return storage_id.to_s + ":" + slot_number.to_s
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module MiscHelper
|
|
2
|
+
|
|
3
|
+
def help_slogan
|
|
4
|
+
slogans =
|
|
5
|
+
[
|
|
6
|
+
"Help? We don't NEED no steenkin' help!",
|
|
7
|
+
"Help serves only to coddle the weak!",
|
|
8
|
+
"Help is only for the weak and timid.",
|
|
9
|
+
"You want HELP? You can't handle the HELP!",
|
|
10
|
+
"Move along now. This is not the help that you are searching for.",
|
|
11
|
+
]
|
|
12
|
+
slogans[rand(slogans.size)]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
end
|