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.
Files changed (112) hide show
  1. data/AUTHORS +2 -0
  2. data/ChangeLog +454 -0
  3. data/README.bacuview +7 -0
  4. data/README.rails +153 -0
  5. data/Rakefile +94 -0
  6. data/app/controllers/application.rb +11 -0
  7. data/app/controllers/client_controller.rb +27 -0
  8. data/app/controllers/job_controller.rb +100 -0
  9. data/app/controllers/jobmedia_controller.rb +2 -0
  10. data/app/controllers/media_controller.rb +44 -0
  11. data/app/controllers/misc_controller.rb +14 -0
  12. data/app/controllers/pool_controller.rb +38 -0
  13. data/app/helpers/application_helper.rb +88 -0
  14. data/app/helpers/client_finder.rb +112 -0
  15. data/app/helpers/client_helper.rb +45 -0
  16. data/app/helpers/job_helper.rb +56 -0
  17. data/app/helpers/jobmedia_helper.rb +2 -0
  18. data/app/helpers/media_helper.rb +14 -0
  19. data/app/helpers/misc_helper.rb +15 -0
  20. data/app/helpers/pool_helper.rb +2 -0
  21. data/app/models/client.rb +14 -0
  22. data/app/models/job.rb +21 -0
  23. data/app/models/jobmedia.rb +9 -0
  24. data/app/models/media.rb +23 -0
  25. data/app/models/pool.rb +13 -0
  26. data/app/views/client/_spec.rhtml +1 -0
  27. data/app/views/client/check.rhtml +1 -0
  28. data/app/views/client/index.rhtml +27 -0
  29. data/app/views/client/show.rhtml +8 -0
  30. data/app/views/job/_spec.rhtml +7 -0
  31. data/app/views/job/index.rhtml +38 -0
  32. data/app/views/job/last.rhtml +52 -0
  33. data/app/views/job/show.rhtml +19 -0
  34. data/app/views/job/spec.rhtml +38 -0
  35. data/app/views/layouts/bacuview-layout.rhtml +50 -0
  36. data/app/views/media/_spec.rhtml +1 -0
  37. data/app/views/media/index.rhtml +30 -0
  38. data/app/views/media/show.rhtml +8 -0
  39. data/app/views/misc/about.rhtml +2 -0
  40. data/app/views/misc/help.rhtml +1 -0
  41. data/app/views/pool/_spec.rhtml +1 -0
  42. data/app/views/pool/index.rhtml +29 -0
  43. data/app/views/pool/show.rhtml +8 -0
  44. data/bin/bacuview +3 -0
  45. data/config/bacuview.yml.template +2 -0
  46. data/config/boot.rb +19 -0
  47. data/config/database.yml.template +13 -0
  48. data/config/environment.rb +87 -0
  49. data/config/environments/development.rb +19 -0
  50. data/config/environments/production.rb +19 -0
  51. data/config/environments/test.rb +19 -0
  52. data/config/routes.rb +61 -0
  53. data/log/development.log +0 -0
  54. data/log/production.log +0 -0
  55. data/log/test.log +0 -0
  56. data/public/404.html +8 -0
  57. data/public/500.html +8 -0
  58. data/public/bacu-bat.png +0 -0
  59. data/public/bacuweb.css +7 -0
  60. data/public/client-sophie-thumb.png +0 -0
  61. data/public/client-sophie.png +0 -0
  62. data/public/clients-thumb.png +0 -0
  63. data/public/clients.png +0 -0
  64. data/public/dispatch.cgi +10 -0
  65. data/public/dispatch.fcgi +24 -0
  66. data/public/dispatch.rb +10 -0
  67. data/public/favicon.ico +0 -0
  68. data/public/home.html +108 -0
  69. data/public/images/bacu-bat.png +0 -0
  70. data/public/images/busy.png +0 -0
  71. data/public/images/dunno.png +0 -0
  72. data/public/images/error.png +0 -0
  73. data/public/images/okay.png +0 -0
  74. data/public/images/pool_pie.png +0 -0
  75. data/public/install.html +156 -0
  76. data/public/javascripts/application.js +2 -0
  77. data/public/javascripts/client_check.js +24 -0
  78. data/public/javascripts/controls.js +815 -0
  79. data/public/javascripts/dragdrop.js +724 -0
  80. data/public/javascripts/effects.js +953 -0
  81. data/public/javascripts/prototype.js +1985 -0
  82. data/public/job-1449-thumb.png +0 -0
  83. data/public/job-1449.png +0 -0
  84. data/public/job-last-thumb.png +0 -0
  85. data/public/job-last.png +0 -0
  86. data/public/jobs-thumb.png +0 -0
  87. data/public/jobs.png +0 -0
  88. data/public/media-39-thumb.png +0 -0
  89. data/public/media-39.png +0 -0
  90. data/public/media-thumb.png +0 -0
  91. data/public/media.png +0 -0
  92. data/public/news.html +144 -0
  93. data/public/pool-lto3-thumb.png +0 -0
  94. data/public/pool-lto3.png +0 -0
  95. data/public/pools-thumb.png +0 -0
  96. data/public/pools.png +0 -0
  97. data/public/robots.txt +1 -0
  98. data/public/stylesheets/bacuview.css +37 -0
  99. data/script/about +3 -0
  100. data/script/breakpointer +3 -0
  101. data/script/console +3 -0
  102. data/script/destroy +3 -0
  103. data/script/generate +3 -0
  104. data/script/performance/benchmarker +3 -0
  105. data/script/performance/profiler +3 -0
  106. data/script/plugin +3 -0
  107. data/script/process/reaper +3 -0
  108. data/script/process/spawner +3 -0
  109. data/script/process/spinner +3 -0
  110. data/script/runner +3 -0
  111. data/script/server +3 -0
  112. 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,2 @@
1
+ class JobmediaController < ApplicationController
2
+ 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,14 @@
1
+ class MiscController < ApplicationController
2
+ layout "bacuview-layout"
3
+
4
+ def about
5
+ @page_title = "About"
6
+ params['action'] = nil
7
+ end
8
+
9
+ def help
10
+ @page_title = "Help"
11
+ params['action'] = nil
12
+ end
13
+
14
+ 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,2 @@
1
+ module JobmediaHelper
2
+ 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
@@ -0,0 +1,2 @@
1
+ module PoolHelper
2
+ end