374 lines
10 KiB
CoffeeScript
374 lines
10 KiB
CoffeeScript
define [
|
|
'i18n!jobs'
|
|
'jquery'
|
|
'vendor/slickgrid'
|
|
'jquery.ajaxJSON'
|
|
'jqueryui/dialog'
|
|
], (I18n, $, Slick) ->
|
|
###
|
|
xsslint safeString.identifier klass d out_of runtime_string
|
|
###
|
|
|
|
fillin_job_data = (job) ->
|
|
$('#show-job .show-field').each (idx, field) =>
|
|
field_name = field.id.replace("job-", '')
|
|
$(field).text(job[field_name] || '')
|
|
$('#job-id-link').attr('href', "/jobs?flavor=id&q=#{job.id}")
|
|
|
|
selected_job = null
|
|
|
|
class FlavorGrid
|
|
constructor: (@options, @type_name, @grid_name) ->
|
|
@data = []
|
|
@$element = $(@grid_name)
|
|
setTimeout @refresh, 0
|
|
@setTimer() if @options.refresh_rate
|
|
@query = ''
|
|
|
|
setTimer: () =>
|
|
setTimeout (=> @refresh(@setTimer)), @options.refresh_rate
|
|
|
|
saveSelection: =>
|
|
if @type_name == 'running'
|
|
@oldSelected = {}
|
|
for row in @grid.getSelectedRows()
|
|
@oldSelected[@data[row]['id']] = true
|
|
|
|
restoreSelection: =>
|
|
if @type_name == 'running'
|
|
index = 0
|
|
newSelected = []
|
|
for item in @data
|
|
newSelected.push index if @oldSelected[item['id']]
|
|
index += 1
|
|
@restoringSelection = true
|
|
@grid.setSelectedRows(newSelected)
|
|
@restoringSelection = false
|
|
|
|
refresh: (cb) =>
|
|
@$element.queue () =>
|
|
$.ajaxJSON @options.url, "GET", { flavor: @options.flavor, q: @query }, (data) =>
|
|
@saveSelection()
|
|
@data.length = 0
|
|
@loading = {}
|
|
@data.push item for item in data[@type_name]
|
|
if data.total && data.total > @data.length
|
|
@data.push({}) for i in [@data.length ... data.total]
|
|
|
|
if (@sortData)
|
|
@sort(null, @sortData)
|
|
else
|
|
@grid.invalidate()
|
|
@restoreSelection()
|
|
|
|
cb?()
|
|
@updated?()
|
|
@$element.dequeue()
|
|
|
|
change_flavor: (flavor) =>
|
|
@options.flavor = flavor
|
|
@grid.setSelectedRows []
|
|
@refresh()
|
|
|
|
grid_options: () ->
|
|
{ rowHeight: 20 }
|
|
|
|
init: () ->
|
|
@columns = @build_columns()
|
|
@loading = {}
|
|
@grid = new Slick.Grid(@grid_name, @data, @columns, @grid_options())
|
|
this
|
|
|
|
class window.Jobs extends FlavorGrid
|
|
constructor: (options, type_name = 'jobs', grid_name = '#jobs-grid') ->
|
|
Jobs.max_attempts = options.max_attempts if options.max_attempts
|
|
super(options, type_name, grid_name)
|
|
if options.starting_query
|
|
@query = options.starting_query
|
|
@show_search($('#jobs-flavor').val())
|
|
|
|
search: (query) ->
|
|
@query = query
|
|
@refresh()
|
|
|
|
show_search: (flavor) =>
|
|
switch flavor
|
|
when "id", "strand", "tag"
|
|
$('#jobs-search').show()
|
|
$('#jobs-search').attr('placeholder', flavor)
|
|
else
|
|
$('#jobs-search').hide()
|
|
|
|
change_flavor: (flavor) =>
|
|
@show_search(flavor)
|
|
super(flavor)
|
|
|
|
attempts_formatter: (r,c,d) =>
|
|
return '' unless @data[r].id
|
|
max = (@data[r].max_attempts || Jobs.max_attempts)
|
|
if d == 0
|
|
klass = ''
|
|
else if d < max
|
|
klass = 'has-failed-attempts'
|
|
else if d == @options.on_hold_attempt_count
|
|
klass = 'on-hold'
|
|
d = 'hold'
|
|
else
|
|
klass = 'has-failed-max-attempts'
|
|
out_of = if d == 'hold' then '' else "/ #{max}"
|
|
"<span class='#{klass}'>#{d}#{out_of}</span>"
|
|
|
|
load: (row) =>
|
|
@$element.queue () =>
|
|
row = row - (row % @options.limit)
|
|
if @loading[row]
|
|
@$element.dequeue()
|
|
return
|
|
@loading[row] = true
|
|
$.ajaxJSON @options.url, "GET", { flavor: @options.flavor, q: @query, offset: row }, (data) =>
|
|
@data[row ... row + data[@type_name].length] = data[@type_name]
|
|
@grid.invalidate()
|
|
@$element.dequeue()
|
|
|
|
id_formatter: (r,c,d) =>
|
|
if @data[r].id
|
|
@data[r].id
|
|
else
|
|
@load(r)
|
|
"<span class='unloaded-id'>-</span>"
|
|
|
|
build_columns: () ->
|
|
[
|
|
id: 'id'
|
|
name: I18n.t('columns.id', 'id')
|
|
field: 'id'
|
|
width: 100
|
|
formatter: @id_formatter
|
|
,
|
|
id: 'tag'
|
|
name: I18n.t('columns.tag', 'tag')
|
|
field: 'tag'
|
|
width: 200
|
|
,
|
|
id: 'attempts'
|
|
name: I18n.t('columns.attempt', 'attempt')
|
|
field: 'attempts'
|
|
width: 65
|
|
formatter: @attempts_formatter
|
|
,
|
|
id: 'priority'
|
|
name: I18n.t('columns.priority', 'priority')
|
|
field: 'priority'
|
|
width: 60
|
|
,
|
|
id: 'strand'
|
|
name: I18n.t('columns.strand', 'strand')
|
|
field: 'strand'
|
|
width: 100
|
|
,
|
|
id: 'run_at'
|
|
name: I18n.t('columns.run_at', 'run at')
|
|
field: 'run_at'
|
|
width: 165
|
|
]
|
|
|
|
init: () ->
|
|
super()
|
|
@grid.setSelectionModel(new Slick.RowSelectionModel())
|
|
@grid.onSelectedRowsChanged.subscribe =>
|
|
return if @restoringSelection
|
|
rows = @grid.getSelectedRows()
|
|
row = if rows?.length == 1 then rows[0] else -1
|
|
selected_job = @data[rows[0]] || {}
|
|
fillin_job_data(selected_job)
|
|
this
|
|
|
|
selectAll: () ->
|
|
@grid.setSelectedRows([0...@data.length])
|
|
@grid.onSelectedRowsChanged.notify()
|
|
|
|
onSelected: (action) ->
|
|
params =
|
|
flavor: @options.flavor
|
|
q: @query
|
|
update_action: action
|
|
|
|
if @grid.getSelectedRows().length < 1
|
|
alert('No jobs are selected')
|
|
return
|
|
|
|
all_jobs = @grid.getSelectedRows().length > 1 && @grid.getSelectedRows().length == @data.length
|
|
|
|
if all_jobs
|
|
message = switch action
|
|
when 'hold' then I18n.t 'confirm.hold_all', "Are you sure you want to hold *all* jobs of this type and matching this query?"
|
|
when 'unhold' then I18n.t 'confirm.unhold_all', "Are you sure you want to unhold *all* jobs of this type and matching this query?"
|
|
when 'destroy' then I18n.t 'confirm.destroy_all', "Are you sure you want to destroy *all* jobs of this type and matching this query?"
|
|
return unless confirm(message)
|
|
|
|
# special case -- if they've selected all, then don't send the ids so that
|
|
# we can operate on jobs that match the query but haven't even been loaded
|
|
# yet
|
|
unless all_jobs
|
|
params.job_ids = (@data[row].id for row in @grid.getSelectedRows())
|
|
|
|
$.ajaxJSON @options.batch_update_url, "POST", params, @refresh
|
|
@grid.setSelectedRows []
|
|
|
|
updated: () ->
|
|
$('#jobs-total').text @data.length
|
|
if @data.length == 1 && @type_name == 'jobs'
|
|
@grid.setSelectedRows [0]
|
|
@grid.onSelectedRowsChanged.notify()
|
|
|
|
getFullJobDetails: (cb) ->
|
|
if !selected_job || selected_job.handler
|
|
cb()
|
|
else
|
|
$.ajaxJSON "#{@options.job_url}/#{selected_job.id}", "GET", {flavor: @options.flavor}, (data) =>
|
|
selected_job.handler = data.handler
|
|
selected_job.last_error = data.last_error
|
|
fillin_job_data(selected_job)
|
|
cb()
|
|
|
|
class Workers extends Jobs
|
|
constructor: (options) ->
|
|
super(options, 'running', '#running-grid')
|
|
|
|
runtime_formatter: (r,c,d) =>
|
|
runtime = (new Date() - Date.parse(d)) / 1000
|
|
if runtime >= @options.super_slow_threshold
|
|
klass = 'super-slow'
|
|
else if runtime > @options.slow_threshold
|
|
klass = 'slow'
|
|
else
|
|
klass = ''
|
|
format = 'HH:mm:ss'
|
|
format = 'd\\dHH:mm:ss' if runtime > 86400
|
|
runtime_string = new Date(null, null, null, null, null, runtime).toString(format)
|
|
runtime_string = 'FOREVA' if runtime > 86400 * 28
|
|
"<span class='#{klass}'>#{runtime_string}</span>"
|
|
|
|
build_columns: () ->
|
|
cols = [
|
|
id: 'worker'
|
|
name: I18n.t('columns.worker', 'worker')
|
|
field: 'locked_by'
|
|
width: 90
|
|
].concat(super())
|
|
cols.pop()
|
|
cols.push(
|
|
id: 'runtime',
|
|
name: I18n.t('columns.runtime', 'runtime')
|
|
field: 'locked_at'
|
|
width: 85
|
|
formatter: @runtime_formatter
|
|
)
|
|
for col in cols
|
|
col.sortable = true
|
|
cols
|
|
|
|
updated: () ->
|
|
|
|
init: () ->
|
|
super()
|
|
@sort = (event, data) =>
|
|
@sortData = data
|
|
@saveSelection() if event
|
|
field = data.sortCol.field
|
|
|
|
@data.sort (a, b) =>
|
|
aField = a[field] || ''
|
|
bField = b[field] || ''
|
|
if aField > bField
|
|
result = 1
|
|
else if aField < bField
|
|
result = -1
|
|
else
|
|
result = 0
|
|
|
|
result = -result unless data.sortAsc
|
|
result = -result if field == 'locked_at'
|
|
result
|
|
|
|
@grid.invalidate()
|
|
@restoreSelection()
|
|
@grid.onSort.subscribe(@sort)
|
|
@grid.setSortColumn('runtime', false)
|
|
@sortData = {
|
|
sortCol:
|
|
field: 'locked_at'
|
|
sortAsc: false
|
|
}
|
|
|
|
class Tags extends FlavorGrid
|
|
constructor: (options) ->
|
|
super(options, 'tags', '#tags-grid')
|
|
|
|
build_columns: () ->
|
|
[
|
|
id: 'tag'
|
|
name: I18n.t('columns.tag', 'tag')
|
|
field: 'tag'
|
|
width: 200
|
|
,
|
|
id: 'count'
|
|
name: I18n.t('columns.count', 'count')
|
|
field: 'count'
|
|
width: 50
|
|
]
|
|
|
|
grid_options: () ->
|
|
$.extend(super(), { enableCellNavigation: false })
|
|
|
|
init: () ->
|
|
super()
|
|
@grid.setSelectionModel(new Slick.RowSelectionModel())
|
|
this
|
|
|
|
$.extend(window,
|
|
Jobs: Jobs
|
|
Workers: Workers
|
|
Tags: Tags
|
|
)
|
|
|
|
$(document).ready () ->
|
|
$('#tags-flavor').change () ->
|
|
window.tags.change_flavor($(this).val())
|
|
$('#jobs-flavor').change () ->
|
|
window.jobs.change_flavor($(this).val())
|
|
|
|
$('#jobs-refresh').click () ->
|
|
window.jobs.refresh()
|
|
|
|
search_event = if $('#jobs-search')[0].onsearch == undefined then 'change' else 'search'
|
|
$('#jobs-search').bind search_event, () ->
|
|
window.jobs.search $(this).val()
|
|
|
|
$('#select-all-jobs').click () -> window.jobs.selectAll()
|
|
|
|
$('#hold-jobs').click () -> window.jobs.onSelected('hold')
|
|
$('#un-hold-jobs').click () -> window.jobs.onSelected('unhold')
|
|
$('#delete-jobs').click () -> window.jobs.onSelected('destroy')
|
|
|
|
$('#job-handler-show').click () ->
|
|
window.jobs.getFullJobDetails () ->
|
|
$('#job-handler-wrapper').clone().dialog
|
|
title: I18n.t('titles.job_handler', 'Job Handler')
|
|
width: 900
|
|
height: 700
|
|
modal: true
|
|
false
|
|
|
|
$('#job-last_error-show').click () ->
|
|
window.jobs.getFullJobDetails () ->
|
|
$('#job-last_error-wrapper').clone().dialog
|
|
title: I18n.t('titles.last_error', 'Last Error')
|
|
width: 900
|
|
height: 700
|
|
modal: true
|
|
false
|
|
|
|
{ Jobs, Workers, Tags }
|
|
|