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.
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
+ })();