├── public ├── js │ ├── .jshintignore │ ├── noty │ │ ├── layouts │ │ │ ├── inline.js │ │ │ ├── top.js │ │ │ ├── bottom.js │ │ │ ├── topLeft.js │ │ │ ├── topRight.js │ │ │ ├── topCenter.js │ │ │ ├── bottomLeft.js │ │ │ ├── bottomRight.js │ │ │ ├── bottomCenter.js │ │ │ ├── center.js │ │ │ ├── centerLeft.js │ │ │ └── centerRight.js │ │ └── themes │ │ │ └── default.js │ ├── ChartHandler.js │ ├── flot │ │ ├── jquery.flot.symbol.min.js │ │ ├── jquery.flot.threshold.min.js │ │ ├── jquery.flot.crosshair.min.js │ │ ├── jquery.flot.fillbetween.min.js │ │ ├── jquery.flot.resize.min.js │ │ ├── jquery.flot.stack.min.js │ │ ├── jquery.flot.categories.min.js │ │ ├── jquery.flot.image.min.js │ │ ├── jquery.flot.symbol.js │ │ ├── jquery.colorhelpers.min.js │ │ ├── jquery.flot.resize.js │ │ ├── jquery.flot.canvas.min.js │ │ ├── jquery.flot.selection.min.js │ │ ├── jquery.flot.threshold.js │ │ ├── jquery.flot.errorbars.min.js │ │ ├── jquery.flot.crosshair.js │ │ ├── jquery.flot.navigate.min.js │ │ ├── jquery.flot.fillbetween.js │ │ ├── jquery.flot.time.min.js │ │ ├── jquery.flot.categories.js │ │ ├── jquery.colorhelpers.js │ │ ├── jquery.flot.stack.js │ │ ├── jquery.flot.image.js │ │ ├── jquery.flot.canvas.js │ │ └── jquery.flot.pie.min.js │ ├── .jshintrc │ ├── DataModel.js │ ├── RedisHandler.js │ └── jquery.blockUI.js ├── img │ └── close.png ├── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.ttf │ └── fontawesome-webfont.woff ├── templates │ ├── errors │ │ ├── 404.dust │ │ └── not-connected.dust │ ├── newJob.dust │ ├── queueList.dust │ ├── jobList.dust │ ├── index.dust │ └── layouts │ │ └── master.dust └── css │ └── master.css ├── .gitignore ├── config ├── index.js ├── development.json └── production.json ├── index.js ├── lib ├── server.js ├── enforceConnection.js ├── redisConnector.js ├── setupAndMiddleware.js └── updateInfo.js ├── app.js ├── models └── bull.js ├── controllers ├── newjob.js ├── queues.js ├── failed.js ├── pending.js ├── complete.js ├── active.js ├── delayed.js ├── index.js └── jobs.js ├── package.json ├── LICENSE.md └── README.md /public/js/.jshintignore: -------------------------------------------------------------------------------- 1 | lib 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/ 3 | .build/ 4 | *.iml 5 | node_modules/ -------------------------------------------------------------------------------- /public/img/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andris9/Matador/master/public/img/close.png -------------------------------------------------------------------------------- /public/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andris9/Matador/master/public/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /public/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andris9/Matador/master/public/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /public/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andris9/Matador/master/public/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /public/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andris9/Matador/master/public/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = require('./'+(process.env.NODE_ENV ? process.env.NODE_ENV.toLowerCase() : "development")); -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var config = require('./config'), 2 | app = require('./app')(config); 3 | 4 | app.listen(config.port, function() { 5 | console.log("Matador listening on port", config.port, "in", process.env.NODE_ENV, "mode"); 6 | }); -------------------------------------------------------------------------------- /public/templates/errors/404.dust: -------------------------------------------------------------------------------- 1 | {>"layouts/master" /} 2 | 3 | {
Page not found! 5 |There was an error connecting to the redis server specified in the config file.
4 | Please check your config file and verify that your redis server is up and able to be connected to from this IP address.
After you have verified that you can connect to your server, refresh the page.
6 || Name | 16 |Pending | 17 |Stalled | 18 |Delayed | 19 |Active | 20 |Completed | 21 |Failed | 22 |Options | 23 |
|---|---|---|---|---|---|---|---|
| 28 | | 29 | | 30 | | 31 | | 32 | | 33 | | 34 | |
35 |
36 |
40 |
46 |
47 | |
48 |
| Id | 23 |Type | 24 |Status | 25 |Progress | 26 |Delay until | 27 |Manage | 28 |
|---|---|---|---|---|---|
| 33 | | 34 | | 35 | | 36 | | 37 | | 38 | 41 | 44 | 47 | | 48 |
| Id | 26 |Type | 27 |Status | 28 |Manage | 29 |
|---|---|---|---|
| 34 | | 35 | | 36 | | 37 | 40 | 43 | | 44 |
99 |
100 | **Q/A:**
101 |
102 |
103 | *What is a stuck job?*
104 | A stuck job is a job that has no state. It's possible that jobs listed as stuck are just in between states, but if there's ever a job with no state for a long time, the overview page gives you a place where you can delete or revert such jobs back the pending queue. Because stuck jobs are any job without a state, and while jobs are processing they may become stateless, there is no way to mass-manage stuck jobs. This is because it would be really easy to accidentally modify jobs you didn't mean to if a job was being processed while you were mass managing "stuck jobs".
105 |
106 |
107 | **Wrap-up**
108 |
109 |
110 | Have an issue? Feel free to open an issue in the issues page. You can also send me a tweet and I'd be glad to respond!
111 |
112 | If you wanna help, feel free to fork and make pull requests! The general layout and feel of Matador is pretty basic, and I tried to keep my code relatively clean (though, some of it is pretty awful looking right now...sorry about that).
113 |
114 | ***
115 | **License**
116 | ***
117 |
118 | The MIT License (MIT)
119 |
120 | Copyright (c) 2014 Shane King
121 |
122 | Permission is hereby granted, free of charge, to any person obtaining a copy
123 | of this software and associated documentation files (the "Software"), to deal
124 | in the Software without restriction, including without limitation the rights
125 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
126 | copies of the Software, and to permit persons to whom the Software is
127 | furnished to do so, subject to the following conditions:
128 |
129 | The above copyright notice and this permission notice shall be included in
130 | all copies or substantial portions of the Software.
131 |
132 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
133 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
134 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
135 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
136 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
137 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
138 | THE SOFTWARE.
139 |
--------------------------------------------------------------------------------
/public/js/flot/jquery.flot.categories.js:
--------------------------------------------------------------------------------
1 | /* Flot plugin for plotting textual data or categories.
2 |
3 | Copyright (c) 2007-2014 IOLA and Ole Laursen.
4 | Licensed under the MIT license.
5 |
6 | Consider a dataset like [["February", 34], ["March", 20], ...]. This plugin
7 | allows you to plot such a dataset directly.
8 |
9 | To enable it, you must specify mode: "categories" on the axis with the textual
10 | labels, e.g.
11 |
12 | $.plot("#placeholder", data, { xaxis: { mode: "categories" } });
13 |
14 | By default, the labels are ordered as they are met in the data series. If you
15 | need a different ordering, you can specify "categories" on the axis options
16 | and list the categories there:
17 |
18 | xaxis: {
19 | mode: "categories",
20 | categories: ["February", "March", "April"]
21 | }
22 |
23 | If you need to customize the distances between the categories, you can specify
24 | "categories" as an object mapping labels to values
25 |
26 | xaxis: {
27 | mode: "categories",
28 | categories: { "February": 1, "March": 3, "April": 4 }
29 | }
30 |
31 | If you don't specify all categories, the remaining categories will be numbered
32 | from the max value plus 1 (with a spacing of 1 between each).
33 |
34 | Internally, the plugin works by transforming the input data through an auto-
35 | generated mapping where the first category becomes 0, the second 1, etc.
36 | Hence, a point like ["February", 34] becomes [0, 34] internally in Flot (this
37 | is visible in hover and click events that return numbers rather than the
38 | category labels). The plugin also overrides the tick generator to spit out the
39 | categories as ticks instead of the values.
40 |
41 | If you need to map a value back to its label, the mapping is always accessible
42 | as "categories" on the axis object, e.g. plot.getAxes().xaxis.categories.
43 |
44 | */
45 |
46 | (function ($) {
47 | var options = {
48 | xaxis: {
49 | categories: null
50 | },
51 | yaxis: {
52 | categories: null
53 | }
54 | };
55 |
56 | function processRawData(plot, series, data, datapoints) {
57 | // if categories are enabled, we need to disable
58 | // auto-transformation to numbers so the strings are intact
59 | // for later processing
60 |
61 | var xCategories = series.xaxis.options.mode == "categories",
62 | yCategories = series.yaxis.options.mode == "categories";
63 |
64 | if (!(xCategories || yCategories))
65 | return;
66 |
67 | var format = datapoints.format;
68 |
69 | if (!format) {
70 | // FIXME: auto-detection should really not be defined here
71 | var s = series;
72 | format = [];
73 | format.push({ x: true, number: true, required: true });
74 | format.push({ y: true, number: true, required: true });
75 |
76 | if (s.bars.show || (s.lines.show && s.lines.fill)) {
77 | var autoscale = !!((s.bars.show && s.bars.zero) || (s.lines.show && s.lines.zero));
78 | format.push({ y: true, number: true, required: false, defaultValue: 0, autoscale: autoscale });
79 | if (s.bars.horizontal) {
80 | delete format[format.length - 1].y;
81 | format[format.length - 1].x = true;
82 | }
83 | }
84 |
85 | datapoints.format = format;
86 | }
87 |
88 | for (var m = 0; m < format.length; ++m) {
89 | if (format[m].x && xCategories)
90 | format[m].number = false;
91 |
92 | if (format[m].y && yCategories)
93 | format[m].number = false;
94 | }
95 | }
96 |
97 | function getNextIndex(categories) {
98 | var index = -1;
99 |
100 | for (var v in categories)
101 | if (categories[v] > index)
102 | index = categories[v];
103 |
104 | return index + 1;
105 | }
106 |
107 | function categoriesTickGenerator(axis) {
108 | var res = [];
109 | for (var label in axis.categories) {
110 | var v = axis.categories[label];
111 | if (v >= axis.min && v <= axis.max)
112 | res.push([v, label]);
113 | }
114 |
115 | res.sort(function (a, b) { return a[0] - b[0]; });
116 |
117 | return res;
118 | }
119 |
120 | function setupCategoriesForAxis(series, axis, datapoints) {
121 | if (series[axis].options.mode != "categories")
122 | return;
123 |
124 | if (!series[axis].categories) {
125 | // parse options
126 | var c = {}, o = series[axis].options.categories || {};
127 | if ($.isArray(o)) {
128 | for (var i = 0; i < o.length; ++i)
129 | c[o[i]] = i;
130 | }
131 | else {
132 | for (var v in o)
133 | c[v] = o[v];
134 | }
135 |
136 | series[axis].categories = c;
137 | }
138 |
139 | // fix ticks
140 | if (!series[axis].options.ticks)
141 | series[axis].options.ticks = categoriesTickGenerator;
142 |
143 | transformPointsOnAxis(datapoints, axis, series[axis].categories);
144 | }
145 |
146 | function transformPointsOnAxis(datapoints, axis, categories) {
147 | // go through the points, transforming them
148 | var points = datapoints.points,
149 | ps = datapoints.pointsize,
150 | format = datapoints.format,
151 | formatColumn = axis.charAt(0),
152 | index = getNextIndex(categories);
153 |
154 | for (var i = 0; i < points.length; i += ps) {
155 | if (points[i] == null)
156 | continue;
157 |
158 | for (var m = 0; m < ps; ++m) {
159 | var val = points[i + m];
160 |
161 | if (val == null || !format[m][formatColumn])
162 | continue;
163 |
164 | if (!(val in categories)) {
165 | categories[val] = index;
166 | ++index;
167 | }
168 |
169 | points[i + m] = categories[val];
170 | }
171 | }
172 | }
173 |
174 | function processDatapoints(plot, series, datapoints) {
175 | setupCategoriesForAxis(series, "xaxis", datapoints);
176 | setupCategoriesForAxis(series, "yaxis", datapoints);
177 | }
178 |
179 | function init(plot) {
180 | plot.hooks.processRawData.push(processRawData);
181 | plot.hooks.processDatapoints.push(processDatapoints);
182 | }
183 |
184 | $.plot.plugins.push({
185 | init: init,
186 | options: options,
187 | name: 'categories',
188 | version: '1.0'
189 | });
190 | })(jQuery);
191 |
--------------------------------------------------------------------------------
/public/js/flot/jquery.colorhelpers.js:
--------------------------------------------------------------------------------
1 | /* Plugin for jQuery for working with colors.
2 | *
3 | * Version 1.1.
4 | *
5 | * Inspiration from jQuery color animation plugin by John Resig.
6 | *
7 | * Released under the MIT license by Ole Laursen, October 2009.
8 | *
9 | * Examples:
10 | *
11 | * $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString()
12 | * var c = $.color.extract($("#mydiv"), 'background-color');
13 | * console.log(c.r, c.g, c.b, c.a);
14 | * $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)"
15 | *
16 | * Note that .scale() and .add() return the same modified object
17 | * instead of making a new one.
18 | *
19 | * V. 1.1: Fix error handling so e.g. parsing an empty string does
20 | * produce a color rather than just crashing.
21 | */
22 |
23 | (function($) {
24 | $.color = {};
25 |
26 | // construct color object with some convenient chainable helpers
27 | $.color.make = function (r, g, b, a) {
28 | var o = {};
29 | o.r = r || 0;
30 | o.g = g || 0;
31 | o.b = b || 0;
32 | o.a = a != null ? a : 1;
33 |
34 | o.add = function (c, d) {
35 | for (var i = 0; i < c.length; ++i)
36 | o[c.charAt(i)] += d;
37 | return o.normalize();
38 | };
39 |
40 | o.scale = function (c, f) {
41 | for (var i = 0; i < c.length; ++i)
42 | o[c.charAt(i)] *= f;
43 | return o.normalize();
44 | };
45 |
46 | o.toString = function () {
47 | if (o.a >= 1.0) {
48 | return "rgb("+[o.r, o.g, o.b].join(",")+")";
49 | } else {
50 | return "rgba("+[o.r, o.g, o.b, o.a].join(",")+")";
51 | }
52 | };
53 |
54 | o.normalize = function () {
55 | function clamp(min, value, max) {
56 | return value < min ? min: (value > max ? max: value);
57 | }
58 |
59 | o.r = clamp(0, parseInt(o.r), 255);
60 | o.g = clamp(0, parseInt(o.g), 255);
61 | o.b = clamp(0, parseInt(o.b), 255);
62 | o.a = clamp(0, o.a, 1);
63 | return o;
64 | };
65 |
66 | o.clone = function () {
67 | return $.color.make(o.r, o.b, o.g, o.a);
68 | };
69 |
70 | return o.normalize();
71 | }
72 |
73 | // extract CSS color property from element, going up in the DOM
74 | // if it's "transparent"
75 | $.color.extract = function (elem, css) {
76 | var c;
77 |
78 | do {
79 | c = elem.css(css).toLowerCase();
80 | // keep going until we find an element that has color, or
81 | // we hit the body or root (have no parent)
82 | if (c != '' && c != 'transparent')
83 | break;
84 | elem = elem.parent();
85 | } while (elem.length && !$.nodeName(elem.get(0), "body"));
86 |
87 | // catch Safari's way of signalling transparent
88 | if (c == "rgba(0, 0, 0, 0)")
89 | c = "transparent";
90 |
91 | return $.color.parse(c);
92 | }
93 |
94 | // parse CSS color string (like "rgb(10, 32, 43)" or "#fff"),
95 | // returns color object, if parsing failed, you get black (0, 0,
96 | // 0) out
97 | $.color.parse = function (str) {
98 | var res, m = $.color.make;
99 |
100 | // Look for rgb(num,num,num)
101 | if (res = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str))
102 | return m(parseInt(res[1], 10), parseInt(res[2], 10), parseInt(res[3], 10));
103 |
104 | // Look for rgba(num,num,num,num)
105 | if (res = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))
106 | return m(parseInt(res[1], 10), parseInt(res[2], 10), parseInt(res[3], 10), parseFloat(res[4]));
107 |
108 | // Look for rgb(num%,num%,num%)
109 | if (res = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str))
110 | return m(parseFloat(res[1])*2.55, parseFloat(res[2])*2.55, parseFloat(res[3])*2.55);
111 |
112 | // Look for rgba(num%,num%,num%,num)
113 | if (res = /rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))
114 | return m(parseFloat(res[1])*2.55, parseFloat(res[2])*2.55, parseFloat(res[3])*2.55, parseFloat(res[4]));
115 |
116 | // Look for #a0b1c2
117 | if (res = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str))
118 | return m(parseInt(res[1], 16), parseInt(res[2], 16), parseInt(res[3], 16));
119 |
120 | // Look for #fff
121 | if (res = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str))
122 | return m(parseInt(res[1]+res[1], 16), parseInt(res[2]+res[2], 16), parseInt(res[3]+res[3], 16));
123 |
124 | // Otherwise, we're most likely dealing with a named color
125 | var name = $.trim(str).toLowerCase();
126 | if (name == "transparent")
127 | return m(255, 255, 255, 0);
128 | else {
129 | // default to black
130 | res = lookupColors[name] || [0, 0, 0];
131 | return m(res[0], res[1], res[2]);
132 | }
133 | }
134 |
135 | var lookupColors = {
136 | aqua:[0,255,255],
137 | azure:[240,255,255],
138 | beige:[245,245,220],
139 | black:[0,0,0],
140 | blue:[0,0,255],
141 | brown:[165,42,42],
142 | cyan:[0,255,255],
143 | darkblue:[0,0,139],
144 | darkcyan:[0,139,139],
145 | darkgrey:[169,169,169],
146 | darkgreen:[0,100,0],
147 | darkkhaki:[189,183,107],
148 | darkmagenta:[139,0,139],
149 | darkolivegreen:[85,107,47],
150 | darkorange:[255,140,0],
151 | darkorchid:[153,50,204],
152 | darkred:[139,0,0],
153 | darksalmon:[233,150,122],
154 | darkviolet:[148,0,211],
155 | fuchsia:[255,0,255],
156 | gold:[255,215,0],
157 | green:[0,128,0],
158 | indigo:[75,0,130],
159 | khaki:[240,230,140],
160 | lightblue:[173,216,230],
161 | lightcyan:[224,255,255],
162 | lightgreen:[144,238,144],
163 | lightgrey:[211,211,211],
164 | lightpink:[255,182,193],
165 | lightyellow:[255,255,224],
166 | lime:[0,255,0],
167 | magenta:[255,0,255],
168 | maroon:[128,0,0],
169 | navy:[0,0,128],
170 | olive:[128,128,0],
171 | orange:[255,165,0],
172 | pink:[255,192,203],
173 | purple:[128,0,128],
174 | violet:[128,0,128],
175 | red:[255,0,0],
176 | silver:[192,192,192],
177 | white:[255,255,255],
178 | yellow:[255,255,0]
179 | };
180 | })(jQuery);
181 |
--------------------------------------------------------------------------------
/public/js/RedisHandler.js:
--------------------------------------------------------------------------------
1 | var RedisHandler = function(){
2 | var _self = this;
3 |
4 | _self.util = {
5 | notyConfirm: function(message, cb){
6 | var buttons = [
7 | {addClass: 'btn btn-primary', text: 'Ok', onClick: function ($noty) { $noty.close(); cb(); } },
8 | {addClass: 'btn btn-danger', text: 'Cancel', onClick: function ($noty) { $noty.close(); } }
9 | ];
10 | noty({text: message, type: 'alert', layout: 'center', buttons: buttons, modal: true});
11 | },
12 | handleAjaxResponse: function(ajaxResponse){
13 | noty({text: ajaxResponse.message, type: ajaxResponse.success ? 'success' : 'error', timeout: 3500, layout: 'topRight'});
14 | },
15 | blockUI: function(){
16 | $.blockUI({message: 'Job ID: ' + id +
92 | '\nType: ' + type +
93 | '\nStatus: ' + o.status +
94 | '\n\nData: ' + data;
95 |
96 | if (stacktrace){
97 | message = message + '\n\nStack Trace: \n' + stacktrace + '' + '';
98 | } else {
99 | message = message + '';
100 | }
101 |
102 | var displayedNoty = noty({
103 | text: message,
104 | type: 'alert',
105 | layout: 'center',
106 | buttons: buttons,
107 | modal: true,
108 | template: ''
109 | });
110 |
111 | displayedNoty.$message.parents('li').width("50vw");
112 | displayedNoty.$message.parents('.i-am-new').css('left', '25vw');
113 | }
114 | }).always(function(){
115 | $.unblockUI();
116 | });
117 | },
118 | pendingByStatus: function(status){
119 | status = status.toLowerCase();
120 | var statusDisplay = status;
121 | if(status === "pending"){
122 | status = "wait";
123 | statusDisplay = "pending";
124 | }
125 | _self.util.notyConfirm("Are you sure you want to make all jobs with the status "+status+" pending? This will put all jobs with this status in the queue to be run again.", function(){
126 | _self.util.blockUI();
127 | $.getJSON(window.basepath + "/api/jobs/pending/status/"+status).done(function(response){
128 | if(status !== statusDisplay && response.success){
129 | response.message = response.message.replace(status, statusDisplay);
130 | }
131 | _self.util.handleAjaxResponse(response);
132 | dataModel.fn.refreshViewModel(true);
133 | }).always(function(){
134 | $.unblockUI();
135 | });
136 | });
137 | },
138 | createJob: function(){
139 | var data = $("#newjob").serializeArray();
140 | var url = window.basepath + "/api/jobs/create";
141 | $.ajax({
142 | type:'POST',
143 | url:url,
144 | data: data,
145 | success: function(){
146 | // clear form
147 | $('#newjob').trigger("reset");
148 | $('.alert').html('').addClass('hidden');
149 | },
150 | error: function(response){
151 | // display error
152 | $('.alert').html('Error! ' + response.responseText).removeClass('hidden');
153 | }
154 | });
155 | }
156 | };
157 |
158 | return _self;
159 | };
160 |
161 | var redisHandler = new RedisHandler();
162 |
--------------------------------------------------------------------------------
/public/js/flot/jquery.flot.stack.js:
--------------------------------------------------------------------------------
1 | /* Flot plugin for stacking data sets rather than overlyaing them.
2 |
3 | Copyright (c) 2007-2014 IOLA and Ole Laursen.
4 | Licensed under the MIT license.
5 |
6 | The plugin assumes the data is sorted on x (or y if stacking horizontally).
7 | For line charts, it is assumed that if a line has an undefined gap (from a
8 | null point), then the line above it should have the same gap - insert zeros
9 | instead of "null" if you want another behaviour. This also holds for the start
10 | and end of the chart. Note that stacking a mix of positive and negative values
11 | in most instances doesn't make sense (so it looks weird).
12 |
13 | Two or more series are stacked when their "stack" attribute is set to the same
14 | key (which can be any number or string or just "true"). To specify the default
15 | stack, you can set the stack option like this:
16 |
17 | series: {
18 | stack: null/false, true, or a key (number/string)
19 | }
20 |
21 | You can also specify it for a single series, like this:
22 |
23 | $.plot( $("#placeholder"), [{
24 | data: [ ... ],
25 | stack: true
26 | }])
27 |
28 | The stacking order is determined by the order of the data series in the array
29 | (later series end up on top of the previous).
30 |
31 | Internally, the plugin modifies the datapoints in each series, adding an
32 | offset to the y value. For line series, extra data points are inserted through
33 | interpolation. If there's a second y value, it's also adjusted (e.g for bar
34 | charts or filled areas).
35 |
36 | */
37 |
38 | (function ($) {
39 | var options = {
40 | series: { stack: null } // or number/string
41 | };
42 |
43 | function init(plot) {
44 | function findMatchingSeries(s, allseries) {
45 | var res = null;
46 | for (var i = 0; i < allseries.length; ++i) {
47 | if (s == allseries[i])
48 | break;
49 |
50 | if (allseries[i].stack == s.stack)
51 | res = allseries[i];
52 | }
53 |
54 | return res;
55 | }
56 |
57 | function stackData(plot, s, datapoints) {
58 | if (s.stack == null || s.stack === false)
59 | return;
60 |
61 | var other = findMatchingSeries(s, plot.getData());
62 | if (!other)
63 | return;
64 |
65 | var ps = datapoints.pointsize,
66 | points = datapoints.points,
67 | otherps = other.datapoints.pointsize,
68 | otherpoints = other.datapoints.points,
69 | newpoints = [],
70 | px, py, intery, qx, qy, bottom,
71 | withlines = s.lines.show,
72 | horizontal = s.bars.horizontal,
73 | withbottom = ps > 2 && (horizontal ? datapoints.format[2].x : datapoints.format[2].y),
74 | withsteps = withlines && s.lines.steps,
75 | fromgap = true,
76 | keyOffset = horizontal ? 1 : 0,
77 | accumulateOffset = horizontal ? 0 : 1,
78 | i = 0, j = 0, l, m;
79 |
80 | while (true) {
81 | if (i >= points.length)
82 | break;
83 |
84 | l = newpoints.length;
85 |
86 | if (points[i] == null) {
87 | // copy gaps
88 | for (m = 0; m < ps; ++m)
89 | newpoints.push(points[i + m]);
90 | i += ps;
91 | }
92 | else if (j >= otherpoints.length) {
93 | // for lines, we can't use the rest of the points
94 | if (!withlines) {
95 | for (m = 0; m < ps; ++m)
96 | newpoints.push(points[i + m]);
97 | }
98 | i += ps;
99 | }
100 | else if (otherpoints[j] == null) {
101 | // oops, got a gap
102 | for (m = 0; m < ps; ++m)
103 | newpoints.push(null);
104 | fromgap = true;
105 | j += otherps;
106 | }
107 | else {
108 | // cases where we actually got two points
109 | px = points[i + keyOffset];
110 | py = points[i + accumulateOffset];
111 | qx = otherpoints[j + keyOffset];
112 | qy = otherpoints[j + accumulateOffset];
113 | bottom = 0;
114 |
115 | if (px == qx) {
116 | for (m = 0; m < ps; ++m)
117 | newpoints.push(points[i + m]);
118 |
119 | newpoints[l + accumulateOffset] += qy;
120 | bottom = qy;
121 |
122 | i += ps;
123 | j += otherps;
124 | }
125 | else if (px > qx) {
126 | // we got past point below, might need to
127 | // insert interpolated extra point
128 | if (withlines && i > 0 && points[i - ps] != null) {
129 | intery = py + (points[i - ps + accumulateOffset] - py) * (qx - px) / (points[i - ps + keyOffset] - px);
130 | newpoints.push(qx);
131 | newpoints.push(intery + qy);
132 | for (m = 2; m < ps; ++m)
133 | newpoints.push(points[i + m]);
134 | bottom = qy;
135 | }
136 |
137 | j += otherps;
138 | }
139 | else { // px < qx
140 | if (fromgap && withlines) {
141 | // if we come from a gap, we just skip this point
142 | i += ps;
143 | continue;
144 | }
145 |
146 | for (m = 0; m < ps; ++m)
147 | newpoints.push(points[i + m]);
148 |
149 | // we might be able to interpolate a point below,
150 | // this can give us a better y
151 | if (withlines && j > 0 && otherpoints[j - otherps] != null)
152 | bottom = qy + (otherpoints[j - otherps + accumulateOffset] - qy) * (px - qx) / (otherpoints[j - otherps + keyOffset] - qx);
153 |
154 | newpoints[l + accumulateOffset] += bottom;
155 |
156 | i += ps;
157 | }
158 |
159 | fromgap = false;
160 |
161 | if (l != newpoints.length && withbottom)
162 | newpoints[l + 2] += bottom;
163 | }
164 |
165 | // maintain the line steps invariant
166 | if (withsteps && l != newpoints.length && l > 0
167 | && newpoints[l] != null
168 | && newpoints[l] != newpoints[l - ps]
169 | && newpoints[l + 1] != newpoints[l - ps + 1]) {
170 | for (m = 0; m < ps; ++m)
171 | newpoints[l + ps + m] = newpoints[l + m];
172 | newpoints[l + 1] = newpoints[l - ps + 1];
173 | }
174 | }
175 |
176 | datapoints.points = newpoints;
177 | }
178 |
179 | plot.hooks.processDatapoints.push(stackData);
180 | }
181 |
182 | $.plot.plugins.push({
183 | init: init,
184 | options: options,
185 | name: 'stack',
186 | version: '1.2'
187 | });
188 | })(jQuery);
189 |
--------------------------------------------------------------------------------
/public/js/flot/jquery.flot.image.js:
--------------------------------------------------------------------------------
1 | /* Flot plugin for plotting images.
2 |
3 | Copyright (c) 2007-2014 IOLA and Ole Laursen.
4 | Licensed under the MIT license.
5 |
6 | The data syntax is [ [ image, x1, y1, x2, y2 ], ... ] where (x1, y1) and
7 | (x2, y2) are where you intend the two opposite corners of the image to end up
8 | in the plot. Image must be a fully loaded Javascript image (you can make one
9 | with new Image()). If the image is not complete, it's skipped when plotting.
10 |
11 | There are two helpers included for retrieving images. The easiest work the way
12 | that you put in URLs instead of images in the data, like this:
13 |
14 | [ "myimage.png", 0, 0, 10, 10 ]
15 |
16 | Then call $.plot.image.loadData( data, options, callback ) where data and
17 | options are the same as you pass in to $.plot. This loads the images, replaces
18 | the URLs in the data with the corresponding images and calls "callback" when
19 | all images are loaded (or failed loading). In the callback, you can then call
20 | $.plot with the data set. See the included example.
21 |
22 | A more low-level helper, $.plot.image.load(urls, callback) is also included.
23 | Given a list of URLs, it calls callback with an object mapping from URL to
24 | Image object when all images are loaded or have failed loading.
25 |
26 | The plugin supports these options:
27 |
28 | series: {
29 | images: {
30 | show: boolean
31 | anchor: "corner" or "center"
32 | alpha: [ 0, 1 ]
33 | }
34 | }
35 |
36 | They can be specified for a specific series:
37 |
38 | $.plot( $("#placeholder"), [{
39 | data: [ ... ],
40 | images: { ... }
41 | ])
42 |
43 | Note that because the data format is different from usual data points, you
44 | can't use images with anything else in a specific data series.
45 |
46 | Setting "anchor" to "center" causes the pixels in the image to be anchored at
47 | the corner pixel centers inside of at the pixel corners, effectively letting
48 | half a pixel stick out to each side in the plot.
49 |
50 | A possible future direction could be support for tiling for large images (like
51 | Google Maps).
52 |
53 | */
54 |
55 | (function ($) {
56 | var options = {
57 | series: {
58 | images: {
59 | show: false,
60 | alpha: 1,
61 | anchor: "corner" // or "center"
62 | }
63 | }
64 | };
65 |
66 | $.plot.image = {};
67 |
68 | $.plot.image.loadDataImages = function (series, options, callback) {
69 | var urls = [], points = [];
70 |
71 | var defaultShow = options.series.images.show;
72 |
73 | $.each(series, function (i, s) {
74 | if (!(defaultShow || s.images.show))
75 | return;
76 |
77 | if (s.data)
78 | s = s.data;
79 |
80 | $.each(s, function (i, p) {
81 | if (typeof p[0] == "string") {
82 | urls.push(p[0]);
83 | points.push(p);
84 | }
85 | });
86 | });
87 |
88 | $.plot.image.load(urls, function (loadedImages) {
89 | $.each(points, function (i, p) {
90 | var url = p[0];
91 | if (loadedImages[url])
92 | p[0] = loadedImages[url];
93 | });
94 |
95 | callback();
96 | });
97 | }
98 |
99 | $.plot.image.load = function (urls, callback) {
100 | var missing = urls.length, loaded = {};
101 | if (missing == 0)
102 | callback({});
103 |
104 | $.each(urls, function (i, url) {
105 | var handler = function () {
106 | --missing;
107 |
108 | loaded[url] = this;
109 |
110 | if (missing == 0)
111 | callback(loaded);
112 | };
113 |
114 | $('