sidekiq-status 0.5.0 → 3.0.3
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.
- checksums.yaml +5 -5
- data/.github/workflows/ci.yaml +53 -0
- data/.gitignore +4 -1
- data/.gitlab-ci.yml +17 -0
- data/Appraisals +11 -0
- data/CHANGELOG.md +41 -0
- data/README.md +148 -41
- data/Rakefile +2 -0
- data/gemfiles/sidekiq_6.1.gemfile +7 -0
- data/gemfiles/sidekiq_6.x.gemfile +7 -0
- data/gemfiles/sidekiq_7.x.gemfile +7 -0
- data/lib/sidekiq-status/client_middleware.rb +54 -1
- data/lib/sidekiq-status/redis_adapter.rb +18 -0
- data/lib/sidekiq-status/redis_client_adapter.rb +14 -0
- data/lib/sidekiq-status/server_middleware.rb +76 -17
- data/lib/sidekiq-status/sidekiq_extensions.rb +7 -0
- data/lib/sidekiq-status/storage.rb +26 -13
- data/lib/sidekiq-status/testing/inline.rb +10 -0
- data/lib/sidekiq-status/version.rb +1 -1
- data/lib/sidekiq-status/web.rb +158 -10
- data/lib/sidekiq-status/worker.rb +12 -5
- data/lib/sidekiq-status.rb +43 -10
- data/sidekiq-status.gemspec +9 -4
- data/spec/environment.rb +1 -0
- data/spec/lib/sidekiq-status/client_middleware_spec.rb +30 -13
- data/spec/lib/sidekiq-status/server_middleware_spec.rb +102 -30
- data/spec/lib/sidekiq-status/web_spec.rb +84 -0
- data/spec/lib/sidekiq-status/worker_spec.rb +21 -2
- data/spec/lib/sidekiq-status_spec.rb +158 -77
- data/spec/spec_helper.rb +104 -24
- data/spec/support/test_jobs.rb +84 -7
- data/web/sidekiq-status-single-web.png +0 -0
- data/web/sidekiq-status-web.png +0 -0
- data/web/views/status.erb +118 -0
- data/web/views/status_not_found.erb +6 -0
- data/web/views/statuses.erb +135 -17
- metadata +102 -16
- data/.travis.yml +0 -2
- data/CHANGELOG +0 -2
data/spec/spec_helper.rb
CHANGED
@@ -1,69 +1,149 @@
|
|
1
1
|
require "rspec"
|
2
|
-
|
3
|
-
require 'celluloid'
|
2
|
+
require 'colorize'
|
4
3
|
require 'sidekiq'
|
4
|
+
|
5
|
+
# Celluloid should only be manually required before Sidekiq versions 4.+
|
6
|
+
require 'sidekiq/version'
|
7
|
+
require 'celluloid' if Gem::Version.new(Sidekiq::VERSION) < Gem::Version.new('4.0')
|
8
|
+
|
5
9
|
require 'sidekiq/processor'
|
6
10
|
require 'sidekiq/manager'
|
7
11
|
require 'sidekiq-status'
|
8
12
|
|
13
|
+
# Clears jobs before every test
|
14
|
+
RSpec.configure do |config|
|
15
|
+
config.before(:each) do
|
16
|
+
Sidekiq.redis { |conn| conn.flushall }
|
17
|
+
client_middleware
|
18
|
+
sleep 0.05
|
19
|
+
end
|
20
|
+
end
|
9
21
|
|
10
22
|
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
|
11
23
|
|
12
|
-
|
13
|
-
|
14
|
-
|
24
|
+
# Configures client middleware
|
25
|
+
def client_middleware client_middleware_options = {}
|
26
|
+
Sidekiq.configure_client do |config|
|
27
|
+
Sidekiq::Status.configure_client_middleware config, client_middleware_options
|
15
28
|
end
|
16
29
|
end
|
17
30
|
|
18
|
-
def
|
31
|
+
def redis_thread messages_limit, *channels
|
32
|
+
|
19
33
|
parent = Thread.current
|
20
34
|
thread = Thread.new {
|
21
|
-
|
35
|
+
messages = []
|
22
36
|
Sidekiq.redis do |conn|
|
23
|
-
|
37
|
+
puts "Subscribing to #{channels} for #{messages_limit.to_s.bold} messages".cyan if ENV['DEBUG']
|
38
|
+
conn.subscribe_with_timeout 60, *channels do |on|
|
24
39
|
on.subscribe do |ch, subscriptions|
|
40
|
+
puts "Subscribed to #{ch}".cyan if ENV['DEBUG']
|
25
41
|
if subscriptions == channels.size
|
26
42
|
sleep 0.1 while parent.status != "sleep"
|
27
43
|
parent.run
|
28
44
|
end
|
29
45
|
end
|
30
46
|
on.message do |ch, msg|
|
31
|
-
|
32
|
-
|
47
|
+
puts "Message received: #{ch} -> #{msg}".white if ENV['DEBUG']
|
48
|
+
messages << msg
|
49
|
+
conn.unsubscribe if messages.length >= messages_limit
|
33
50
|
end
|
34
51
|
end
|
35
52
|
end
|
36
|
-
|
53
|
+
puts "Returing from thread".cyan if ENV['DEBUG']
|
54
|
+
messages
|
37
55
|
}
|
56
|
+
|
38
57
|
Thread.stop
|
39
58
|
yield if block_given?
|
40
59
|
thread
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
def redis_client_thread message_limit, *channels
|
64
|
+
thread = Thread.new {
|
65
|
+
messages = []
|
66
|
+
Sidekiq.redis do |conn|
|
67
|
+
puts "Subscribing to #{channels} for #{message_limit.to_s.bold} messages".cyan if ENV['DEBUG']
|
68
|
+
pubsub = conn.pubsub
|
69
|
+
pubsub.call("SUBSCRIBE", *channels)
|
70
|
+
|
71
|
+
timeouts = 0
|
72
|
+
loop do
|
73
|
+
type, ch, msg = pubsub.next_event(2)
|
74
|
+
next if type == "subscribe"
|
75
|
+
if msg
|
76
|
+
puts "Message received: #{ch} -> #{msg}".white if ENV['DEBUG']
|
77
|
+
messages << msg
|
78
|
+
break if messages.length >= message_limit
|
79
|
+
else
|
80
|
+
# no new message was received in the allocated timeout
|
81
|
+
timeouts += 1
|
82
|
+
break if timeouts >= 30
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
puts "Returing from thread".cyan if ENV['DEBUG']
|
87
|
+
messages
|
88
|
+
}
|
89
|
+
sleep 0.1
|
90
|
+
yield if block_given?
|
91
|
+
thread.join
|
92
|
+
end
|
93
|
+
|
94
|
+
def branched_redis_thread n, *channels, &block
|
95
|
+
if Sidekiq.major_version < 7
|
96
|
+
redis_thread(n, *channels, &block)
|
97
|
+
else
|
98
|
+
redis_client_thread(n, *channels, &block)
|
99
|
+
end
|
41
100
|
end
|
42
101
|
|
43
|
-
def capture_status_updates
|
44
|
-
|
102
|
+
def capture_status_updates n, &block
|
103
|
+
branched_redis_thread(n, "status_updates", &block).value
|
45
104
|
end
|
46
105
|
|
47
|
-
|
106
|
+
# Configures server middleware and launches a sidekiq server
|
107
|
+
def start_server server_middleware_options = {}
|
108
|
+
|
109
|
+
# Creates a process for the Sidekiq server
|
48
110
|
pid = Process.fork do
|
49
|
-
|
50
|
-
|
111
|
+
|
112
|
+
# Redirect the server's outputs
|
113
|
+
$stdout.reopen File::NULL, 'w' unless ENV['DEBUG']
|
114
|
+
$stderr.reopen File::NULL, 'w' unless ENV['DEBUG']
|
115
|
+
|
116
|
+
# Load and configure server options
|
51
117
|
require 'sidekiq/cli'
|
52
|
-
|
118
|
+
|
119
|
+
# Add the server middleware
|
53
120
|
Sidekiq.configure_server do |config|
|
54
|
-
config.
|
55
|
-
config.
|
56
|
-
|
57
|
-
end
|
121
|
+
config.concurrency = 5
|
122
|
+
config.redis = Sidekiq::RedisConnection.create if Sidekiq.major_version < 7
|
123
|
+
Sidekiq::Status.configure_server_middleware config, server_middleware_options
|
58
124
|
end
|
59
|
-
|
125
|
+
|
126
|
+
# Launch
|
127
|
+
puts "Server starting".yellow if ENV['DEBUG']
|
128
|
+
instance = Sidekiq::CLI.instance
|
129
|
+
instance.parse(['-r', File.expand_path('environment.rb', File.dirname(__FILE__))])
|
130
|
+
instance.run
|
131
|
+
|
60
132
|
end
|
61
133
|
|
134
|
+
# Run the client-side code
|
62
135
|
yield
|
63
136
|
|
64
|
-
|
137
|
+
# Pause to ensure all jobs are picked up & started before TERM is sent
|
138
|
+
sleep 0.2
|
139
|
+
|
140
|
+
# Attempt to shut down the server normally
|
65
141
|
Process.kill 'TERM', pid
|
66
|
-
|
142
|
+
Process.wait pid
|
143
|
+
|
67
144
|
ensure
|
145
|
+
|
146
|
+
# Ensure the server is actually dead
|
68
147
|
Process.kill 'KILL', pid rescue "OK" # it's OK if the process is gone already
|
148
|
+
|
69
149
|
end
|
data/spec/support/test_jobs.rb
CHANGED
@@ -1,16 +1,34 @@
|
|
1
|
+
require 'sidekiq-status'
|
2
|
+
|
1
3
|
class StubJob
|
2
4
|
include Sidekiq::Worker
|
3
5
|
include Sidekiq::Status::Worker
|
4
6
|
|
5
|
-
sidekiq_options 'retry' =>
|
7
|
+
sidekiq_options 'retry' => false
|
8
|
+
|
9
|
+
def perform(*args)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class StubNoStatusJob
|
14
|
+
include Sidekiq::Worker
|
15
|
+
|
16
|
+
sidekiq_options 'retry' => false
|
6
17
|
|
7
18
|
def perform(*args)
|
8
19
|
end
|
9
20
|
end
|
10
21
|
|
22
|
+
|
23
|
+
class ExpiryJob < StubJob
|
24
|
+
def expiration
|
25
|
+
15
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
11
29
|
class LongJob < StubJob
|
12
30
|
def perform(*args)
|
13
|
-
sleep args[0] ||
|
31
|
+
sleep args[0] || 0.25
|
14
32
|
end
|
15
33
|
end
|
16
34
|
|
@@ -23,6 +41,13 @@ class DataJob < StubJob
|
|
23
41
|
end
|
24
42
|
end
|
25
43
|
|
44
|
+
class CustomDataJob < StubJob
|
45
|
+
def perform
|
46
|
+
store({mister_cat: 'meow'})
|
47
|
+
sleep 0.5
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
26
51
|
class ProgressJob < StubJob
|
27
52
|
def perform
|
28
53
|
total 500
|
@@ -34,7 +59,7 @@ end
|
|
34
59
|
class ConfirmationJob < StubJob
|
35
60
|
def perform(*args)
|
36
61
|
Sidekiq.redis do |conn|
|
37
|
-
conn.publish "job_messages_#{jid}", "while in #perform, status = #{conn.hget jid, :status}"
|
62
|
+
conn.publish "job_messages_#{jid}", "while in #perform, status = #{conn.hget "sidekiq:status:#{jid}", :status}"
|
38
63
|
end
|
39
64
|
end
|
40
65
|
end
|
@@ -54,13 +79,65 @@ class FailingJob < StubJob
|
|
54
79
|
end
|
55
80
|
end
|
56
81
|
|
82
|
+
class FailingNoStatusJob < StubNoStatusJob
|
83
|
+
def perform
|
84
|
+
raise StandardError
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
class RetryAndFailJob < StubJob
|
89
|
+
sidekiq_options retry: 1
|
90
|
+
|
91
|
+
def perform
|
92
|
+
raise StandardError
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
class FailingHardJob < StubJob
|
97
|
+
def perform
|
98
|
+
raise Exception
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
class FailingHardNoStatusJob < StubNoStatusJob
|
103
|
+
def perform
|
104
|
+
raise Exception
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
class ExitedJob < StubJob
|
109
|
+
def perform
|
110
|
+
raise SystemExit
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
class ExitedNoStatusJob < StubNoStatusJob
|
115
|
+
def perform
|
116
|
+
raise SystemExit
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
class InterruptedJob < StubJob
|
121
|
+
def perform
|
122
|
+
raise Interrupt
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
class InterruptedNoStatusJob < StubNoStatusJob
|
127
|
+
def perform
|
128
|
+
raise Interrupt
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
57
132
|
class RetriedJob < StubJob
|
58
|
-
|
59
|
-
|
133
|
+
|
134
|
+
sidekiq_options retry: true
|
135
|
+
sidekiq_retry_in do |count| 3 end # 3 second delay > job timeout in test suite
|
136
|
+
|
137
|
+
def perform
|
60
138
|
Sidekiq.redis do |conn|
|
61
139
|
key = "RetriedJob_#{jid}"
|
62
|
-
|
63
|
-
unless conn.exists key
|
140
|
+
if [0, false].include? conn.exists(key)
|
64
141
|
conn.set key, 'tried'
|
65
142
|
raise StandardError
|
66
143
|
end
|
Binary file
|
Binary file
|
@@ -0,0 +1,118 @@
|
|
1
|
+
<% require 'cgi'; def h(v); CGI.escape_html(v.to_s); end %>
|
2
|
+
<style>
|
3
|
+
.progress {
|
4
|
+
background-color: #C8E1ED;
|
5
|
+
}
|
6
|
+
.progress-percentage {
|
7
|
+
margin: 5px;
|
8
|
+
padding-left: 4px;
|
9
|
+
color: #333;
|
10
|
+
text-align: left;
|
11
|
+
text-shadow: 0 0 5px white;
|
12
|
+
font-weight: bold;
|
13
|
+
font-size: 14px;
|
14
|
+
}
|
15
|
+
</style>
|
16
|
+
|
17
|
+
<h3>
|
18
|
+
Job Status: <%= h @status["jid"] %>
|
19
|
+
<span class='label label-<%= h @status["label"] %>'>
|
20
|
+
<%= h @status["status"] %>
|
21
|
+
</span>
|
22
|
+
</h3>
|
23
|
+
|
24
|
+
<div class="progress" style="height: 30px;">
|
25
|
+
<div class="progress-bar" role="progressbar" aria-valuenow="<%= @status["pct_complete"].to_i %>" aria-valuemin="0" aria-valuemax="100" style="width: <%= @status["pct_complete"].to_i %>%">
|
26
|
+
<div class="progress-percentage">
|
27
|
+
<%= @status["pct_complete"].to_i %>%
|
28
|
+
</div>
|
29
|
+
</div>
|
30
|
+
</div>
|
31
|
+
|
32
|
+
<div class="panel panel-default" style="background-color: inherit">
|
33
|
+
<div class="panel-body">
|
34
|
+
<h4><%= h @status["worker"] %></h4>
|
35
|
+
|
36
|
+
<div class="row">
|
37
|
+
<div class="col-sm-2">
|
38
|
+
<strong>Arguments</strong>
|
39
|
+
</div>
|
40
|
+
<div class="col-sm-10">
|
41
|
+
<p><%= @status["args"].empty? ? "<i>none</i>" : h(@status["args"]) %></p>
|
42
|
+
</div>
|
43
|
+
</div>
|
44
|
+
|
45
|
+
<div class="row">
|
46
|
+
<div class="col-sm-2">
|
47
|
+
<strong>Message</strong>
|
48
|
+
</div>
|
49
|
+
<div class="col-sm-10">
|
50
|
+
<p><%= h(@status["message"]) || "<i>none</i>" %></p>
|
51
|
+
</div>
|
52
|
+
</div>
|
53
|
+
|
54
|
+
<div class="row">
|
55
|
+
<div class="col-sm-2">
|
56
|
+
<strong>Last Updated</strong>
|
57
|
+
</div>
|
58
|
+
<div class="col-sm-10">
|
59
|
+
<p>
|
60
|
+
<% secs = Time.now.to_i - @status["update_time"].to_i %>
|
61
|
+
<% if secs > 0 %>
|
62
|
+
<%= ChronicDuration.output(secs, :weeks => true, :units => 2) %> ago
|
63
|
+
<% else %>
|
64
|
+
Now
|
65
|
+
<% end %>
|
66
|
+
</p>
|
67
|
+
</div>
|
68
|
+
</div>
|
69
|
+
|
70
|
+
<div class="row">
|
71
|
+
<div class="col-sm-2">
|
72
|
+
<strong>Elapsed Time</strong>
|
73
|
+
</div>
|
74
|
+
<div class="col-sm-10">
|
75
|
+
<p>
|
76
|
+
<% if @status["elapsed"] %>
|
77
|
+
<%= ChronicDuration.output(@status["elapsed"].to_i, :weeks => true, :units => 2) %>
|
78
|
+
<% end %>
|
79
|
+
</p>
|
80
|
+
</div>
|
81
|
+
</div>
|
82
|
+
|
83
|
+
<div class="row">
|
84
|
+
<div class="col-sm-2">
|
85
|
+
<strong>ETA</strong>
|
86
|
+
</div>
|
87
|
+
<div class="col-sm-10">
|
88
|
+
<p>
|
89
|
+
<% if @status["eta"] %>
|
90
|
+
<%= ChronicDuration.output(@status["eta"].to_i, :weeks => true, :units => 2) %>
|
91
|
+
<% end %>
|
92
|
+
</p>
|
93
|
+
</div>
|
94
|
+
</div>
|
95
|
+
|
96
|
+
<% if @status["custom"].any? %>
|
97
|
+
<hr>
|
98
|
+
<% @status["custom"].each do |key, val| %>
|
99
|
+
<div class="row">
|
100
|
+
<div class="col-sm-2">
|
101
|
+
<strong><%= key %></strong>
|
102
|
+
</div>
|
103
|
+
<div class="col-sm-10">
|
104
|
+
<% if val && val.include?("\n") %>
|
105
|
+
<pre><%= h val %></pre>
|
106
|
+
<% else %>
|
107
|
+
<p><%= h(val) || "<i>none</i>" %></p>
|
108
|
+
<% end %>
|
109
|
+
</div>
|
110
|
+
</div>
|
111
|
+
<% end %>
|
112
|
+
<% end %>
|
113
|
+
</div>
|
114
|
+
</div>
|
115
|
+
|
116
|
+
<a href="<%= root_path %>statuses">
|
117
|
+
<small>← back to all statuses</small>
|
118
|
+
</a>
|
data/web/views/statuses.erb
CHANGED
@@ -1,31 +1,149 @@
|
|
1
|
-
|
1
|
+
<% require 'cgi'; def h(v); CGI.escape_html(v.to_s); end %>
|
2
|
+
<style>
|
3
|
+
.progress {
|
4
|
+
background-color: #C8E1ED;
|
5
|
+
}
|
6
|
+
.bar {
|
7
|
+
background-color: #2897cb;
|
8
|
+
color: white;
|
9
|
+
text-shadow: 0 0 0;
|
10
|
+
}
|
11
|
+
.message {
|
12
|
+
text-shadow: 0 0 5px white;
|
13
|
+
font-weight: bold; padding-left: 4px;
|
14
|
+
color: #333;
|
15
|
+
}
|
16
|
+
.actions {
|
17
|
+
text-align: center;
|
18
|
+
}
|
19
|
+
.header {
|
20
|
+
text-align: center;
|
21
|
+
}
|
22
|
+
.header_update_time {
|
23
|
+
width: 10%;
|
24
|
+
}
|
25
|
+
.header_pct_complete {
|
26
|
+
width: 45%;
|
27
|
+
}
|
28
|
+
.btn-warning {
|
29
|
+
background-image: linear-gradient(#f0ad4e, #eea236)
|
30
|
+
}
|
31
|
+
.nav-container {
|
32
|
+
display: flex;
|
33
|
+
line-height: 45px;
|
34
|
+
}
|
35
|
+
.nav-container .pull-right {
|
36
|
+
float: none !important;
|
37
|
+
}
|
38
|
+
.nav-container .pagination {
|
39
|
+
display: flex;
|
40
|
+
align-items: center;
|
41
|
+
}
|
42
|
+
.nav-container .per-page, .filter-status {
|
43
|
+
display: flex;
|
44
|
+
align-items: center;
|
45
|
+
margin: 20px 0 20px 10px;
|
46
|
+
white-space: nowrap;
|
47
|
+
}
|
48
|
+
.nav-container .per-page SELECT {
|
49
|
+
margin: 0 0 0 5px;
|
50
|
+
}
|
51
|
+
</style>
|
52
|
+
<script>
|
53
|
+
function setPerPage(select){
|
54
|
+
window.location = select.options[select.selectedIndex].getAttribute('data-url')
|
55
|
+
}
|
56
|
+
</script>
|
57
|
+
<div style="display: flex; justify-content: space-between;">
|
58
|
+
<h3 class="wi">Recent job statuses</h3>
|
59
|
+
<div class="nav-container">
|
60
|
+
<%= erb :_paging, locals: { url: "#{root_path}statuses" } %>
|
61
|
+
<div class="filter-status">
|
62
|
+
Filter Status:
|
63
|
+
<select class="form-control" onchange="setPerPage(this)">
|
64
|
+
<% (['all', 'complete', 'failed', 'interrupted', 'queued', 'retrying', 'stopped', 'working']).each do |status| %>
|
65
|
+
<option data-url="?<%= qparams(status: status)%>" value="<%= status %>" <%= 'selected="selected"' if status == (params[:status]) %>><%= status %></option>
|
66
|
+
<% end %>
|
67
|
+
</select>
|
68
|
+
</div>
|
2
69
|
|
3
|
-
|
4
|
-
|
70
|
+
<div class="per-page">
|
71
|
+
Per page:
|
72
|
+
<select class="form-control" onchange="setPerPage(this)">
|
73
|
+
<% (Sidekiq::Status::Web.per_page_opts + ['all']).each do |num| %>
|
74
|
+
<option data-url="?<%= qparams(page: 1, per_page: num)%>" value="<%= num %>" <%= 'selected="selected"' if num.to_s == (params[:per_page] || @count) %>><%= num %></option>
|
75
|
+
<% end %>
|
76
|
+
</select>
|
77
|
+
</div>
|
78
|
+
</div>
|
79
|
+
</div>
|
80
|
+
<table class="table table-hover table-bordered table-striped">
|
5
81
|
<tr>
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
82
|
+
<% @headers.each do |hdr| %>
|
83
|
+
<th class="header <%= h hdr[:class] %> header_<%= h hdr[:id] %>">
|
84
|
+
<a href="<%= h hdr[:url] %>"><%= h hdr[:name] %></a>
|
85
|
+
</th>
|
86
|
+
<% end %>
|
87
|
+
<th class="header">
|
88
|
+
Actions
|
89
|
+
</th>
|
11
90
|
</tr>
|
12
91
|
<% @statuses.each do |container| %>
|
13
92
|
<tr>
|
14
93
|
<td>
|
15
|
-
|
16
|
-
|
17
|
-
|
94
|
+
<div title='<%= h container["jid"] %>'><a href="<%= root_path %>statuses/<%= h container["jid"] %>"><%= h container["worker"] %></a></div>
|
95
|
+
</td>
|
96
|
+
<td>
|
97
|
+
<div class='args' title='<%= h container["jid"] %>'><%= h container["args"] %></div>
|
98
|
+
</td>
|
99
|
+
<td style='text-align: center;'>
|
100
|
+
<div class='label label-<%= h container["label"] %>'><%= h container["status"] %></div>
|
101
|
+
</td>
|
102
|
+
<% secs = Time.now.to_i - container["update_time"].to_i %>
|
103
|
+
<td style='text-align: center; white-space: nowrap;' title="<%= Time.at(container["update_time"].to_i) %>">
|
104
|
+
<% if secs > 0 %>
|
105
|
+
<%= ChronicDuration.output(secs, :weeks => true, :units => 2) %> ago
|
106
|
+
<% else %>
|
107
|
+
Now
|
108
|
+
<% end %>
|
18
109
|
</td>
|
19
|
-
<td><%= container.status %></td>
|
20
|
-
<td><%= Time.at(container.update_time.to_i) %></td>
|
21
110
|
<td>
|
22
111
|
<div class="progress progress-striped" style="margin-bottom: 0">
|
23
|
-
<div class=
|
24
|
-
|
25
|
-
<%= container.pct_complete %>%
|
112
|
+
<div class='message' style='text-align:right; padding-right:0.5em; background-color: transparent; float:right;'>
|
113
|
+
<%= h container["message"] %>
|
26
114
|
</div>
|
115
|
+
<% if container["pct_complete"].to_i > 0 %>
|
116
|
+
<div class="bar message" style="width: <%= h container["pct_complete"] %>%;">
|
117
|
+
<%= h container["pct_complete"] %>%
|
118
|
+
</div>
|
119
|
+
<% end %>
|
27
120
|
</div>
|
28
|
-
|
121
|
+
</td>
|
122
|
+
<td style='text-align: center; white-space: nowrap;'>
|
123
|
+
<% if container["elapsed"] %>
|
124
|
+
<%= ChronicDuration.output(container["elapsed"].to_i, :weeks => true, :units => 2) %>
|
125
|
+
<% end %>
|
126
|
+
</td>
|
127
|
+
<td style='text-align: center; white-space: nowrap;'>
|
128
|
+
<% if container["eta"] %>
|
129
|
+
<%= ChronicDuration.output(container["eta"].to_i, :weeks => true, :units => 2) %>
|
130
|
+
<% end %>
|
131
|
+
</td>
|
132
|
+
<td>
|
133
|
+
<div class="actions">
|
134
|
+
<form action="statuses" method="post">
|
135
|
+
<input type="hidden" name="jid" value="<%= h container["jid"] %>" />
|
136
|
+
<%= csrf_tag %>
|
137
|
+
<% if container["status"] == "complete" %>
|
138
|
+
<input type="hidden" name="_method" value="delete" />
|
139
|
+
<input type="submit" class="btn btn-danger btn-xs" value="Remove" />
|
140
|
+
<% elsif container["status"] == "failed" %>
|
141
|
+
<input type="hidden" name="_method" value="put" />
|
142
|
+
<input type="submit" class="btn btn-warning btn-xs" value="Retry Now" />
|
143
|
+
<% end %>
|
144
|
+
</form>
|
145
|
+
</div>
|
146
|
+
</td>
|
29
147
|
</tr>
|
30
148
|
<% end %>
|
31
149
|
<% if @statuses.empty? %>
|