minicron 0.1.0
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 +7 -0
- data/LICENSE +674 -0
- data/README.md +187 -0
- data/Rakefile +17 -0
- data/bin/minicron +26 -0
- data/lib/minicron.rb +179 -0
- data/lib/minicron/alert.rb +115 -0
- data/lib/minicron/alert/email.rb +50 -0
- data/lib/minicron/alert/pagerduty.rb +39 -0
- data/lib/minicron/alert/sms.rb +47 -0
- data/lib/minicron/cli.rb +367 -0
- data/lib/minicron/constants.rb +7 -0
- data/lib/minicron/cron.rb +192 -0
- data/lib/minicron/hub/app.rb +132 -0
- data/lib/minicron/hub/assets/app/application.js +151 -0
- data/lib/minicron/hub/assets/app/components/schedules.js +280 -0
- data/lib/minicron/hub/assets/app/controllers/executions.js +35 -0
- data/lib/minicron/hub/assets/app/controllers/hosts.js +129 -0
- data/lib/minicron/hub/assets/app/controllers/jobs.js +109 -0
- data/lib/minicron/hub/assets/app/controllers/schedules.js +80 -0
- data/lib/minicron/hub/assets/app/helpers.js +22 -0
- data/lib/minicron/hub/assets/app/models/execution.js +13 -0
- data/lib/minicron/hub/assets/app/models/host.js +15 -0
- data/lib/minicron/hub/assets/app/models/job.js +15 -0
- data/lib/minicron/hub/assets/app/models/job_execution_output.js +11 -0
- data/lib/minicron/hub/assets/app/models/schedule.js +32 -0
- data/lib/minicron/hub/assets/app/router.js +31 -0
- data/lib/minicron/hub/assets/app/routes/executions.js +36 -0
- data/lib/minicron/hub/assets/app/routes/hosts.js +42 -0
- data/lib/minicron/hub/assets/app/routes/index.js +9 -0
- data/lib/minicron/hub/assets/app/routes/jobs.js +52 -0
- data/lib/minicron/hub/assets/app/routes/schedules.js +37 -0
- data/lib/minicron/hub/assets/css/bootswatch.min.css +9 -0
- data/lib/minicron/hub/assets/css/main.scss +323 -0
- data/lib/minicron/hub/assets/fonts/glyphicons-halflings-regular.eot +0 -0
- data/lib/minicron/hub/assets/fonts/glyphicons-halflings-regular.svg +229 -0
- data/lib/minicron/hub/assets/fonts/glyphicons-halflings-regular.ttf +0 -0
- data/lib/minicron/hub/assets/fonts/glyphicons-halflings-regular.woff +0 -0
- data/lib/minicron/hub/assets/fonts/lato-bold-700.woff +0 -0
- data/lib/minicron/hub/assets/fonts/lato-italic-400.woff +0 -0
- data/lib/minicron/hub/assets/fonts/lato-regular-400.woff +0 -0
- data/lib/minicron/hub/assets/js/ansi_up-1.1.1.min.js +6 -0
- data/lib/minicron/hub/assets/js/auth/ember-auth-9.0.7.min.js +2 -0
- data/lib/minicron/hub/assets/js/auth/ember-auth-request-jquery-1.0.3.min.js +1 -0
- data/lib/minicron/hub/assets/js/bootstrap-3.1.1.min.js +6 -0
- data/lib/minicron/hub/assets/js/ember-1.4.1.min.js +18 -0
- data/lib/minicron/hub/assets/js/ember-data-1.0.0-beta.7.f87cba88.min.js +10 -0
- data/lib/minicron/hub/assets/js/faye-browser-1.0.1.min.js +2 -0
- data/lib/minicron/hub/assets/js/handlebars-1.3.0.min.js +29 -0
- data/lib/minicron/hub/assets/js/jquery-2.1.0.min.js +4 -0
- data/lib/minicron/hub/assets/js/moment-2.5.1.min.js +7 -0
- data/lib/minicron/hub/controllers/api/executions.rb +34 -0
- data/lib/minicron/hub/controllers/api/hosts.rb +150 -0
- data/lib/minicron/hub/controllers/api/job_execution_outputs.rb +30 -0
- data/lib/minicron/hub/controllers/api/jobs.rb +118 -0
- data/lib/minicron/hub/controllers/api/schedule.rb +184 -0
- data/lib/minicron/hub/controllers/index.rb +5 -0
- data/lib/minicron/hub/db/schema.rb +98 -0
- data/lib/minicron/hub/db/schema.sql +158 -0
- data/lib/minicron/hub/models/alert.rb +7 -0
- data/lib/minicron/hub/models/execution.rb +8 -0
- data/lib/minicron/hub/models/host.rb +7 -0
- data/lib/minicron/hub/models/job.rb +18 -0
- data/lib/minicron/hub/models/job_execution_output.rb +7 -0
- data/lib/minicron/hub/models/schedule.rb +25 -0
- data/lib/minicron/hub/serializers/execution.rb +75 -0
- data/lib/minicron/hub/serializers/host.rb +57 -0
- data/lib/minicron/hub/serializers/job.rb +104 -0
- data/lib/minicron/hub/serializers/job_execution_output.rb +48 -0
- data/lib/minicron/hub/serializers/schedule.rb +68 -0
- data/lib/minicron/hub/views/handlebars/application.erb +51 -0
- data/lib/minicron/hub/views/handlebars/errors.erb +29 -0
- data/lib/minicron/hub/views/handlebars/executions.erb +79 -0
- data/lib/minicron/hub/views/handlebars/hosts.erb +205 -0
- data/lib/minicron/hub/views/handlebars/jobs.erb +203 -0
- data/lib/minicron/hub/views/handlebars/loading.erb +3 -0
- data/lib/minicron/hub/views/handlebars/schedules.erb +354 -0
- data/lib/minicron/hub/views/index.erb +7 -0
- data/lib/minicron/hub/views/layouts/app.erb +15 -0
- data/lib/minicron/monitor.rb +116 -0
- data/lib/minicron/transport.rb +15 -0
- data/lib/minicron/transport/client.rb +80 -0
- data/lib/minicron/transport/faye/client.rb +103 -0
- data/lib/minicron/transport/faye/extensions/job_handler.rb +184 -0
- data/lib/minicron/transport/faye/server.rb +58 -0
- data/lib/minicron/transport/server.rb +62 -0
- data/lib/minicron/transport/ssh.rb +51 -0
- data/spec/invalid_config.toml +2 -0
- data/spec/minicron/cli_spec.rb +154 -0
- data/spec/minicron/transport/client_spec.rb +8 -0
- data/spec/minicron/transport/faye/client_spec.rb +53 -0
- data/spec/minicron/transport/server_spec.rb +70 -0
- data/spec/minicron/transport_spec.rb +13 -0
- data/spec/minicron_spec.rb +133 -0
- data/spec/spec_helper.rb +33 -0
- data/spec/valid_config.toml +48 -0
- metadata +577 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
require 'shellwords'
|
|
2
|
+
|
|
3
|
+
module Minicron
|
|
4
|
+
# Used to interact with the crontab on hosts over an ssh connection
|
|
5
|
+
# TODO: I've had a moment of clarity, I don't need to do all the CRUD
|
|
6
|
+
# using unix commands. I can cat the crontab, manipulate it in ruby
|
|
7
|
+
# and then echo it back!
|
|
8
|
+
class Cron
|
|
9
|
+
# Initialise the cron class
|
|
10
|
+
#
|
|
11
|
+
# @param ssh [Minicron::Transport::SSH] instance
|
|
12
|
+
def initialize(ssh)
|
|
13
|
+
@ssh = ssh
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Escape the quotes in a command
|
|
17
|
+
#
|
|
18
|
+
# @param command [String]
|
|
19
|
+
# @return [String]
|
|
20
|
+
def escape_command(command)
|
|
21
|
+
command.gsub(/\\|'/) { |c| "\\#{c}" }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Build the minicron command to be used in the crontab
|
|
25
|
+
#
|
|
26
|
+
# @param command [String]
|
|
27
|
+
# @param schedule [String]
|
|
28
|
+
# @return [String]
|
|
29
|
+
def build_minicron_command(command, schedule)
|
|
30
|
+
# Escape any quotes in the command
|
|
31
|
+
command = escape_command(command)
|
|
32
|
+
|
|
33
|
+
"#{schedule} root minicron run '#{command}'"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Used to find a string and replace it with another in the crontab by
|
|
37
|
+
# using the sed command
|
|
38
|
+
#
|
|
39
|
+
# @param conn an instance of an open ssh connection
|
|
40
|
+
# @param find [String]
|
|
41
|
+
# @param replace [String]
|
|
42
|
+
def find_and_replace(conn, find, replace)
|
|
43
|
+
# TODO: move ssh test here for crontab permissions
|
|
44
|
+
|
|
45
|
+
# Get the full crontab
|
|
46
|
+
crontab = conn.exec!('cat /etc/crontab').to_s.strip
|
|
47
|
+
|
|
48
|
+
# Replace the full string with the replacement string
|
|
49
|
+
begin
|
|
50
|
+
crontab[find] = replace
|
|
51
|
+
rescue Exception => e
|
|
52
|
+
raise Exception, "Unable to replace '#{find}' with '#{replace}' in the crontab, reason: #{e}"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Echo the crontab back to the tmp crontab
|
|
56
|
+
update = conn.exec!("echo #{crontab.shellescape} > /etc/crontab.tmp && echo 'y' || echo 'n'").to_s.strip
|
|
57
|
+
|
|
58
|
+
# Throw an exception if it failed
|
|
59
|
+
if update != 'y'
|
|
60
|
+
raise Exception, "Unable to replace '#{find}' with '#{replace}' in the crontab"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# If it's a delete
|
|
64
|
+
if replace == ''
|
|
65
|
+
# Check the original line is no longer there
|
|
66
|
+
grep = conn.exec!("grep -F \"#{find}\" /etc/crontab.tmp").to_s.strip
|
|
67
|
+
|
|
68
|
+
# Throw an exception if we can't see our new line at the end of the file
|
|
69
|
+
if grep != replace
|
|
70
|
+
raise Exception, "Expected to find nothing when grepping crontab but found #{grep}"
|
|
71
|
+
end
|
|
72
|
+
else
|
|
73
|
+
# Check the updated line is there
|
|
74
|
+
grep = conn.exec!("grep -F \"#{replace}\" /etc/crontab.tmp").to_s.strip
|
|
75
|
+
|
|
76
|
+
# Throw an exception if we can't see our new line at the end of the file
|
|
77
|
+
if grep != replace
|
|
78
|
+
raise Exception, "Expected to find '#{replace}' when grepping crontab but found #{grep}"
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# And finally replace the crontab with the new one now we now the change worked
|
|
83
|
+
move = conn.exec!("mv /etc/crontab.tmp /etc/crontab && echo 'y' || echo 'n'").to_s.strip
|
|
84
|
+
|
|
85
|
+
if move != 'y'
|
|
86
|
+
raise Exception, 'Unable to move tmp crontab with updated crontab'
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Add the schedule for this job to the crontab
|
|
91
|
+
#
|
|
92
|
+
# @param job [Minicron::Hub::Job] an instance of a job model
|
|
93
|
+
# @param schedule [String] the job schedule as a string
|
|
94
|
+
# @param conn an instance of an open ssh connection
|
|
95
|
+
def add_schedule(job, schedule, conn = nil)
|
|
96
|
+
# Open an SSH connection
|
|
97
|
+
conn ||= @ssh.open
|
|
98
|
+
|
|
99
|
+
# Prepare the line we are going to write to the crontab
|
|
100
|
+
line = build_minicron_command(job.command, schedule)
|
|
101
|
+
echo_line = "echo \"#{line}\" >> /etc/crontab && echo 'y' || echo 'n'"
|
|
102
|
+
|
|
103
|
+
# Append it to the end of the crontab
|
|
104
|
+
write = conn.exec!(echo_line).strip
|
|
105
|
+
|
|
106
|
+
# Throw an exception if it failed
|
|
107
|
+
if write != 'y'
|
|
108
|
+
raise Exception, "Unable to write '#{line}' to the crontab"
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Check the line is there
|
|
112
|
+
tail = conn.exec!('tail -n 1 /etc/crontab').strip
|
|
113
|
+
|
|
114
|
+
# Throw an exception if we can't see our new line at the end of the file
|
|
115
|
+
if tail != line
|
|
116
|
+
raise Exception, "Expected to find '#{line}' at eof but found '#{tail}'"
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Update the schedule for this job in the crontab
|
|
121
|
+
#
|
|
122
|
+
# @param job [Minicron::Hub::Job] an instance of a job model
|
|
123
|
+
# @param old_schedule [String] the old job schedule as a string
|
|
124
|
+
# @param new_schedule [String] the new job schedule as a string
|
|
125
|
+
# @param conn an instance of an open ssh connection
|
|
126
|
+
def update_schedule(job, old_schedule, new_schedule, conn = nil)
|
|
127
|
+
# Open an SSH connection
|
|
128
|
+
conn ||= @ssh.open
|
|
129
|
+
|
|
130
|
+
# We are looking for the current value of the schedule
|
|
131
|
+
find = build_minicron_command(job.command, old_schedule)
|
|
132
|
+
|
|
133
|
+
# And replacing it with the updated value
|
|
134
|
+
replace = build_minicron_command(job.command, new_schedule)
|
|
135
|
+
|
|
136
|
+
# Replace the old schedule with the new schedule
|
|
137
|
+
find_and_replace(conn, find, replace)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Remove the schedule for this job from the crontab
|
|
141
|
+
#
|
|
142
|
+
# @param job [Minicron::Hub::Job] an instance of a job model
|
|
143
|
+
# @param schedule [String] the job schedule as a string
|
|
144
|
+
# @param conn an instance of an open ssh connection
|
|
145
|
+
def delete_schedule(job, schedule, conn = nil)
|
|
146
|
+
# Open an SSH connection
|
|
147
|
+
conn ||= @ssh.open
|
|
148
|
+
|
|
149
|
+
# We are looking for the current value of the schedule
|
|
150
|
+
find = build_minicron_command(job.command, schedule)
|
|
151
|
+
|
|
152
|
+
# Replace the old schedule with nothing i.e deleting it
|
|
153
|
+
find_and_replace(conn, find, '')
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Delete a job and all it's schedules from the crontab
|
|
157
|
+
#
|
|
158
|
+
# @param job [Minicron::Hub::Job] a job instance with it's schedules
|
|
159
|
+
# @param conn an instance of an open ssh connection
|
|
160
|
+
def delete_job(job, conn = nil)
|
|
161
|
+
conn ||= @ssh.open
|
|
162
|
+
|
|
163
|
+
# Loop through each schedule and delete them one by one
|
|
164
|
+
# TODO: share the ssh connection for this so it's faster when
|
|
165
|
+
# many schedules exist
|
|
166
|
+
# TODO: what if one schedule removal fails but others don't? Should
|
|
167
|
+
# we try and rollback somehow or just return the job with half its
|
|
168
|
+
# schedules deleted?
|
|
169
|
+
job.schedules.each do |schedule|
|
|
170
|
+
delete_schedule(job, schedule.formatted, conn)
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Delete a host and all it's jobs from the crontab
|
|
175
|
+
#
|
|
176
|
+
# @param job [Minicron::Hub::Job] a job instance with it's schedules
|
|
177
|
+
# @param conn an instance of an open ssh connection
|
|
178
|
+
def delete_host(host, conn = nil)
|
|
179
|
+
conn ||= @ssh.open
|
|
180
|
+
|
|
181
|
+
# Loop through each job and delete them one by one
|
|
182
|
+
# TODO: share the ssh connection for this so it's faster when
|
|
183
|
+
# many schedules exist
|
|
184
|
+
# TODO: what if one schedule removal fails but others don't? Should
|
|
185
|
+
# we try and rollback somehow or just return the job with half its
|
|
186
|
+
# schedules deleted?
|
|
187
|
+
host.jobs.each do |job|
|
|
188
|
+
delete_job(job, conn)
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
require 'minicron'
|
|
2
|
+
require 'sinatra/base'
|
|
3
|
+
require 'sinatra/activerecord'
|
|
4
|
+
require 'sinatra/assetpack'
|
|
5
|
+
require 'erubis'
|
|
6
|
+
require 'oj'
|
|
7
|
+
|
|
8
|
+
module Minicron::Hub
|
|
9
|
+
class App < Sinatra::Base
|
|
10
|
+
register Sinatra::ActiveRecordExtension
|
|
11
|
+
register Sinatra::AssetPack
|
|
12
|
+
|
|
13
|
+
# Set the application root
|
|
14
|
+
set :root, Minicron::HUB_PATH
|
|
15
|
+
|
|
16
|
+
configure do
|
|
17
|
+
# Don't log them. We'll do that ourself
|
|
18
|
+
set :dump_errors, false
|
|
19
|
+
|
|
20
|
+
# Don't capture any errors. Throw them up the stack
|
|
21
|
+
set :raise_errors, true
|
|
22
|
+
|
|
23
|
+
# Disable internal middleware for presenting errors
|
|
24
|
+
# as useful HTML pages
|
|
25
|
+
set :show_exceptions, false
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Configure how we server assets
|
|
29
|
+
assets do
|
|
30
|
+
serve '/css', :from => 'assets/css'
|
|
31
|
+
serve '/js', :from => 'assets/js'
|
|
32
|
+
serve '/fonts', :from => 'assets/fonts'
|
|
33
|
+
serve '/app', :from => 'assets/app'
|
|
34
|
+
|
|
35
|
+
# Set up the application css
|
|
36
|
+
css :app, '/css/all.css', [
|
|
37
|
+
'/css/bootswatch.min.css',
|
|
38
|
+
'/css/main.css'
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
# Set up the application javascript
|
|
42
|
+
js :app, '/js/all.js', [
|
|
43
|
+
# Dependencies, the order of these is important
|
|
44
|
+
'/js/jquery-2.1.0.min.js',
|
|
45
|
+
'/js/handlebars-1.3.0.min.js',
|
|
46
|
+
'/js/ember-1.4.1.min.js',
|
|
47
|
+
'/js/ember-data-1.0.0-beta.7.f87cba88.min.js',
|
|
48
|
+
'/js/faye-browser-1.0.1.min.js',
|
|
49
|
+
'/js/ansi_up-1.1.1.min.js',
|
|
50
|
+
'/js/bootstrap-3.1.1.min.js',
|
|
51
|
+
'/js/moment-2.5.1.min.js',
|
|
52
|
+
|
|
53
|
+
# Ember application files
|
|
54
|
+
'/app/**/*.js'
|
|
55
|
+
]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Called on class initilisation, sets up the database and requires all
|
|
59
|
+
# the application files
|
|
60
|
+
def initialize
|
|
61
|
+
super
|
|
62
|
+
|
|
63
|
+
# Initialize the db
|
|
64
|
+
Minicron::Hub::App.setup_db
|
|
65
|
+
|
|
66
|
+
# Load all our model serializers
|
|
67
|
+
Dir[File.dirname(__FILE__) + '/serializers/*.rb'].each do |serializer|
|
|
68
|
+
require serializer
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Load all our models
|
|
72
|
+
Dir[File.dirname(__FILE__) + '/models/*.rb'].each do |model|
|
|
73
|
+
require model
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Load all our controllers
|
|
77
|
+
Dir[File.dirname(__FILE__) + '/controllers/**/*.rb'].each do |controller|
|
|
78
|
+
require controller
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Used to set up the database connection
|
|
83
|
+
def self.setup_db
|
|
84
|
+
# Configure the database
|
|
85
|
+
case Minicron.config['database']['type']
|
|
86
|
+
when 'mysql'
|
|
87
|
+
set :database, {
|
|
88
|
+
:adapter => 'mysql2',
|
|
89
|
+
:host => Minicron.config['database']['host'],
|
|
90
|
+
:database => Minicron.config['database']['database'],
|
|
91
|
+
:username => Minicron.config['database']['username'],
|
|
92
|
+
:password => Minicron.config['database']['password']
|
|
93
|
+
}
|
|
94
|
+
else
|
|
95
|
+
raise Exception, "The database #{Minicron.config['database']['type']} is not supported"
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Based on http://hawkins.io/2013/06/error-handling-in-sinatra-apis/
|
|
101
|
+
class ExceptionHandling
|
|
102
|
+
def initialize(app)
|
|
103
|
+
@app = app
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def handle_exception(env, e, status)
|
|
107
|
+
if Minicron.config['global']['trace']
|
|
108
|
+
env['rack.errors'].puts(e)
|
|
109
|
+
env['rack.errors'].puts(e.backtrace.join("\n"))
|
|
110
|
+
env['rack.errors'].flush
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Display the error message
|
|
114
|
+
hash = { :error => e.to_s }
|
|
115
|
+
|
|
116
|
+
# Display the full trace if tracing is enabled
|
|
117
|
+
hash[:trace] = e.backtrace if Minicron.config['global']['trace']
|
|
118
|
+
|
|
119
|
+
[status, { 'Content-Type' => 'application/json' }, [hash.to_json]]
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def call(env)
|
|
123
|
+
begin
|
|
124
|
+
@app.call env
|
|
125
|
+
rescue ActiveRecord::RecordNotFound => e
|
|
126
|
+
handle_exception(env, e, 404)
|
|
127
|
+
rescue Exception => e
|
|
128
|
+
handle_exception(env, e, 500)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
(function() {
|
|
4
|
+
window.Minicron = Ember.Application.create({
|
|
5
|
+
LOG_TRANSITIONS: true,
|
|
6
|
+
LOG_ACTIVE_GENERATION: true,
|
|
7
|
+
LOG_VIEW_LOOKUPS: true,
|
|
8
|
+
LOG_TRANSITIONS_INTERNAL: true
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
// This should be called by all views when they are loaded
|
|
12
|
+
Minicron.onViewLoad = function() {
|
|
13
|
+
// Twitter tooltip plugin
|
|
14
|
+
Ember.$('body').tooltip({
|
|
15
|
+
selector: '[data-toggle=tooltip]'
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// Configure Ember Data so it can find the API
|
|
20
|
+
Minicron.ApplicationAdapter = DS.RESTAdapter.extend({
|
|
21
|
+
namespace: 'api',
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
Minicron.ApplicationController = Ember.ArrayController.extend({
|
|
25
|
+
sortedExecutions: function() {
|
|
26
|
+
return this.get('content').toArray().sort(function(a, b) {
|
|
27
|
+
a = +moment(a.get('created_at'));
|
|
28
|
+
b = +moment(b.get('created_at'));
|
|
29
|
+
|
|
30
|
+
if (a > b) {
|
|
31
|
+
return -1;
|
|
32
|
+
} else if (a < b) {
|
|
33
|
+
return 1;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return 0;
|
|
37
|
+
});
|
|
38
|
+
}.property('content.@each').cacheable()
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
Minicron.ApplicationRoute = Ember.Route.extend({
|
|
42
|
+
actions: {
|
|
43
|
+
error: function(error) {
|
|
44
|
+
console.log(error);
|
|
45
|
+
|
|
46
|
+
// Create a new error to be passed as the controller
|
|
47
|
+
var ember_error = new Ember.Error();
|
|
48
|
+
|
|
49
|
+
// Set the details of the error we need
|
|
50
|
+
ember_error.name = 'Error';
|
|
51
|
+
ember_error.message = error.responseJSON.error;
|
|
52
|
+
ember_error.number = error.status;
|
|
53
|
+
|
|
54
|
+
this.render('fatal-error', {
|
|
55
|
+
controller: ember_error
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
model: function() {
|
|
60
|
+
return this.store.find('execution');
|
|
61
|
+
},
|
|
62
|
+
setupController: function(controller, model) {
|
|
63
|
+
controller.set('application', model);
|
|
64
|
+
this._super(controller, model);
|
|
65
|
+
},
|
|
66
|
+
afterModel: function(model, transition) {
|
|
67
|
+
var client = new Faye.Client(window.location.protocol + '//' + window.location.host + '/faye'),
|
|
68
|
+
store = model.store,
|
|
69
|
+
self = this;
|
|
70
|
+
window.store = store;
|
|
71
|
+
|
|
72
|
+
client.addExtension({
|
|
73
|
+
incoming: function(message, callback) {
|
|
74
|
+
// We only care about job messages
|
|
75
|
+
if (message.channel.substr(1, 3) === 'job') {
|
|
76
|
+
var segments = message.channel.split('/');
|
|
77
|
+
var job_id = segments[2];
|
|
78
|
+
var job_execution_id = segments[3];
|
|
79
|
+
var type = segments[4];
|
|
80
|
+
var message_data = message.data.message;
|
|
81
|
+
|
|
82
|
+
// TODO: remove this!
|
|
83
|
+
console.log(job_id, job_execution_id, type, message);
|
|
84
|
+
|
|
85
|
+
// Is it a status message?
|
|
86
|
+
if (type === 'status') {
|
|
87
|
+
// Is it a setup message
|
|
88
|
+
if (typeof message_data.action != 'undefined' && message_data.action === 'SETUP') {
|
|
89
|
+
// The SETUP message is defined slightly differently, segment 2 contains the
|
|
90
|
+
// job hash and segment 3 contains '*job_id*-*job_execution_id*'
|
|
91
|
+
var ids = job_execution_id.split('-');
|
|
92
|
+
job_id = ids[0];
|
|
93
|
+
job_execution_id = ids[1];
|
|
94
|
+
|
|
95
|
+
// Append the job relationship to it
|
|
96
|
+
store.find('job', job_id).then(function(job) {
|
|
97
|
+
// Create the execution
|
|
98
|
+
store.push('execution', {
|
|
99
|
+
id: job_execution_id,
|
|
100
|
+
created_at: moment.utc(message.data.ts).format('YYYY-MM-DDTHH:mm:ss[Z]'),
|
|
101
|
+
job: job
|
|
102
|
+
}, true);
|
|
103
|
+
});
|
|
104
|
+
// Is it a start message?
|
|
105
|
+
} else if (message_data.substr(0, 5) === 'START') {
|
|
106
|
+
// Set the execution start time
|
|
107
|
+
store.find('execution', job_execution_id).then(function(execution) {
|
|
108
|
+
execution.set('started_at', moment.utc(message_data.substr(6)).format('YYYY-MM-DDTHH:mm:ss[Z]'));
|
|
109
|
+
});
|
|
110
|
+
// Is it a finish message?
|
|
111
|
+
} else if (message_data.substr(0, 6) === 'FINISH') {
|
|
112
|
+
// Set the execution finish time
|
|
113
|
+
store.find('execution', job_execution_id).then(function(execution) {
|
|
114
|
+
execution.set('finished_at', moment.utc(message_data.substr(7)).format('YYYY-MM-DDTHH:mm:ss[Z]'));
|
|
115
|
+
});
|
|
116
|
+
// Is it an exit message?
|
|
117
|
+
} else if (message_data.substr(0, 4) === 'EXIT') {
|
|
118
|
+
// Set the execution exit status
|
|
119
|
+
store.find('execution', job_execution_id).then(function(execution) {
|
|
120
|
+
execution.set('exit_status', +message_data.substr(5));
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
// Is it an output message?
|
|
124
|
+
} else if (type === 'output') {
|
|
125
|
+
store.find('execution', job_execution_id).then(function(execution) {
|
|
126
|
+
// Add this bit of job execution output
|
|
127
|
+
var output = store.createRecord('job_execution_output', {
|
|
128
|
+
id: message.data.job_execution_output_id,
|
|
129
|
+
output: message_data,
|
|
130
|
+
seq: message.data.seq,
|
|
131
|
+
timestamp: moment(message.data.ts).format()
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
output.set('execution', execution);
|
|
135
|
+
|
|
136
|
+
execution.get('job_execution_outputs').then(function(outputs) {
|
|
137
|
+
outputs.pushObject(output);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// We're done with the message, pass it back to Faye
|
|
144
|
+
callback(message);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
client.subscribe('/job/**');
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
})();
|