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.
@@ -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
@@ -0,0 +1,9 @@
1
+ module Taskr #:nodoc:
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 3
5
+ TINY = 1
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
@@ -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