vines-services 0.1.0 → 0.1.1
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.
- data/README +5 -5
- data/Rakefile +13 -11
- data/bin/vines-services +0 -1
- data/lib/vines/services/command/init.rb +156 -139
- data/lib/vines/services/controller/messages_controller.rb +8 -3
- data/lib/vines/services/controller/users_controller.rb +11 -11
- data/lib/vines/services/indexer.rb +1 -1
- data/lib/vines/services/priority_queue.rb +12 -12
- data/lib/vines/services/roster.rb +85 -46
- data/lib/vines/services/storage/couchdb/service.rb +1 -1
- data/lib/vines/services/storage/couchdb/system.rb +1 -1
- data/lib/vines/services/storage/couchdb/upload.rb +1 -1
- data/lib/vines/services/storage/couchdb/user.rb +13 -3
- data/lib/vines/services/storage/couchdb.rb +8 -12
- data/lib/vines/services/version.rb +1 -1
- data/test/priority_queue_test.rb +1 -1
- data/web/coffeescripts/api.coffee +6 -0
- data/web/coffeescripts/files.coffee +65 -39
- data/web/coffeescripts/services.coffee +5 -2
- data/web/coffeescripts/setup.coffee +35 -12
- data/web/coffeescripts/systems.coffee +115 -228
- data/web/javascripts/api.js +69 -0
- data/web/javascripts/app.js +2 -0
- data/web/javascripts/commands.js +28 -0
- data/web/javascripts/files.js +424 -0
- data/web/javascripts/init.js +27 -0
- data/web/javascripts/services.js +404 -0
- data/web/javascripts/setup.js +485 -0
- data/web/javascripts/systems.js +342 -0
- data/web/stylesheets/app.css +714 -0
- data/web/stylesheets/services.css +4 -14
- data/web/stylesheets/setup.css +1 -2
- data/web/stylesheets/systems.css +124 -108
- metadata +95 -92
@@ -0,0 +1,404 @@
|
|
1
|
+
var ServicesPage;
|
2
|
+
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
|
3
|
+
ServicesPage = (function() {
|
4
|
+
var ATTRS, MEMBERS, SERVICES, SYSTEMS, USERS;
|
5
|
+
SERVICES = 'http://getvines.com/protocol/services';
|
6
|
+
MEMBERS = 'http://getvines.com/protocol/services/members';
|
7
|
+
SYSTEMS = 'http://getvines.com/protocol/systems';
|
8
|
+
ATTRS = 'http://getvines.com/protocol/systems/attributes';
|
9
|
+
USERS = 'http://getvines.com/protocol/users';
|
10
|
+
function ServicesPage(session) {
|
11
|
+
this.session = session;
|
12
|
+
this.api = new Api(this.session);
|
13
|
+
this.selectedService = null;
|
14
|
+
this.validateTimeout = null;
|
15
|
+
this.layout = null;
|
16
|
+
this.users = [];
|
17
|
+
}
|
18
|
+
ServicesPage.prototype.deleteService = function(event) {
|
19
|
+
var selected;
|
20
|
+
this.drawBlankSlate();
|
21
|
+
this.toggleForm('#remove-contact-form');
|
22
|
+
selected = $("#services li[data-id='" + this.selectedService.id + "']");
|
23
|
+
this.api.remove(SERVICES, this.selectedService.id, __bind(function(result) {
|
24
|
+
return selected.fadeOut(200, function() {
|
25
|
+
selected.remove();
|
26
|
+
return this.selectedService = null;
|
27
|
+
});
|
28
|
+
}, this));
|
29
|
+
return false;
|
30
|
+
};
|
31
|
+
ServicesPage.prototype.icon = function(member) {
|
32
|
+
var icon, icons;
|
33
|
+
icons = {
|
34
|
+
darwin: 'mac.png',
|
35
|
+
linux: 'linux.png',
|
36
|
+
windows: 'windows.png'
|
37
|
+
};
|
38
|
+
icon = icons[member.os] || 'run.png';
|
39
|
+
return "images/" + icon;
|
40
|
+
};
|
41
|
+
ServicesPage.prototype.drawMember = function(member) {
|
42
|
+
var node;
|
43
|
+
if (!this.editorVisible()) {
|
44
|
+
return;
|
45
|
+
}
|
46
|
+
node = $("<li>\n <span class=\"icon\"><img src=\"" + (this.icon(member)) + "\"/></span>\n <span class=\"text\"></span>\n</li>").appendTo('#members');
|
47
|
+
return $('.text', node).text(member.name);
|
48
|
+
};
|
49
|
+
ServicesPage.prototype.operators = function() {
|
50
|
+
var node, operator, _i, _len, _ref, _results;
|
51
|
+
_ref = ['like', 'not like', 'starts with', 'ends with', 'is', 'is not', '>', '>=', '<', '<=', 'and', 'or'];
|
52
|
+
_results = [];
|
53
|
+
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
54
|
+
operator = _ref[_i];
|
55
|
+
node = $("<li data-selector=\"" + operator + "\">\n " + operator + "\n</li>").appendTo('#operators');
|
56
|
+
_results.push(node.click(__bind(function(event) {
|
57
|
+
var name;
|
58
|
+
$('#syntax').focus();
|
59
|
+
name = $(event.currentTarget).attr('data-selector');
|
60
|
+
$('#syntax').val($('#syntax').val() + (" " + name + " "));
|
61
|
+
return this.validateIn();
|
62
|
+
}, this)));
|
63
|
+
}
|
64
|
+
return _results;
|
65
|
+
};
|
66
|
+
ServicesPage.prototype.selectService = function(node) {
|
67
|
+
var id, name;
|
68
|
+
id = $(node).attr('data-id');
|
69
|
+
name = $(node).attr('data-name');
|
70
|
+
$('#services li').removeClass('selected');
|
71
|
+
$(node).addClass('selected');
|
72
|
+
$('#remove-service-msg').html("Are you sure you want to remove the " + ("<strong>" + name + "</strong> service?"));
|
73
|
+
$('#remove-service-form .buttons').fadeIn(200);
|
74
|
+
return this.api.get(SERVICES, {
|
75
|
+
id: id
|
76
|
+
}, __bind(function(result) {
|
77
|
+
this.selectedService = result;
|
78
|
+
return this.drawEditor(result);
|
79
|
+
}, this));
|
80
|
+
};
|
81
|
+
ServicesPage.prototype.serviceNode = function(service) {
|
82
|
+
var label, node;
|
83
|
+
label = service.size === 1 ? 'system' : 'systems';
|
84
|
+
node = $("<li data-id=\"" + service.id + "\" data-name=\"\" data-size=\"" + service.size + "\">\n <span class=\"text\">" + service.name + "</span>\n <span class=\"count\">" + service.size + " " + label + "</span>\n</li>").appendTo('#services');
|
85
|
+
node.attr('data-name', service.name);
|
86
|
+
$('.text', node).text(service.name);
|
87
|
+
node.click(__bind(function(event) {
|
88
|
+
return this.selectService(event.currentTarget);
|
89
|
+
}, this));
|
90
|
+
return node;
|
91
|
+
};
|
92
|
+
ServicesPage.prototype.findServices = function() {
|
93
|
+
return this.api.get(SERVICES, {}, __bind(function(result) {
|
94
|
+
var row, _i, _len, _ref, _results;
|
95
|
+
_ref = result.rows;
|
96
|
+
_results = [];
|
97
|
+
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
98
|
+
row = _ref[_i];
|
99
|
+
_results.push(this.serviceNode(row));
|
100
|
+
}
|
101
|
+
return _results;
|
102
|
+
}, this));
|
103
|
+
};
|
104
|
+
ServicesPage.prototype.findMembers = function(id) {
|
105
|
+
return this.api.get(MEMBERS, {
|
106
|
+
id: id
|
107
|
+
}, __bind(function(result) {
|
108
|
+
var row, _i, _len, _ref, _results;
|
109
|
+
_ref = result.rows;
|
110
|
+
_results = [];
|
111
|
+
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
112
|
+
row = _ref[_i];
|
113
|
+
_results.push(this.drawMember(row));
|
114
|
+
}
|
115
|
+
return _results;
|
116
|
+
}, this));
|
117
|
+
};
|
118
|
+
ServicesPage.prototype.findUsers = function(syntax) {
|
119
|
+
return this.api.get(USERS, {}, __bind(function(result) {
|
120
|
+
var row;
|
121
|
+
this.users = (function() {
|
122
|
+
var _i, _len, _ref, _results;
|
123
|
+
_ref = result.rows;
|
124
|
+
_results = [];
|
125
|
+
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
126
|
+
row = _ref[_i];
|
127
|
+
if (!row.system) {
|
128
|
+
_results.push(row);
|
129
|
+
}
|
130
|
+
}
|
131
|
+
return _results;
|
132
|
+
})();
|
133
|
+
return this.drawUsers();
|
134
|
+
}, this));
|
135
|
+
};
|
136
|
+
ServicesPage.prototype.attributeNode = function(attribute) {
|
137
|
+
var node;
|
138
|
+
node = $('<li data-name=""></li>').appendTo('#attributes');
|
139
|
+
node.text(attribute);
|
140
|
+
node.attr('data-name', attribute);
|
141
|
+
return node.click(__bind(function(event) {
|
142
|
+
var name;
|
143
|
+
$('#syntax').focus();
|
144
|
+
name = $(event.currentTarget).attr('data-name');
|
145
|
+
$('#syntax').val($('#syntax').val() + (" " + name + " "));
|
146
|
+
return this.validateIn();
|
147
|
+
}, this));
|
148
|
+
};
|
149
|
+
ServicesPage.prototype.findAttributes = function(syntax) {
|
150
|
+
return this.api.get(ATTRS, {}, __bind(function(result) {
|
151
|
+
var row, _i, _len, _ref, _results;
|
152
|
+
_ref = result.rows;
|
153
|
+
_results = [];
|
154
|
+
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
155
|
+
row = _ref[_i];
|
156
|
+
_results.push(this.attributeNode(row));
|
157
|
+
}
|
158
|
+
return _results;
|
159
|
+
}, this));
|
160
|
+
};
|
161
|
+
ServicesPage.prototype.validateIn = function(millis) {
|
162
|
+
clearTimeout(this.validateTimeout);
|
163
|
+
return this.validateTimeout = setTimeout((__bind(function() {
|
164
|
+
return this.validate();
|
165
|
+
}, this)), millis || 500);
|
166
|
+
};
|
167
|
+
ServicesPage.prototype.validate = function() {
|
168
|
+
var code, prev;
|
169
|
+
$('#syntax-status').text('');
|
170
|
+
prev = $('#syntax').data('prev');
|
171
|
+
code = $.trim($('#syntax').val());
|
172
|
+
$('#syntax').data('prev', code);
|
173
|
+
if (!(code && code !== prev)) {
|
174
|
+
return;
|
175
|
+
}
|
176
|
+
$('#syntax-status').text('Searching . . .');
|
177
|
+
$('#members').empty();
|
178
|
+
return this.api.get2(MEMBERS, code, __bind(function(result) {
|
179
|
+
var row, _i, _len, _ref, _results;
|
180
|
+
if (result.ok) {
|
181
|
+
$('#syntax-status').text('');
|
182
|
+
_ref = result.rows;
|
183
|
+
_results = [];
|
184
|
+
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
185
|
+
row = _ref[_i];
|
186
|
+
_results.push(this.drawMember(row));
|
187
|
+
}
|
188
|
+
return _results;
|
189
|
+
} else {
|
190
|
+
return $('#syntax-status').text(result.error);
|
191
|
+
}
|
192
|
+
}, this));
|
193
|
+
};
|
194
|
+
ServicesPage.prototype.validateForm = function() {
|
195
|
+
var name, valid;
|
196
|
+
$('#name-error').empty();
|
197
|
+
valid = true;
|
198
|
+
name = $.trim($('#name').val());
|
199
|
+
if (name === '') {
|
200
|
+
$('#name-error').text('Name is required.');
|
201
|
+
valid = false;
|
202
|
+
}
|
203
|
+
return valid;
|
204
|
+
};
|
205
|
+
ServicesPage.prototype.save = function() {
|
206
|
+
var accounts, service, u, users;
|
207
|
+
if (!this.validateForm()) {
|
208
|
+
return;
|
209
|
+
}
|
210
|
+
users = $('#users :checked').map(function() {
|
211
|
+
return $(this).val();
|
212
|
+
}).get();
|
213
|
+
accounts = $('#unix-users').val().split(',');
|
214
|
+
accounts = (function() {
|
215
|
+
var _i, _len, _results;
|
216
|
+
_results = [];
|
217
|
+
for (_i = 0, _len = accounts.length; _i < _len; _i++) {
|
218
|
+
u = accounts[_i];
|
219
|
+
if ($.trim(u).length > 0) {
|
220
|
+
_results.push($.trim(u));
|
221
|
+
}
|
222
|
+
}
|
223
|
+
return _results;
|
224
|
+
})();
|
225
|
+
service = {
|
226
|
+
name: $('#name').val(),
|
227
|
+
code: $('#syntax').val(),
|
228
|
+
accounts: accounts,
|
229
|
+
users: users
|
230
|
+
};
|
231
|
+
if ($('#id').val().length > 0) {
|
232
|
+
service['id'] = $('#id').val();
|
233
|
+
}
|
234
|
+
this.api.save(SERVICES, service, __bind(function(result) {
|
235
|
+
var node;
|
236
|
+
new Notification('Service saved successfully');
|
237
|
+
result.size = $('#members').length;
|
238
|
+
$('#id').val(result.id);
|
239
|
+
node = $("#services li[data-id='" + result.id + "']");
|
240
|
+
if (node.length === 0) {
|
241
|
+
node = this.serviceNode(result);
|
242
|
+
return this.selectService(node);
|
243
|
+
} else {
|
244
|
+
return $('.text', node).text(result.name);
|
245
|
+
}
|
246
|
+
}, this));
|
247
|
+
return false;
|
248
|
+
};
|
249
|
+
ServicesPage.prototype.drawBlankSlate = function() {
|
250
|
+
$('#beta').empty();
|
251
|
+
$("<form id=\"blank-slate\">\n <p>\n Services are dynamically updated groups of systems based on\n criteria you define. Send a command to the service and it runs\n on every system in the group.\n </p>\n <input type=\"submit\" id=\"blank-slate-add\" value=\"Add Service\"/>\n</form>").appendTo('#beta');
|
252
|
+
if (!this.api.user.permissions.services) {
|
253
|
+
$('#blank-slate-add').remove();
|
254
|
+
}
|
255
|
+
return $('#blank-slate').submit(__bind(function() {
|
256
|
+
this.drawEditor();
|
257
|
+
return false;
|
258
|
+
}, this));
|
259
|
+
};
|
260
|
+
ServicesPage.prototype.draw = function() {
|
261
|
+
var fn;
|
262
|
+
if (!this.session.connected()) {
|
263
|
+
window.location.hash = '';
|
264
|
+
return;
|
265
|
+
}
|
266
|
+
$('body').attr('id', 'services-page');
|
267
|
+
$('#container').hide().empty();
|
268
|
+
$("<div id=\"alpha\" class=\"sidebar column y-fill\">\n <h2>Services <div id=\"search-services-icon\"></div></h2>\n <div id=\"search-services-form\"></div>\n <ul id=\"services\" class=\"selectable scroll y-fill\"></ul>\n <div id=\"alpha-controls\" class=\"controls\">\n <div id=\"add-service\"></div>\n <div id=\"remove-service\"></div>\n </div>\n <form id=\"remove-service-form\" class=\"overlay\" style=\"display:none;\">\n <h2>Remove Service</h2>\n <p id=\"remove-service-msg\">Select a service in the list above to remove.</p>\n <fieldset class=\"buttons\" style=\"display:none;\">\n <input id=\"remove-service-cancel\" type=\"button\" value=\"Cancel\"/>\n <input id=\"remove-service-ok\" type=\"submit\" value=\"Remove\"/>\n </fieldset>\n </form>\n</div>\n<div id=\"beta\" class=\"primary column x-fill y-fill\"></div>\n<div id=\"charlie\" class=\"sidebar column y-fill\">\n <h2>Operators</h2>\n <ul id=\"operators\"></ul>\n <h2>Attributes <div id=\"search-attributes-icon\"></div></h2>\n <div id=\"search-attributes-form\"></div>\n <ul id=\"attributes\" class=\"y-fill scroll\"></ul>\n</div>").appendTo('#container');
|
269
|
+
new Button('#add-service', ICONS.plus);
|
270
|
+
new Button('#remove-service', ICONS.minus);
|
271
|
+
if (!this.api.user.permissions.services) {
|
272
|
+
$('#alpha-controls div').remove();
|
273
|
+
}
|
274
|
+
this.drawBlankSlate();
|
275
|
+
$('#add-service').click(__bind(function() {
|
276
|
+
return this.drawEditor();
|
277
|
+
}, this));
|
278
|
+
$('#remove-service').click(__bind(function() {
|
279
|
+
return this.toggleForm('#remove-service-form');
|
280
|
+
}, this));
|
281
|
+
$('#remove-service-cancel').click(__bind(function() {
|
282
|
+
return this.toggleForm('#remove-service-form');
|
283
|
+
}, this));
|
284
|
+
$('#remove-service-form').submit(__bind(function() {
|
285
|
+
return this.deleteService();
|
286
|
+
}, this));
|
287
|
+
this.operators();
|
288
|
+
this.findServices();
|
289
|
+
this.findAttributes();
|
290
|
+
this.findUsers();
|
291
|
+
$('#container').show();
|
292
|
+
this.layout = this.resize();
|
293
|
+
fn = __bind(function() {
|
294
|
+
this.layout.resize();
|
295
|
+
return this.layout.resize();
|
296
|
+
}, this);
|
297
|
+
new Filter({
|
298
|
+
list: '#services',
|
299
|
+
icon: '#search-services-icon',
|
300
|
+
form: '#search-services-form',
|
301
|
+
attrs: ['data-name'],
|
302
|
+
open: fn,
|
303
|
+
close: fn
|
304
|
+
});
|
305
|
+
return new Filter({
|
306
|
+
list: '#attributes',
|
307
|
+
icon: '#search-attributes-icon',
|
308
|
+
form: '#search-attributes-form',
|
309
|
+
attrs: ['data-name'],
|
310
|
+
open: fn,
|
311
|
+
close: fn
|
312
|
+
});
|
313
|
+
};
|
314
|
+
ServicesPage.prototype.drawEditor = function(service) {
|
315
|
+
if (!this.pageVisible()) {
|
316
|
+
return;
|
317
|
+
}
|
318
|
+
if (!service) {
|
319
|
+
this.selectedService = null;
|
320
|
+
$('#services li').removeClass('selected');
|
321
|
+
}
|
322
|
+
$('#beta').empty();
|
323
|
+
$("<form id=\"editor-form\" class=\"sections y-fill scroll\">\n <input id=\"id\" type=\"hidden\"/>\n <div>\n <section>\n <h2>Service</h2>\n <fieldset>\n <label for=\"name\">Name</label>\n <input id=\"name\" type=\"text\"/>\n <p id=\"name-error\" class=\"error\"></p>\n <label for=\"syntax\">Criteria</label>\n <textarea id=\"syntax\" placeholder=\"fqdn starts with 'www.' and platform is 'mac_os_x'\"></textarea>\n <p id=\"syntax-status\"></p>\n </fieldset>\n </section>\n <section>\n <h2>Members</h2>\n <fieldset id=\"service-preview\">\n <ul id=\"members\" class=\"scroll\"></ul>\n </fieldset>\n </section>\n <section>\n <h2>Permissions</h2>\n <fieldset>\n <label>Users</label>\n <ul id=\"users\" class=\"scroll\"></ul>\n <label for=\"unix-users\">Unix Accounts</label>\n <input id=\"unix-users\" type=\"text\"/>\n </fieldset>\n </section>\n </div>\n</form>\n<form id=\"editor-buttons\">\n <input id=\"save\" type=\"submit\" value=\"Save\"/>\n</form>").appendTo('#beta');
|
324
|
+
if (service) {
|
325
|
+
this.findMembers(service.id);
|
326
|
+
$('#id').val(service.id);
|
327
|
+
$('#name').val(service.name);
|
328
|
+
$('#syntax').val(service.code);
|
329
|
+
$('#unix-users').val(service.accounts.join(', '));
|
330
|
+
}
|
331
|
+
if (this.users.length > 0) {
|
332
|
+
this.drawUsers();
|
333
|
+
}
|
334
|
+
this.layout.resize();
|
335
|
+
$('#name').focus();
|
336
|
+
$('#syntax').change(__bind(function() {
|
337
|
+
return this.validateIn();
|
338
|
+
}, this));
|
339
|
+
$('#syntax').keyup(__bind(function() {
|
340
|
+
return this.validateIn();
|
341
|
+
}, this));
|
342
|
+
$('#editor-form').submit(__bind(function() {
|
343
|
+
return this.save();
|
344
|
+
}, this));
|
345
|
+
return $('#editor-buttons').submit(__bind(function() {
|
346
|
+
return this.save();
|
347
|
+
}, this));
|
348
|
+
};
|
349
|
+
ServicesPage.prototype.drawUsers = function() {
|
350
|
+
var node, user, _i, _len, _ref;
|
351
|
+
if (!this.editorVisible()) {
|
352
|
+
return;
|
353
|
+
}
|
354
|
+
$('#users').empty();
|
355
|
+
_ref = this.users;
|
356
|
+
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
357
|
+
user = _ref[_i];
|
358
|
+
node = $("<li>\n <input id='user-" + user.jid + "' type='checkbox' value='" + user.jid + "'/>\n <label for='user-" + user.jid + "'>" + user.jid + "</label>\n</li>").appendTo('#users');
|
359
|
+
if (user.jid === this.session.bareJid()) {
|
360
|
+
$('input', node).prop('checked', true);
|
361
|
+
}
|
362
|
+
}
|
363
|
+
if (this.selectedService) {
|
364
|
+
return $('#users input[type="checkbox"]').val(this.selectedService.users);
|
365
|
+
}
|
366
|
+
};
|
367
|
+
ServicesPage.prototype.toggleForm = function(form, fn) {
|
368
|
+
form = $(form);
|
369
|
+
$('form.overlay').each(function() {
|
370
|
+
if (this.id !== form.attr('id')) {
|
371
|
+
return $(this).hide();
|
372
|
+
}
|
373
|
+
});
|
374
|
+
if (form.is(':hidden')) {
|
375
|
+
if (fn) {
|
376
|
+
fn();
|
377
|
+
}
|
378
|
+
return form.fadeIn(100);
|
379
|
+
} else {
|
380
|
+
return form.fadeOut(100, function() {
|
381
|
+
form[0].reset();
|
382
|
+
if (fn) {
|
383
|
+
return fn();
|
384
|
+
}
|
385
|
+
});
|
386
|
+
}
|
387
|
+
};
|
388
|
+
ServicesPage.prototype.pageVisible = function() {
|
389
|
+
return $('#services-page').length > 0;
|
390
|
+
};
|
391
|
+
ServicesPage.prototype.editorVisible = function() {
|
392
|
+
return $('#services-page #editor-form').length > 0;
|
393
|
+
};
|
394
|
+
ServicesPage.prototype.resize = function() {
|
395
|
+
var a, b, c;
|
396
|
+
a = $('#alpha');
|
397
|
+
b = $('#beta');
|
398
|
+
c = $('#charlie');
|
399
|
+
return new Layout(function() {
|
400
|
+
return c.css('left', a.width() + b.width());
|
401
|
+
});
|
402
|
+
};
|
403
|
+
return ServicesPage;
|
404
|
+
})();
|