dvdplm-taskr 0.3.1
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/CHANGELOG.txt +1 -0
- data/GPLv3-LICENSE.txt +674 -0
- data/History.txt +50 -0
- data/Manifest.txt +34 -0
- data/README.txt +27 -0
- data/Rakefile +85 -0
- data/bin/taskr +35 -0
- data/bin/taskr-ctl +32 -0
- data/config.example.yml +176 -0
- data/examples/active_resource_client_example.rb +46 -0
- data/examples/php_client_example.php +125 -0
- data/lib/public/prototype.js +3271 -0
- data/lib/public/taskr.css +45 -0
- data/lib/taskr.rb +97 -0
- data/lib/taskr/actions.rb +318 -0
- data/lib/taskr/controllers.rb +338 -0
- data/lib/taskr/environment.rb +48 -0
- data/lib/taskr/helpers.rb +79 -0
- data/lib/taskr/models.rb +370 -0
- data/lib/taskr/version.rb +9 -0
- data/lib/taskr/views.rb +455 -0
- data/setup.rb +1585 -0
- data/taskr4rails/LICENSE.txt +504 -0
- data/taskr4rails/README +31 -0
- data/taskr4rails/Rakefile +22 -0
- data/taskr4rails/init.rb +1 -0
- data/taskr4rails/install.rb +26 -0
- data/taskr4rails/lib/taskr4rails_controller.rb +76 -0
- data/taskr4rails/tasks/taskr4rails_tasks.rake +4 -0
- data/taskr4rails/test/taskr4rails_test.rb +8 -0
- data/taskr4rails/uninstall.rb +1 -0
- data/test.rb +3 -0
- data/test/taskr_test.rb +11 -0
- data/test/test_helper.rb +2 -0
- metadata +140 -0
data/lib/taskr/models.rb
ADDED
@@ -0,0 +1,370 @@
|
|
1
|
+
# This file is part of Taskr.
|
2
|
+
#
|
3
|
+
# Taskr is free software: you can redistribute it and/or modify
|
4
|
+
# it under the terms of the GNU General Public License as published by
|
5
|
+
# the Free Software Foundation, either version 3 of the License, or
|
6
|
+
# (at your option) any later version.
|
7
|
+
#
|
8
|
+
# Taskr is distributed in the hope that it will be useful,
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
11
|
+
# GNU General Public License for more details.
|
12
|
+
#
|
13
|
+
# You should have received a copy of the GNU General Public License
|
14
|
+
# along with Taskr. If not, see <http://www.gnu.org/licenses/>.
|
15
|
+
|
16
|
+
require 'camping/db'
|
17
|
+
require 'openwfe/util/scheduler'
|
18
|
+
require 'date'
|
19
|
+
|
20
|
+
class Rufus::Scheduler
|
21
|
+
public :duration_to_f
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
module Taskr::Models
|
26
|
+
|
27
|
+
class Task < Base
|
28
|
+
has_many :task_actions,
|
29
|
+
:include => :action_parameters,
|
30
|
+
:dependent => :destroy
|
31
|
+
|
32
|
+
serialize :schedule_options
|
33
|
+
serialize :last_triggered_error
|
34
|
+
|
35
|
+
validates_presence_of :schedule_method
|
36
|
+
validates_presence_of :schedule_when
|
37
|
+
validates_presence_of :name
|
38
|
+
validates_uniqueness_of :name
|
39
|
+
validates_presence_of :task_actions
|
40
|
+
validates_associated :task_actions
|
41
|
+
|
42
|
+
def schedule!(scheduler = Taskr.scheduler)
|
43
|
+
case schedule_method
|
44
|
+
when 'cron'
|
45
|
+
method = :schedule
|
46
|
+
when 'at'
|
47
|
+
method = :schedule_at
|
48
|
+
when 'in'
|
49
|
+
method = :schedule_in
|
50
|
+
when 'every'
|
51
|
+
method = :schedule_every
|
52
|
+
end
|
53
|
+
|
54
|
+
if method == :schedule_at || method == :schedule_in
|
55
|
+
t = next_trigger_time
|
56
|
+
method = :schedule_at
|
57
|
+
if t < Time.now
|
58
|
+
$LOG.warn "Task #{name.inspect} will not be scheduled because its trigger time is in the past (#{t.inspect})."
|
59
|
+
return nil
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
$LOG.debug "Scheduling task #{name.inspect}: #{self.inspect}"
|
64
|
+
|
65
|
+
if task_actions(true).length > 0
|
66
|
+
action = prepare_action
|
67
|
+
else
|
68
|
+
$LOG.warn "Task #{name.inspect} has no actions and as a result will not be scheduled!"
|
69
|
+
return false
|
70
|
+
end
|
71
|
+
|
72
|
+
job_id = scheduler.send(method, t || schedule_when, :schedulable => action)
|
73
|
+
|
74
|
+
if job_id
|
75
|
+
$LOG.debug "Task #{name.inspect} scheduled with job id #{job_id}"
|
76
|
+
else
|
77
|
+
$LOG.error "Task #{name.inspect} was NOT scheduled!"
|
78
|
+
return nil
|
79
|
+
end
|
80
|
+
|
81
|
+
self.update_attribute(:scheduler_job_id, job_id)
|
82
|
+
if method == :schedule_at || method == :schedule_in
|
83
|
+
job = scheduler.get_job(job_id)
|
84
|
+
at = job.schedule_info
|
85
|
+
self.update_attribute(:schedule_when, at)
|
86
|
+
self.update_attribute(:schedule_method, 'at')
|
87
|
+
end
|
88
|
+
|
89
|
+
return job_id
|
90
|
+
end
|
91
|
+
|
92
|
+
def prepare_action
|
93
|
+
if task_actions.length == 1
|
94
|
+
ta = task_actions.first
|
95
|
+
|
96
|
+
parameters = {}
|
97
|
+
ta.action_parameters.each{|p| parameters[p.name] = p.value}
|
98
|
+
|
99
|
+
action = (ta.action_class.kind_of?(Class) ? ta.action_class : ta.action_class.constantize).new(parameters)
|
100
|
+
action.task = self
|
101
|
+
action.task_action = ta
|
102
|
+
elsif task_actions.length > 1
|
103
|
+
action = Taskr::Actions::Multi.new
|
104
|
+
task_actions.each do |ta|
|
105
|
+
parameters = {}
|
106
|
+
ta.action_parameters.each{|p| parameters[p.name] = p.value}
|
107
|
+
|
108
|
+
a = (ta.action_class.kind_of?(Class) ? ta.action_class : ta.action_class.constantize).new(parameters)
|
109
|
+
a.task = self
|
110
|
+
a.task_action = ta
|
111
|
+
|
112
|
+
action.actions << a
|
113
|
+
end
|
114
|
+
action.task = self
|
115
|
+
else
|
116
|
+
raise "Task #{name.inspect} has no actions!"
|
117
|
+
end
|
118
|
+
|
119
|
+
action
|
120
|
+
end
|
121
|
+
|
122
|
+
def next_trigger_time
|
123
|
+
# TODO: need to figure out how to calulate trigger_time for these.. for now return :unknown
|
124
|
+
return :unknown unless schedule_method == 'at' || schedule_method == 'in'
|
125
|
+
|
126
|
+
if schedule_method == 'in'
|
127
|
+
return (created_on || Time.now) + Taskr.scheduler.duration_to_f(schedule_when)
|
128
|
+
end
|
129
|
+
|
130
|
+
# Time parsing code from Rails
|
131
|
+
time_hash = Date._parse(schedule_when)
|
132
|
+
time_hash[:sec_fraction] = ((time_hash[:sec_fraction].to_f % 1) * 1_000_000).to_i
|
133
|
+
time_array = time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction)
|
134
|
+
# treat 0000-00-00 00:00:00 as nil
|
135
|
+
Time.send(Base.default_timezone, *time_array) rescue DateTime.new(*time_array[0..5]) rescue nil
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
def to_s
|
140
|
+
"#{name.inspect}@#{schedule_when}"
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
|
145
|
+
class TaskAction < Base
|
146
|
+
belongs_to :task
|
147
|
+
|
148
|
+
has_many :action_parameters,
|
149
|
+
:class_name => 'TaskActionParameter',
|
150
|
+
:foreign_key => :task_action_id,
|
151
|
+
:dependent => :destroy
|
152
|
+
alias_method :parameters, :action_parameters
|
153
|
+
|
154
|
+
has_many :log_entries
|
155
|
+
has_one :last_log_entry,
|
156
|
+
:class_name => 'LogEntry',
|
157
|
+
:foreign_key => :task_id,
|
158
|
+
:order => 'timestamp DESC'
|
159
|
+
|
160
|
+
validates_associated :action_parameters
|
161
|
+
|
162
|
+
def action_class=(class_name)
|
163
|
+
if class_name.kind_of? Class
|
164
|
+
self[:action_class_name] = class_name.to_s
|
165
|
+
else
|
166
|
+
self[:action_class_name] = class_name
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def action_class
|
171
|
+
self[:action_class_name].constantize
|
172
|
+
end
|
173
|
+
|
174
|
+
def description
|
175
|
+
action_class.description
|
176
|
+
end
|
177
|
+
|
178
|
+
def to_xml(options = {})
|
179
|
+
options[:indent] ||= 2
|
180
|
+
xml = options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
|
181
|
+
xml.instruct! unless options[:skip_instruct]
|
182
|
+
xml.tag!('task-action', :type => self.class) do
|
183
|
+
xml.tag!('id', {:type => 'integer'}, id)
|
184
|
+
xml.tag!('action-class-name', action_class_name)
|
185
|
+
xml.tag!('order', {:type => 'integer'}, order) unless order.blank?
|
186
|
+
xml.tag!('task-id', {:type => 'integer'}, task_id)
|
187
|
+
xml.tag!('action-parameters', {:type => 'array'}) do
|
188
|
+
action_parameters.each {|ap| ap.to_xml(options)}
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def to_s
|
194
|
+
"#{self.class.name.demodulize}(#{task_action})"
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
class TaskActionParameter < Base
|
199
|
+
belongs_to :task_action
|
200
|
+
serialize :value
|
201
|
+
|
202
|
+
def to_xml(options = {})
|
203
|
+
options[:indent] ||= 2
|
204
|
+
xml = options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
|
205
|
+
xml.instruct! unless options[:skip_instruct]
|
206
|
+
xml.tag!('action-parameter', :type => self.class) do
|
207
|
+
xml.tag!('id', {:type => 'integer'}, id)
|
208
|
+
xml.tag!('name', name)
|
209
|
+
xml.tag!('value') do
|
210
|
+
xml.cdata!(value)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
def to_s
|
216
|
+
"#{self.class.name.demodulize}(#{name}:#{value})"
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
class LogEntry < Base
|
221
|
+
belongs_to :task
|
222
|
+
belongs_to :task_action
|
223
|
+
|
224
|
+
class << self
|
225
|
+
def log(level, action_or_task, data)
|
226
|
+
level = level.upcase
|
227
|
+
if action_or_task.kind_of? TaskAction
|
228
|
+
action = action_or_task
|
229
|
+
task = action.task
|
230
|
+
elsif action_or_task.kind_of? Task
|
231
|
+
action = nil
|
232
|
+
task = action_or_task
|
233
|
+
elsif action_or_task.kind_of? Integer
|
234
|
+
action = TaskAction.find(action_or_task)
|
235
|
+
task = action.task
|
236
|
+
elsif action_or_task.kind_of? Taskr::Actions::Base
|
237
|
+
action = action_or_task.task_action
|
238
|
+
task = action.task
|
239
|
+
else
|
240
|
+
raise ArgumentError, "#{action_or_task.inspect} is not a valid Task or TaskAction!"
|
241
|
+
end
|
242
|
+
|
243
|
+
threshold = Taskr::Conf[:task_log][:level].upcase if Taskr::Conf[:task_log] && Taskr::Conf[:task_log][:level]
|
244
|
+
|
245
|
+
if threshold.blank? ||
|
246
|
+
['DEBUG', 'INFO', 'WARN', 'ERROR'].index(threshold) <= ['DEBUG', 'INFO', 'WARN', 'ERROR'].index(level)
|
247
|
+
LogEntry.create(
|
248
|
+
:level => level,
|
249
|
+
:timestamp => Time.now,
|
250
|
+
:task => task,
|
251
|
+
:task_action => action,
|
252
|
+
:data => data
|
253
|
+
)
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
# Produces a Logger-like class that will create log entries for the given
|
258
|
+
# TaskAction. The returned object exploses behaviour much like a standard
|
259
|
+
# Ruby Logger, so that it can be used in place of a Logger when necessary.
|
260
|
+
def logger_for_action(action)
|
261
|
+
ActionLogger.new(action)
|
262
|
+
end
|
263
|
+
|
264
|
+
['debug', 'info', 'warn', 'error'].each do |level|
|
265
|
+
define_method(level) do |action, data|
|
266
|
+
log(level, action, data)
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
# Exposes a Logger-like interface for logging entries for some particular
|
272
|
+
# TaskAction.
|
273
|
+
class ActionLogger
|
274
|
+
def initialize(action)
|
275
|
+
@action = action
|
276
|
+
end
|
277
|
+
|
278
|
+
def method_missing(method, data)
|
279
|
+
LogEntry.send(method, @action, "#{"#{@progname}: " unless @progname.blank?}#{data}")
|
280
|
+
end
|
281
|
+
|
282
|
+
def respond_to?(method)
|
283
|
+
[:debug, :info, :warn, :error].include?(method)
|
284
|
+
end
|
285
|
+
|
286
|
+
def progname
|
287
|
+
action.task.name
|
288
|
+
end
|
289
|
+
|
290
|
+
def progname=(p)
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
class CreateTaskr < V 0.01
|
296
|
+
def self.up
|
297
|
+
$LOG.info("Migrating database")
|
298
|
+
|
299
|
+
create_table :taskr_tasks, :force => true do |t|
|
300
|
+
t.column :name, :string, :null => false
|
301
|
+
t.column :created_on, :timestamp, :null => false
|
302
|
+
t.column :created_by, :string
|
303
|
+
|
304
|
+
t.column :schedule_method, :string, :null => false
|
305
|
+
t.column :schedule_when, :string, :null => false
|
306
|
+
t.column :schedule_options, :text
|
307
|
+
|
308
|
+
t.column :scheduler_job_id, :integer
|
309
|
+
t.column :last_triggered, :datetime
|
310
|
+
t.column :last_triggered_error, :text
|
311
|
+
end
|
312
|
+
|
313
|
+
add_index :taskr_tasks, [:name], :unique => true
|
314
|
+
|
315
|
+
create_table :taskr_task_actions, :force => true do |t|
|
316
|
+
t.column :task_id, :integer, :null => false
|
317
|
+
t.column :action_class_name, :string, :null => false
|
318
|
+
t.column :order, :integer
|
319
|
+
end
|
320
|
+
|
321
|
+
add_index :taskr_task_actions, [:task_id]
|
322
|
+
|
323
|
+
create_table :taskr_task_action_parameters, :force => true do |t|
|
324
|
+
t.column :task_action_id, :integer, :null => false
|
325
|
+
t.column :name, :string, :null => false
|
326
|
+
t.column :value, :text
|
327
|
+
end
|
328
|
+
|
329
|
+
add_index :taskr_task_action_parameters, [:task_action_id]
|
330
|
+
add_index :taskr_task_action_parameters, [:task_action_id, :name]
|
331
|
+
end
|
332
|
+
|
333
|
+
def self.down
|
334
|
+
drop_table :taskr_task_action_parameters
|
335
|
+
drop_table :taskr_tasks
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
class AddLoggingTables < V 0.3
|
340
|
+
def self.up
|
341
|
+
$LOG.info("Creating logging tables")
|
342
|
+
|
343
|
+
create_table :taskr_log_entries, :force => true do |t|
|
344
|
+
t.column :task_id, :integer
|
345
|
+
t.column :task_action_id, :integer
|
346
|
+
|
347
|
+
t.column :timestamp, :timestamp, :null => false
|
348
|
+
t.column :level, :string, :null => false
|
349
|
+
t.column :data, :text
|
350
|
+
end
|
351
|
+
|
352
|
+
add_index :taskr_log_entries, :task_id
|
353
|
+
add_index :taskr_log_entries, :task_action_id
|
354
|
+
end
|
355
|
+
|
356
|
+
def self.down
|
357
|
+
drop_table :taskr_log_entries
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
class AddMemoToTasks < V 0.3001
|
362
|
+
def self.up
|
363
|
+
add_column :taskr_tasks, :memo, :text
|
364
|
+
end
|
365
|
+
|
366
|
+
def self.down
|
367
|
+
remove_column :taskr_tasks, :memo
|
368
|
+
end
|
369
|
+
end
|
370
|
+
end
|
data/lib/taskr/views.rb
ADDED
@@ -0,0 +1,455 @@
|
|
1
|
+
# This file is part of Taskr.
|
2
|
+
#
|
3
|
+
# Taskr is free software: you can redistribute it and/or modify
|
4
|
+
# it under the terms of the GNU General Public License as published by
|
5
|
+
# the Free Software Foundation, either version 3 of the License, or
|
6
|
+
# (at your option) any later version.
|
7
|
+
#
|
8
|
+
# Taskr is distributed in the hope that it will be useful,
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
11
|
+
# GNU General Public License for more details.
|
12
|
+
#
|
13
|
+
# You should have received a copy of the GNU General Public License
|
14
|
+
# along with Taskr. If not, see <http://www.gnu.org/licenses/>.
|
15
|
+
|
16
|
+
# need auto_validation off to render non-XHTML XML
|
17
|
+
Markaby::Builder.set(:auto_validation, false)
|
18
|
+
Markaby::Builder.set(:indent, 2)
|
19
|
+
|
20
|
+
module Taskr::Views
|
21
|
+
module XML
|
22
|
+
include Taskr::Models
|
23
|
+
|
24
|
+
CONTENT_TYPE = 'text/xml'
|
25
|
+
|
26
|
+
def tasks_list
|
27
|
+
@tasks.to_xml(:root => 'tasks')#, :include => [:task_actions])
|
28
|
+
end
|
29
|
+
|
30
|
+
def view_task
|
31
|
+
@task.to_xml(:root => 'task')#, :include => [:task_actions])
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
module HTML
|
36
|
+
include Taskr::Controllers
|
37
|
+
|
38
|
+
CONTENT_TYPE = 'text/html'
|
39
|
+
|
40
|
+
def tasks_list
|
41
|
+
html_scaffold do
|
42
|
+
h1 {"Tasks"}
|
43
|
+
|
44
|
+
p{a(:href => R(Taskr::Controllers::Tasks, 'new')) {"Schedule New Task"}}
|
45
|
+
|
46
|
+
table do
|
47
|
+
thead do
|
48
|
+
tr do
|
49
|
+
th "Name"
|
50
|
+
th "Schedule"
|
51
|
+
th "Last Triggered"
|
52
|
+
th "Job ID"
|
53
|
+
th "Created On"
|
54
|
+
th "Created By"
|
55
|
+
th ""
|
56
|
+
end
|
57
|
+
end
|
58
|
+
tbody do
|
59
|
+
@tasks.each do |t|
|
60
|
+
tr_css = []
|
61
|
+
tr_css << "error" if t.last_triggered_error
|
62
|
+
tr_css << "expired" if t.next_trigger_time != :unknown && t.next_trigger_time < Time.now
|
63
|
+
|
64
|
+
tr(:class => tr_css.join(" ")) do
|
65
|
+
td {a(:href => R(t)) {strong{t.name}}}
|
66
|
+
td "#{t.schedule_method} #{t.schedule_when}"
|
67
|
+
td do
|
68
|
+
if t.last_triggered
|
69
|
+
"#{distance_of_time_in_words(t.last_triggered, Time.now, true)} ago"
|
70
|
+
else
|
71
|
+
em "Not yet triggered"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
td(:class => "job-id") {t.scheduler_job_id}
|
75
|
+
td t.created_on
|
76
|
+
td t.created_by
|
77
|
+
td {a(:href => R(t, 'edit')) {"Edit"}}
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
br
|
84
|
+
div {scheduler_status}
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def edit_task
|
89
|
+
html_scaffold do
|
90
|
+
script(:type => 'text/javascript') do
|
91
|
+
%{
|
92
|
+
function show_action_parameters(num) {
|
93
|
+
new Ajax.Updater('parameters_'+num, '#{R(Actions)}', {
|
94
|
+
method: 'get',
|
95
|
+
parameters: {
|
96
|
+
id: $F('action_class_name_'+num),
|
97
|
+
action: 'parameters_form',
|
98
|
+
num: num
|
99
|
+
}
|
100
|
+
});
|
101
|
+
}
|
102
|
+
}
|
103
|
+
end
|
104
|
+
|
105
|
+
a(:href => R(Tasks, :list)) {"Back to Task List"}
|
106
|
+
|
107
|
+
form :method => 'put', :action => R(@task, 'update') do
|
108
|
+
h1 "Edit Task \"#{@task.name}\""
|
109
|
+
|
110
|
+
p do
|
111
|
+
label 'name'
|
112
|
+
br
|
113
|
+
input :type => 'text', :name => 'name', :value => @task.name, :size => 40
|
114
|
+
end
|
115
|
+
|
116
|
+
p do
|
117
|
+
label 'schedule'
|
118
|
+
br
|
119
|
+
select(:name => 'schedule_method') do
|
120
|
+
['every','at','in','cron'].each do |method|
|
121
|
+
if @task.schedule_method == method
|
122
|
+
option(:value => method, :selected => 'selected') {method}
|
123
|
+
else
|
124
|
+
option(:value => method) {method}
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
input :type => 'text', :name => 'schedule_when', :value => @task.schedule_when, :size => 30
|
129
|
+
end
|
130
|
+
|
131
|
+
p do
|
132
|
+
label 'description/memo'
|
133
|
+
br
|
134
|
+
textarea(:name => 'memo', :cols => '60', :rows => '4'){@task.memo}
|
135
|
+
end
|
136
|
+
|
137
|
+
p do
|
138
|
+
label "Actions:"
|
139
|
+
@task.task_actions.each do |action|
|
140
|
+
div {action_parameters_form(action)}
|
141
|
+
end
|
142
|
+
# div do
|
143
|
+
# if @task.task_actions.length > 1
|
144
|
+
# ol(:style => 'padding-left: 20px') do
|
145
|
+
# @task.task_actions.each do |ta|
|
146
|
+
# html_task_action_li(ta)
|
147
|
+
# end
|
148
|
+
# end
|
149
|
+
# else
|
150
|
+
# html_task_action_li(@task.task_actions.first)
|
151
|
+
# end
|
152
|
+
# end
|
153
|
+
end
|
154
|
+
|
155
|
+
|
156
|
+
p do
|
157
|
+
a(:id => 'add_action', :href => '#'){'Add another action'}
|
158
|
+
end
|
159
|
+
|
160
|
+
script(:type => 'text/javascript') do
|
161
|
+
%{
|
162
|
+
Event.observe('add_action', 'click', function() {
|
163
|
+
new Ajax.Updater('add_action', '#{R(Actions, :new)}', {
|
164
|
+
method: 'get',
|
165
|
+
parameters: { num: $$('select.action_class_name').size() },
|
166
|
+
insertion: Insertion.Before
|
167
|
+
});
|
168
|
+
return false;
|
169
|
+
})
|
170
|
+
}
|
171
|
+
end
|
172
|
+
|
173
|
+
button(:type => 'submit') {"Save"}
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def new_task
|
179
|
+
html_scaffold do
|
180
|
+
script(:type => 'text/javascript') do
|
181
|
+
%{
|
182
|
+
function show_action_parameters(num) {
|
183
|
+
new Ajax.Updater('parameters_'+num, '#{R(Actions)}', {
|
184
|
+
method: 'get',
|
185
|
+
parameters: {
|
186
|
+
id: $F('action_class_name_'+num),
|
187
|
+
action: 'parameters_form',
|
188
|
+
num: num
|
189
|
+
}
|
190
|
+
});
|
191
|
+
}
|
192
|
+
}
|
193
|
+
end
|
194
|
+
|
195
|
+
form :method => 'post', :action => self/"/tasks?format=#{@format}" do
|
196
|
+
h1 "New Task"
|
197
|
+
input :type => 'hidden', :name => '_method', :value => 'post'
|
198
|
+
|
199
|
+
p do
|
200
|
+
label 'name'
|
201
|
+
br
|
202
|
+
input :type => 'text', :name => 'name', :size => 40
|
203
|
+
end
|
204
|
+
|
205
|
+
p do
|
206
|
+
label 'schedule'
|
207
|
+
br
|
208
|
+
select(:name => 'schedule_method') do
|
209
|
+
['every','at','in','cron'].each do |method|
|
210
|
+
option(:value => method) {method}
|
211
|
+
end
|
212
|
+
end
|
213
|
+
input :type => 'text', :name => 'schedule_when', :size => 30
|
214
|
+
end
|
215
|
+
|
216
|
+
p do
|
217
|
+
label 'description/memo'
|
218
|
+
br
|
219
|
+
textarea(:name => 'memo', :cols => '60', :rows => '4'){""}
|
220
|
+
end
|
221
|
+
|
222
|
+
action_form
|
223
|
+
|
224
|
+
p do
|
225
|
+
a(:id => 'add_action', :href => '#'){'Add another action'}
|
226
|
+
end
|
227
|
+
script(:type => 'text/javascript') do
|
228
|
+
%{
|
229
|
+
Event.observe('add_action', 'click', function() {
|
230
|
+
new Ajax.Updater('add_action', '#{R(Actions, :new)}', {
|
231
|
+
method: 'get',
|
232
|
+
parameters: { num: $$('select.action_class_name').size() },
|
233
|
+
insertion: Insertion.Before
|
234
|
+
});
|
235
|
+
return false;
|
236
|
+
})
|
237
|
+
}
|
238
|
+
end
|
239
|
+
|
240
|
+
button(:type => 'submit') {"submit"}
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
def view_task
|
246
|
+
html_scaffold do
|
247
|
+
form(:method => 'delete', :style => 'display: inline', :action => R(@task)) do
|
248
|
+
button(:type => 'submit', :value => 'delete', :onclick => 'return confirm("Are you sure you want to unschedule and delete this task?")') {"Delete"}
|
249
|
+
end
|
250
|
+
form(:method => 'put', :style => 'display: inline', :action => R(@task, 'run')) do
|
251
|
+
button(:type => 'submit', :value => 'run') {"Run Now!"}
|
252
|
+
end
|
253
|
+
form(:method => 'put', :style => 'display: inline', :action => R(@task, 'reload')) do
|
254
|
+
button(:type => 'submit', :value => 'reload') {"Reload!"}
|
255
|
+
end
|
256
|
+
br
|
257
|
+
a(:href => R(Tasks, :list)) {"Back to Task List"}
|
258
|
+
|
259
|
+
h1 "Task #{@task.id}"
|
260
|
+
table do
|
261
|
+
tr do
|
262
|
+
th "Name:"
|
263
|
+
td @task.name
|
264
|
+
end
|
265
|
+
tr do
|
266
|
+
th "Schedule:"
|
267
|
+
td "#{@task.schedule_method} #{@task.schedule_when}"
|
268
|
+
end
|
269
|
+
tr do
|
270
|
+
th "Description/Memo:"
|
271
|
+
td "#{@task.memo}"
|
272
|
+
end
|
273
|
+
tr do
|
274
|
+
th "Job ID:"
|
275
|
+
td @task.scheduler_job_id
|
276
|
+
end
|
277
|
+
tr do
|
278
|
+
th "Triggered:"
|
279
|
+
td do
|
280
|
+
if @task.last_triggered
|
281
|
+
span "#{distance_of_time_in_words(@task.last_triggered, Time.now, true)} ago"
|
282
|
+
span(:style => 'font-size: 8pt; color: #bbb'){"(#{@task.last_triggered})"}
|
283
|
+
else
|
284
|
+
em "Not yet triggered"
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
if @task.last_triggered_error
|
289
|
+
th "Error:"
|
290
|
+
td(:style => 'color: #e00;') do
|
291
|
+
strong "#{@task.last_triggered_error[:type]}"
|
292
|
+
br
|
293
|
+
pre @task.last_triggered_error[:message]
|
294
|
+
end
|
295
|
+
end
|
296
|
+
tr do
|
297
|
+
th "Actions:"
|
298
|
+
td do
|
299
|
+
if @task.task_actions.length > 1
|
300
|
+
ol(:style => 'padding-left: 20px') do
|
301
|
+
@task.task_actions.each do |ta|
|
302
|
+
html_task_action_li(ta)
|
303
|
+
end
|
304
|
+
end
|
305
|
+
else
|
306
|
+
html_task_action_li(@task.task_actions.first)
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|
310
|
+
tr do
|
311
|
+
th "Created By:"
|
312
|
+
td @task.created_by
|
313
|
+
end
|
314
|
+
tr do
|
315
|
+
th "Created On:"
|
316
|
+
td @task.created_on
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
script %{
|
321
|
+
function clickbold(el) {
|
322
|
+
$$('#logfilter a').each(function(a){a.style.fontWeight = 'normal'})
|
323
|
+
el.style.fontWeight = 'bold'
|
324
|
+
}
|
325
|
+
}
|
326
|
+
|
327
|
+
p(:style => "margin-top: 20px; border-top: 1px dotted black; padding-top: 10px", :id => 'logfilter') do
|
328
|
+
strong "Show: "
|
329
|
+
a(:href => R(LogEntries, :list, :task_id => @task.id, :since => (Time.now - 1.day).to_formatted_s(:db)),
|
330
|
+
:target => 'log', :onclick => "clickbold(this)", :style => 'font-weight: bold') {"Last 24 Hours"}
|
331
|
+
text "|"
|
332
|
+
a(:href => R(LogEntries, :list, :task_id => @task.id, :since => (Time.now - 2.days).to_formatted_s(:db)),
|
333
|
+
:target => 'log', :onclick => "clickbold(this)") {"48 Hours"}
|
334
|
+
text "|"
|
335
|
+
a(:href => R(LogEntries, :list, :task_id => @task.id),
|
336
|
+
:target => 'log', :onclick => "clickbold(this)") {"All"}
|
337
|
+
br
|
338
|
+
|
339
|
+
strong "Level: "
|
340
|
+
a(:href => R(LogEntries, :list, :task_id => @task.id, :level => 'DEBUG'),
|
341
|
+
:target => 'log', :onclick => "clickbold(this)") {"DEBUG"}
|
342
|
+
text "|"
|
343
|
+
a(:href => R(LogEntries, :list, :task_id => @task.id, :level => 'INFO'),
|
344
|
+
:target => 'log', :onclick => "clickbold(this)") {"INFO"}
|
345
|
+
text "|"
|
346
|
+
a(:href => R(LogEntries, :list, :task_id => @task.id, :level => 'WARN'),
|
347
|
+
:target => 'log', :onclick => "clickbold(this)") {"WARN"}
|
348
|
+
text "|"
|
349
|
+
a(:href => R(LogEntries, :list, :task_id => @task.id, :level => 'ERROR'),
|
350
|
+
:target => 'log', :onclick => "clickbold(this)") {"ERROR"}
|
351
|
+
end
|
352
|
+
iframe(:src => R(LogEntries, :list, :task_id => @task.id, :since => (Time.now - 1.day).to_formatted_s(:db)),
|
353
|
+
:style => 'width: 100%; height: 300px', :name => 'log')
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
def scheduler_status
|
358
|
+
s = Taskr.scheduler
|
359
|
+
h3(:style => "margin-bottom: 8px;") {"Scheduler Status"}
|
360
|
+
strong "Running?"
|
361
|
+
span(:style => 'margin-right: 10px') {s.instance_variable_get(:@stopped) ? "NO" : "Yes"}
|
362
|
+
strong "Precision:"
|
363
|
+
span(:style => 'margin-right: 10px') {"#{s.instance_variable_get(:@precision)}s"}
|
364
|
+
strong "Pending Jobs:"
|
365
|
+
span(:style => 'margin-right: 10px') {s.instance_variable_get(:@pending_jobs).size}
|
366
|
+
strong "Thread Status:"
|
367
|
+
span(:style => 'margin-right: 10px') {s.instance_variable_get(:@scheduler_thread).status}
|
368
|
+
end
|
369
|
+
|
370
|
+
def action_list
|
371
|
+
h1 "Actions"
|
372
|
+
ul do
|
373
|
+
@actions.each do |a|
|
374
|
+
li a
|
375
|
+
end
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
def action_parameters_form(action = @action)
|
380
|
+
@num ||= 0
|
381
|
+
|
382
|
+
p {em action.description}
|
383
|
+
|
384
|
+
action.parameters.each do |param|
|
385
|
+
p do
|
386
|
+
label param
|
387
|
+
br
|
388
|
+
unless action.is_a?(Taskr::Models::TaskAction) # Setting up a new task
|
389
|
+
input :type => 'text', :name => "action[#{@num}][#{param}]", :size => 50
|
390
|
+
else # Editing an existing task
|
391
|
+
input :type => 'text', :name => "action[action_id_#{action.id}][#{param.name}]", :size => 50, :value => param.value
|
392
|
+
end
|
393
|
+
end
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
def action_form
|
398
|
+
@num ||= 0
|
399
|
+
|
400
|
+
p do
|
401
|
+
label 'action_class_name'
|
402
|
+
br
|
403
|
+
select(:name => "action[#{@num}][action_class_name]",
|
404
|
+
:id => "action_class_name_#{@num}",
|
405
|
+
:class => "action_class_name",
|
406
|
+
:onchange => "show_action_parameters(#{@num})") do
|
407
|
+
option(:value => "")
|
408
|
+
@actions.each do |a|
|
409
|
+
a.to_s =~ /Taskr::Actions::([^:]*?)$/
|
410
|
+
option(:value => $~[1]) {$~[1]}
|
411
|
+
end
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
415
|
+
div(:id => "parameters_#{@num}")
|
416
|
+
|
417
|
+
end
|
418
|
+
|
419
|
+
def log_entries_list
|
420
|
+
h2 "Log"
|
421
|
+
|
422
|
+
p(:style => 'margin: 0px') do
|
423
|
+
em(:style => "font-weight: normal; font-size: 9pt"){"Entries since #{@since}<br />"} unless @since.blank?
|
424
|
+
em(:style => "font-weight: normal; font-size: 9pt"){"With levels #{@level.join(", ")}<br />"} unless @level.blank?
|
425
|
+
end
|
426
|
+
|
427
|
+
table do
|
428
|
+
@log_entries.each do |entry|
|
429
|
+
case entry.level.downcase.intern
|
430
|
+
when :error
|
431
|
+
bg_color = '#faa'
|
432
|
+
when :warn
|
433
|
+
bg_color = '#ffa'
|
434
|
+
when :info
|
435
|
+
bg_color = '#aaf'
|
436
|
+
when :debug
|
437
|
+
bg_color = '#eee'
|
438
|
+
else
|
439
|
+
bg_color = '#fff; '+entry.level.inspect
|
440
|
+
end
|
441
|
+
tr do
|
442
|
+
td(:style => "vertical-align: top; font-size: 9pt; white-space: nowrap; background: #{bg_color}") do
|
443
|
+
entry.timestamp
|
444
|
+
end
|
445
|
+
td(:style => "vertical-align: top; font-size: 9pt; background-color: #{bg_color}; font-size: 9pt; font-family: monospace") do
|
446
|
+
entry.data.gsub(/<\/?(html|body)>/, '').gsub(/\n/, "<br />")
|
447
|
+
end
|
448
|
+
end
|
449
|
+
end
|
450
|
+
end
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
default_format :HTML
|
455
|
+
end
|