minicron 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +674 -0
  3. data/README.md +187 -0
  4. data/Rakefile +17 -0
  5. data/bin/minicron +26 -0
  6. data/lib/minicron.rb +179 -0
  7. data/lib/minicron/alert.rb +115 -0
  8. data/lib/minicron/alert/email.rb +50 -0
  9. data/lib/minicron/alert/pagerduty.rb +39 -0
  10. data/lib/minicron/alert/sms.rb +47 -0
  11. data/lib/minicron/cli.rb +367 -0
  12. data/lib/minicron/constants.rb +7 -0
  13. data/lib/minicron/cron.rb +192 -0
  14. data/lib/minicron/hub/app.rb +132 -0
  15. data/lib/minicron/hub/assets/app/application.js +151 -0
  16. data/lib/minicron/hub/assets/app/components/schedules.js +280 -0
  17. data/lib/minicron/hub/assets/app/controllers/executions.js +35 -0
  18. data/lib/minicron/hub/assets/app/controllers/hosts.js +129 -0
  19. data/lib/minicron/hub/assets/app/controllers/jobs.js +109 -0
  20. data/lib/minicron/hub/assets/app/controllers/schedules.js +80 -0
  21. data/lib/minicron/hub/assets/app/helpers.js +22 -0
  22. data/lib/minicron/hub/assets/app/models/execution.js +13 -0
  23. data/lib/minicron/hub/assets/app/models/host.js +15 -0
  24. data/lib/minicron/hub/assets/app/models/job.js +15 -0
  25. data/lib/minicron/hub/assets/app/models/job_execution_output.js +11 -0
  26. data/lib/minicron/hub/assets/app/models/schedule.js +32 -0
  27. data/lib/minicron/hub/assets/app/router.js +31 -0
  28. data/lib/minicron/hub/assets/app/routes/executions.js +36 -0
  29. data/lib/minicron/hub/assets/app/routes/hosts.js +42 -0
  30. data/lib/minicron/hub/assets/app/routes/index.js +9 -0
  31. data/lib/minicron/hub/assets/app/routes/jobs.js +52 -0
  32. data/lib/minicron/hub/assets/app/routes/schedules.js +37 -0
  33. data/lib/minicron/hub/assets/css/bootswatch.min.css +9 -0
  34. data/lib/minicron/hub/assets/css/main.scss +323 -0
  35. data/lib/minicron/hub/assets/fonts/glyphicons-halflings-regular.eot +0 -0
  36. data/lib/minicron/hub/assets/fonts/glyphicons-halflings-regular.svg +229 -0
  37. data/lib/minicron/hub/assets/fonts/glyphicons-halflings-regular.ttf +0 -0
  38. data/lib/minicron/hub/assets/fonts/glyphicons-halflings-regular.woff +0 -0
  39. data/lib/minicron/hub/assets/fonts/lato-bold-700.woff +0 -0
  40. data/lib/minicron/hub/assets/fonts/lato-italic-400.woff +0 -0
  41. data/lib/minicron/hub/assets/fonts/lato-regular-400.woff +0 -0
  42. data/lib/minicron/hub/assets/js/ansi_up-1.1.1.min.js +6 -0
  43. data/lib/minicron/hub/assets/js/auth/ember-auth-9.0.7.min.js +2 -0
  44. data/lib/minicron/hub/assets/js/auth/ember-auth-request-jquery-1.0.3.min.js +1 -0
  45. data/lib/minicron/hub/assets/js/bootstrap-3.1.1.min.js +6 -0
  46. data/lib/minicron/hub/assets/js/ember-1.4.1.min.js +18 -0
  47. data/lib/minicron/hub/assets/js/ember-data-1.0.0-beta.7.f87cba88.min.js +10 -0
  48. data/lib/minicron/hub/assets/js/faye-browser-1.0.1.min.js +2 -0
  49. data/lib/minicron/hub/assets/js/handlebars-1.3.0.min.js +29 -0
  50. data/lib/minicron/hub/assets/js/jquery-2.1.0.min.js +4 -0
  51. data/lib/minicron/hub/assets/js/moment-2.5.1.min.js +7 -0
  52. data/lib/minicron/hub/controllers/api/executions.rb +34 -0
  53. data/lib/minicron/hub/controllers/api/hosts.rb +150 -0
  54. data/lib/minicron/hub/controllers/api/job_execution_outputs.rb +30 -0
  55. data/lib/minicron/hub/controllers/api/jobs.rb +118 -0
  56. data/lib/minicron/hub/controllers/api/schedule.rb +184 -0
  57. data/lib/minicron/hub/controllers/index.rb +5 -0
  58. data/lib/minicron/hub/db/schema.rb +98 -0
  59. data/lib/minicron/hub/db/schema.sql +158 -0
  60. data/lib/minicron/hub/models/alert.rb +7 -0
  61. data/lib/minicron/hub/models/execution.rb +8 -0
  62. data/lib/minicron/hub/models/host.rb +7 -0
  63. data/lib/minicron/hub/models/job.rb +18 -0
  64. data/lib/minicron/hub/models/job_execution_output.rb +7 -0
  65. data/lib/minicron/hub/models/schedule.rb +25 -0
  66. data/lib/minicron/hub/serializers/execution.rb +75 -0
  67. data/lib/minicron/hub/serializers/host.rb +57 -0
  68. data/lib/minicron/hub/serializers/job.rb +104 -0
  69. data/lib/minicron/hub/serializers/job_execution_output.rb +48 -0
  70. data/lib/minicron/hub/serializers/schedule.rb +68 -0
  71. data/lib/minicron/hub/views/handlebars/application.erb +51 -0
  72. data/lib/minicron/hub/views/handlebars/errors.erb +29 -0
  73. data/lib/minicron/hub/views/handlebars/executions.erb +79 -0
  74. data/lib/minicron/hub/views/handlebars/hosts.erb +205 -0
  75. data/lib/minicron/hub/views/handlebars/jobs.erb +203 -0
  76. data/lib/minicron/hub/views/handlebars/loading.erb +3 -0
  77. data/lib/minicron/hub/views/handlebars/schedules.erb +354 -0
  78. data/lib/minicron/hub/views/index.erb +7 -0
  79. data/lib/minicron/hub/views/layouts/app.erb +15 -0
  80. data/lib/minicron/monitor.rb +116 -0
  81. data/lib/minicron/transport.rb +15 -0
  82. data/lib/minicron/transport/client.rb +80 -0
  83. data/lib/minicron/transport/faye/client.rb +103 -0
  84. data/lib/minicron/transport/faye/extensions/job_handler.rb +184 -0
  85. data/lib/minicron/transport/faye/server.rb +58 -0
  86. data/lib/minicron/transport/server.rb +62 -0
  87. data/lib/minicron/transport/ssh.rb +51 -0
  88. data/spec/invalid_config.toml +2 -0
  89. data/spec/minicron/cli_spec.rb +154 -0
  90. data/spec/minicron/transport/client_spec.rb +8 -0
  91. data/spec/minicron/transport/faye/client_spec.rb +53 -0
  92. data/spec/minicron/transport/server_spec.rb +70 -0
  93. data/spec/minicron/transport_spec.rb +13 -0
  94. data/spec/minicron_spec.rb +133 -0
  95. data/spec/spec_helper.rb +33 -0
  96. data/spec/valid_config.toml +48 -0
  97. metadata +577 -0
@@ -0,0 +1,280 @@
1
+ 'use strict';
2
+
3
+ (function() {
4
+ // Taken from http://stackoverflow.com/a/1830844/483271
5
+ function isNumeric(value) {
6
+ return !isNaN(parseFloat(value)) && isFinite(value);
7
+ }
8
+
9
+ // Normalise expression values, such as the string equivelants of months/days
10
+ function normaliseExpressionValue(value) {
11
+ var mappings = {
12
+ 'jan': '1',
13
+ 'feb': '2',
14
+ 'mar': '3',
15
+ 'apr': '4',
16
+ 'may': '5',
17
+ 'jun': '6',
18
+ 'jul': '7',
19
+ 'aug': '8',
20
+ 'sep': '9',
21
+ 'oct': '10',
22
+ 'nov': '11',
23
+ 'dec': '12',
24
+ 'sun': '0',
25
+ 'mon': '1',
26
+ 'tue': '2',
27
+ 'wed': '3',
28
+ 'thu': '4',
29
+ 'fri': '5',
30
+ 'sat': '6'
31
+ };
32
+
33
+ // If it was found in the mappings object return it
34
+ if (typeof mappings[value.toLowerCase()] !== 'undefined') {
35
+ return mappings[value.toLowerCase()];
36
+ }
37
+
38
+ return value;
39
+ }
40
+
41
+ // Used to parse the schedule on view load and update the GUI with those values
42
+ function parseSchedule(schedule) {
43
+ // Split the schedule into its 5 parts based on whitespace
44
+ var expression = schedule.split(' ');
45
+
46
+ // Loop every value in the expression
47
+ for (var i = 0; i < expression.length; i++) {
48
+ // If the value is '*' we don't need to do anything
49
+ if (expression[i] === '*') {
50
+ continue;
51
+ }
52
+
53
+ // Select the panel for this expression
54
+ var panel = Ember.$('#schedule-editor').find('.panel').eq(i);
55
+
56
+ // If it's a */n expression
57
+ if (expression[i].substr(0, 2) === '*/' && isNumeric(expression[i].substr(2))) {
58
+ // Show the correct tab
59
+ panel.find('.nav li:eq(1) a').tab('show');
60
+
61
+ // Set the value of the expression in the input box
62
+ panel.find('.tab-content .active input[type="number"]').val(expression[i].substr(2));
63
+ }
64
+
65
+ // If it's an each selected expression
66
+ // TODO: support range expression e.g 10-15
67
+ else if (isNumeric(expression[i]) || expression[i].split(',').length > 1 || expression[i].split('-').length > 1) {
68
+ var value,
69
+ range,
70
+ selected = [],
71
+ split_by_comma = expression[i].split(',');
72
+
73
+ // Show the correct tab
74
+ panel.find('.nav li').last().find('a').tab('show');
75
+
76
+ // If it's just a plain number add it to selected array and we're done
77
+ if (isNumeric(expression[i])) {
78
+ selected.push(expression[i]);
79
+ }
80
+
81
+ // If it's a group of values then we need to split them up and handle them
82
+ else if (split_by_comma.length > 0) {
83
+ for (var j = 0; j < split_by_comma.length; j++) {
84
+ // If it was a range expresssion convert it to a csv format
85
+ if (split_by_comma[j].split('-').length > 1) {
86
+ range = split_by_comma[j].split('-');
87
+
88
+ // Loop every value in the range and add it to the selected array
89
+ for (var pos = parseInt(range[0]); pos <= parseInt(range[1]); pos++) {
90
+ selected.push(pos.toString());
91
+ }
92
+ // If it was just a normal csv expression
93
+ } else {
94
+ selected.push(normaliseExpressionValue(split_by_comma[j]));
95
+ }
96
+ }
97
+ }
98
+
99
+ // Loop every value in the selected array and 'check' it
100
+ panel.find('.tab-content .active input[type="checkbox"]').each(function(k, v) {
101
+ // Get the 'value' of the checkbox
102
+ var $this = Ember.$(this);
103
+ value = $this.data('value');
104
+
105
+ // Should it be checked?
106
+ if (selected.indexOf(value.toString()) >= 0) {
107
+ $this[0].checked = true;
108
+ $this.closest('label').addClass('active');
109
+ }
110
+ });
111
+ }
112
+ }
113
+
114
+ // Finally set the schedule in the schedule input textbox
115
+ updateScheduleInput(schedule);
116
+ }
117
+
118
+ // Helper to set the value of the schedule input textbox when it has been updated
119
+ function updateScheduleInput(value) {
120
+ Ember.$('#schedule-input').find('input').val(value);
121
+ }
122
+
123
+ // Called when updates made to the cron GUI, this will update the schedule record
124
+ function handleUpdate(self, $this) {
125
+ // Get the schedule
126
+ var schedule = self.get('schedule');
127
+
128
+ // The 'id' of the event which we use to defer the type
129
+ // it's either the href with the # stripped off if it's a
130
+ // tab click or the actual id attribute of the element
131
+ var id;
132
+
133
+ // If the href exists we can assume it's a tab
134
+ if (typeof $this.attr('href') !== 'undefined') {
135
+ id = $this.attr('href').substr(1);
136
+ } else {
137
+ id = $this.attr('id');
138
+
139
+ // If it's a checkbox set the id to it's parents parent
140
+ if ($this.attr('type') === 'checkbox') {
141
+ id = $this.closest('.tab-pane').attr('id');
142
+ }
143
+
144
+ // If it's a every-n-value then strip off the -value
145
+ else if (id.substr(-6) === '-value') {
146
+ id = id.substr(0, id.length - 6);
147
+ }
148
+ }
149
+
150
+ // Define the types of schedules we are searching for
151
+ var every_n = ['every-minute', 'every-hour', 'every-day-of-the-month', 'every-month', 'every-day-of-the-week'];
152
+ var every_n_type = ['every-n-minutes', 'every-n-hours'];
153
+
154
+ // Define the key/value vars used when the change to the schedule is set
155
+ var key, value = '';
156
+
157
+ // Is it an 'every n' i.e *
158
+ if (every_n.indexOf(id) >= 0) {
159
+ // Transform the id into a key for the schedule, replace - with _
160
+ key = id.substr(6).split('-').join('_');
161
+ value = '*';
162
+ }
163
+
164
+ // Is it any 'every n type' i.e */n
165
+ else if (every_n_type.indexOf(id) >= 0) {
166
+ // Transform the id into a key for the schedule, replace - with _ and strip the trailing s
167
+ key = id.substr(8, (id.length - 1) - 8).split('-').join('_');
168
+
169
+ // Set the value using the value of the input
170
+ value = '*/' + Ember.$('#' + id + '-value').val();
171
+ }
172
+
173
+ // Otherwise we can assume it's a each selected type
174
+ else {
175
+ // Transform the id into a key for the schedule, removing the every-selected-
176
+ // and replace- with _
177
+ key = id.substr(14).split('-').join('_');
178
+
179
+ // Loop every checkbox
180
+ Ember.$('#' + id).find('input[type="checkbox"]').each(function(k, v) {
181
+ // If the checkbox is checked add the value
182
+ if (v.checked === true) {
183
+ value += Ember.$(this).data('value') + ',';
184
+ }
185
+ });
186
+
187
+ // If no value has been set i.e no checkboxes are ticked, default to *
188
+ if (value.length === 0) {
189
+ value = '*';
190
+ // Otherwise we need to remove the trailing ,
191
+ } else {
192
+ if (value.substr(-1) === ',') {
193
+ value = value.substr(0, value.length - 1);
194
+ }
195
+ }
196
+ }
197
+
198
+ // Update the schedule
199
+ console.log('setting', key, value);
200
+ schedule.set(key, value);
201
+
202
+ // Update the schedule input text box
203
+ updateScheduleInput(schedule.get('formatted'));
204
+ }
205
+
206
+ Minicron.ScheduleEditorComponent = Ember.Component.extend({
207
+ didInsertElement: function() {
208
+ this.store = this.get('targetObject.store');
209
+ var self = this;
210
+
211
+ // Do we already have the schedule id? i.e we are editing a schedule
212
+ if (this.get('schedule_id')) {
213
+ // Look up the schedule
214
+ this.store.find('schedule', this.get('schedule_id')).then(function(schedule) {
215
+ self.set('schedule', schedule);
216
+
217
+ // Parse the schedule and display it in the GUI
218
+ parseSchedule(self.get('schedule.formatted'));
219
+ });
220
+ // If not we must be adding a new schedule
221
+ } else {
222
+ // Create a default schedule record
223
+ this.set('schedule', this.store.createRecord('schedule', {
224
+ minute: '*',
225
+ hour: '*',
226
+ day_of_the_month: '*',
227
+ month: '*',
228
+ day_of_the_week: '*'
229
+ }));
230
+
231
+ // Parse the schedule and display it in the GUI
232
+ parseSchedule(this.get('schedule.formatted'));
233
+ }
234
+
235
+ // Update our reference to 'this' so we can pass it to the update handlers
236
+ self = this;
237
+
238
+ // Handle when one of the tabs or labels (checkboxes) is clicked in readonly mode
239
+ Ember.$('#schedule-editor').find('a[data-toggle="tab"], .btn-group > label').on('click', function(e) {
240
+ var $this = Ember.$(this);
241
+
242
+ if (self.get('read_only')) {
243
+ e.preventDefault();
244
+
245
+ // Is it a label (checkbox)? Then we want to stop the click event bubbling
246
+ // up the DOM so the label doesn't get highlighted
247
+ if ($this[0].nodeName === 'LABEL') {
248
+ return false;
249
+ }
250
+ } else {
251
+ // Only handle the update if it isn't a label (checkbox)
252
+ if ($this[0].nodeName !== 'LABEL') {
253
+ handleUpdate(self, $this);
254
+ }
255
+ }
256
+ });
257
+
258
+ // Handle when one of the 'every n x' is inputs is changed
259
+ Ember.$('#schedule-editor').find('input[type="number"], input[type="checkbox"]').on('change', function(e) {
260
+ if (self.get('read_only')) {
261
+ e.preventDefault();
262
+ return false;
263
+ } else {
264
+ handleUpdate(self, Ember.$(this));
265
+ }
266
+ });
267
+ },
268
+ actions: {
269
+ save: function() {
270
+ this.sendAction('save', {
271
+ job_id: this.get('job_id'),
272
+ schedule: this.get('schedule')
273
+ });
274
+ },
275
+ cancel: function(schedule) {
276
+ this.sendAction('cancel', schedule);
277
+ }
278
+ }
279
+ });
280
+ })();
@@ -0,0 +1,35 @@
1
+ 'use strict';
2
+
3
+ (function() {
4
+ function deleteExecution(self, execution) {
5
+ var confirmation = "Are you sure want to delete this execution?";
6
+
7
+ if (window.confirm(confirmation)) {
8
+ // TODO: Do the same cascading delete that happens on the backend
9
+ // i.e deleting this execution also deletes the job execution output
10
+ execution.deleteRecord();
11
+
12
+ execution.save().then(function() {
13
+ self.transitionToRoute('executions');
14
+ }, function(response) {
15
+ execution.rollback();
16
+ console.log(response);
17
+ window.prompt('Error deleting execution, reason:', response.responseJSON.error);
18
+ });
19
+ }
20
+ }
21
+
22
+ Minicron.ExecutionIndexController = Ember.ObjectController.extend({
23
+ actions: {
24
+ delete: function(execution) {
25
+ deleteExecution(this, execution);
26
+ }
27
+ },
28
+ sortedOutput: (function() {
29
+ return Ember.ArrayProxy.createWithMixins(Ember.SortableMixin, {
30
+ sortProperties: ['seq'],
31
+ content: this.get('content.job_execution_outputs')
32
+ });
33
+ }).property('content.job_execution_outputs')
34
+ });
35
+ })();
@@ -0,0 +1,129 @@
1
+ 'use strict';
2
+
3
+ (function() {
4
+ function deleteHost(self, host) {
5
+ var confirmation = "Are you sure want to delete this host?\n\n";
6
+ confirmation += 'All associated data i.e jobs on this host and the jobs in this hosts crontab will be REMOVED!';
7
+
8
+ if (window.confirm(confirmation)) {
9
+ // TODO: Do the same cascading delete that happens on the backend
10
+ // i.e deleting jobs -> executions -> job_execution_outputs linked
11
+ // to this host
12
+ host.deleteRecord();
13
+
14
+ host.save().then(function() {
15
+ self.transitionToRoute('hosts');
16
+ }, function(response) {
17
+ host.rollback();
18
+ console.log(response);
19
+ window.prompt('Error deleting host, reason:', response.responseJSON.error);
20
+ });
21
+ }
22
+ }
23
+
24
+ function testConnection(self, host) {
25
+ self.set('test_connection', 'Testing..');
26
+
27
+ jQuery.getJSON('/api/hosts/' + host.id + '/test_ssh').done(function(data) {
28
+ // Could we at least connect to the host?
29
+ if (data.connect) {
30
+ // TODO: make this use a bootstrap model as a component
31
+ var results = 'Test Results\n\n';
32
+ results += 'connect: ' + data.connect + '\n';
33
+ results += 'crontab readable: ' + data.read + '\n';
34
+ results += 'crontab writeable: ' + data.write;
35
+
36
+ window.alert(results);
37
+ } else {
38
+ window.prompt('Failed to connect to host, reason:', data.responseJSON.error);
39
+ }
40
+ // If the actual ajax request failed
41
+ }).fail(function(xhr, status, error) {
42
+ console.log(xhr, status, error);
43
+ window.prompt('Failed to connect to API, reason:', xhr.responseJSON.error);
44
+ }).always(function() {
45
+ self.set('test_connection', 'Test Connection');
46
+ });
47
+ }
48
+
49
+ Minicron.HostsIndexController = Ember.ObjectController.extend({
50
+ actions: {
51
+ delete: function(host) {
52
+ deleteHost(this, host);
53
+ }
54
+ }
55
+ });
56
+
57
+ Minicron.HostIndexController = Ember.ObjectController.extend({
58
+ test_connection: 'Test Connection',
59
+ actions: {
60
+ delete: function(host) {
61
+ deleteHost(this, host);
62
+ },
63
+ test: function(host) {
64
+ testConnection(this, host);
65
+ }
66
+ }
67
+ });
68
+
69
+ Minicron.HostsNewController = Ember.Controller.extend({
70
+ actions: {
71
+ save: function() {
72
+ var self = this,
73
+ host = this.store.createRecord('host', {
74
+ name: this.get('name'),
75
+ fqdn: this.get('fqdn'),
76
+ host: this.get('host'),
77
+ port: this.get('port'),
78
+ public_key: this.get('public_key')
79
+ });
80
+
81
+ host.save().then(function(host) {
82
+ self.transitionToRoute('host', host);
83
+ // TODO: better error handling here
84
+ }).catch(function(host) {
85
+ window.alert('Error saving host!');
86
+ console.log(host);
87
+ });
88
+ },
89
+ cancel: function(host) {
90
+ this.transitionToRoute('hosts');
91
+ }
92
+ }
93
+ });
94
+
95
+ Minicron.HostEditController = Ember.ObjectController.extend({
96
+ test_connection: 'Test Connection',
97
+ actions: {
98
+ save: function() {
99
+ var self = this,
100
+ host = this.store.push('host', {
101
+ id: this.get('id'),
102
+ name: this.get('name'),
103
+ fqdn: this.get('fqdn'),
104
+ host: this.get('host'),
105
+ port: this.get('port'),
106
+ public_key: this.get('public_key')
107
+ });
108
+
109
+ host.save().then(function(host) {
110
+ self.transitionToRoute('host', host);
111
+ // TODO: better error handling here
112
+ }).catch(function(host) {
113
+ window.alert('Error saving host!');
114
+ console.log(host);
115
+ });
116
+ },
117
+ delete: function(host) {
118
+ deleteHost(this, host);
119
+ },
120
+ test: function(host) {
121
+ testConnection(this, host);
122
+ },
123
+ cancel: function(host) {
124
+ host.rollback();
125
+ this.transitionToRoute('host', host);
126
+ }
127
+ }
128
+ });
129
+ })();
@@ -0,0 +1,109 @@
1
+ 'use strict';
2
+
3
+ (function() {
4
+ function deleteJob(self, job) {
5
+ var confirmation = "Are you sure want to delete this job?\n\n";
6
+ confirmation += 'All associated data will be deleted and it will be REMOVED from the host!';
7
+
8
+ if (window.confirm(confirmation)) {
9
+ // TODO: Do the same cascading delete that happens on the backend
10
+ // i.e deleting jobs -> executions -> job_execution_outputs linked
11
+ // to this job
12
+ job.deleteRecord();
13
+
14
+ job.save().then(function() {
15
+ self.transitionToRoute('jobs');
16
+ }, function(response) {
17
+ job.rollback();
18
+ console.log(response);
19
+ window.prompt('Error deleting job, reason:', response.responseJSON.error);
20
+ });
21
+ }
22
+ }
23
+
24
+ Minicron.JobsIndexController = Ember.ObjectController.extend({
25
+ actions: {
26
+ delete: function(job) {
27
+ deleteJob(this, job);
28
+ }
29
+ }
30
+ });
31
+
32
+ Minicron.JobsNewController = Ember.ObjectController.extend({
33
+ save_button: 'Save',
34
+ job_name: null,
35
+ job_command: null,
36
+ actions: {
37
+ save: function() {
38
+ var self = this,
39
+ job = this.store.createRecord('job', {
40
+ name: this.get('job_name'),
41
+ command: this.get('job_command'),
42
+ });
43
+ // Let the user know the job is being saved
44
+ this.set('save_button', 'Saving..');
45
+
46
+ // Look up the host
47
+ this.store.find('host', this.get('id')).then(function(host) {
48
+ // Set the job relationship
49
+ job.set('host', host);
50
+
51
+ job.save().then(function(job) {
52
+ // Reset the save button text
53
+ self.set('save_button', 'Save');
54
+
55
+ self.transitionToRoute('job', job);
56
+ // TODO: better error handling here
57
+ }).catch(function(job) {
58
+ // Reset the save button text
59
+ self.set('save_button', 'Save');
60
+
61
+ window.alert('Error saving job!');
62
+ console.log(job);
63
+ });
64
+ });
65
+ },
66
+ cancel: function() {
67
+ this.transitionToRoute('jobs');
68
+ }
69
+ }
70
+ });
71
+
72
+ Minicron.JobIndexController = Ember.ObjectController.extend({
73
+ actions: {
74
+ delete: function(job) {
75
+ deleteJob(this, job);
76
+ },
77
+ test: function(job) {
78
+ testConnection(this, job);
79
+ }
80
+ }
81
+ });
82
+
83
+ Minicron.JobEditController = Ember.ObjectController.extend({
84
+ actions: {
85
+ save: function() {
86
+ var self = this,
87
+ job = this.store.push('job', {
88
+ id: this.get('id'),
89
+ name: this.get('name')
90
+ });
91
+
92
+ job.save().then(function(job) {
93
+ self.transitionToRoute('job', job);
94
+ // TODO: better error handling here
95
+ }).catch(function(job) {
96
+ window.alert('Error saving job!');
97
+ console.log(job);
98
+ });
99
+ },
100
+ delete: function(job) {
101
+ deleteJob(this, job);
102
+ },
103
+ cancel: function(job) {
104
+ job.rollback();
105
+ this.transitionToRoute('job', job);
106
+ }
107
+ }
108
+ });
109
+ })();