vines-services 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +19 -0
- data/README +40 -0
- data/Rakefile +130 -0
- data/bin/vines-services +95 -0
- data/conf/config.rb +25 -0
- data/lib/vines/services/command/init.rb +209 -0
- data/lib/vines/services/command/restart.rb +14 -0
- data/lib/vines/services/command/start.rb +30 -0
- data/lib/vines/services/command/stop.rb +20 -0
- data/lib/vines/services/command/views.rb +26 -0
- data/lib/vines/services/component.rb +26 -0
- data/lib/vines/services/config.rb +105 -0
- data/lib/vines/services/connection.rb +120 -0
- data/lib/vines/services/controller/attributes_controller.rb +19 -0
- data/lib/vines/services/controller/base_controller.rb +99 -0
- data/lib/vines/services/controller/disco_info_controller.rb +61 -0
- data/lib/vines/services/controller/labels_controller.rb +17 -0
- data/lib/vines/services/controller/members_controller.rb +44 -0
- data/lib/vines/services/controller/messages_controller.rb +66 -0
- data/lib/vines/services/controller/probes_controller.rb +45 -0
- data/lib/vines/services/controller/services_controller.rb +81 -0
- data/lib/vines/services/controller/subscriptions_controller.rb +39 -0
- data/lib/vines/services/controller/systems_controller.rb +45 -0
- data/lib/vines/services/controller/transfers_controller.rb +58 -0
- data/lib/vines/services/controller/uploads_controller.rb +62 -0
- data/lib/vines/services/controller/users_controller.rb +127 -0
- data/lib/vines/services/core_ext/blather.rb +46 -0
- data/lib/vines/services/core_ext/couchrest.rb +33 -0
- data/lib/vines/services/indexer.rb +195 -0
- data/lib/vines/services/priority_queue.rb +94 -0
- data/lib/vines/services/roster.rb +70 -0
- data/lib/vines/services/storage/couchdb/fragment.rb +23 -0
- data/lib/vines/services/storage/couchdb/service.rb +170 -0
- data/lib/vines/services/storage/couchdb/system.rb +141 -0
- data/lib/vines/services/storage/couchdb/upload.rb +66 -0
- data/lib/vines/services/storage/couchdb/user.rb +137 -0
- data/lib/vines/services/storage/couchdb/vcard.rb +13 -0
- data/lib/vines/services/storage/couchdb.rb +157 -0
- data/lib/vines/services/storage.rb +33 -0
- data/lib/vines/services/throttle.rb +26 -0
- data/lib/vines/services/version.rb +7 -0
- data/lib/vines/services/vql/compiler.rb +94 -0
- data/lib/vines/services/vql/vql.citrus +115 -0
- data/lib/vines/services/vql/vql.rb +186 -0
- data/lib/vines/services.rb +71 -0
- data/test/config_test.rb +242 -0
- data/test/priority_queue_test.rb +23 -0
- data/test/storage/couchdb_test.rb +30 -0
- data/test/vql/compiler_test.rb +96 -0
- data/test/vql/vql_test.rb +233 -0
- data/web/coffeescripts/api.coffee +51 -0
- data/web/coffeescripts/commands.coffee +18 -0
- data/web/coffeescripts/files.coffee +315 -0
- data/web/coffeescripts/init.coffee +21 -0
- data/web/coffeescripts/services.coffee +356 -0
- data/web/coffeescripts/setup.coffee +503 -0
- data/web/coffeescripts/systems.coffee +371 -0
- data/web/images/default-service.png +0 -0
- data/web/images/linux.png +0 -0
- data/web/images/mac.png +0 -0
- data/web/images/run.png +0 -0
- data/web/images/windows.png +0 -0
- data/web/index.html +17 -0
- data/web/stylesheets/common.css +52 -0
- data/web/stylesheets/files.css +218 -0
- data/web/stylesheets/services.css +181 -0
- data/web/stylesheets/setup.css +117 -0
- data/web/stylesheets/systems.css +142 -0
- metadata +230 -0
@@ -0,0 +1,315 @@
|
|
1
|
+
class FilesPage
|
2
|
+
FILES = 'http://getvines.com/protocol/files'
|
3
|
+
LABELS = 'http://getvines.com/protocol/files/labels'
|
4
|
+
|
5
|
+
constructor: (@session) ->
|
6
|
+
@api = new Api @session
|
7
|
+
@uploads = new Uploads
|
8
|
+
session: @session
|
9
|
+
jid: @api.jid
|
10
|
+
size: this.size
|
11
|
+
complete: (file) =>
|
12
|
+
this.fileNode(file)
|
13
|
+
this.findFiles name: file.name
|
14
|
+
|
15
|
+
findLabels: ->
|
16
|
+
$('#labels').empty()
|
17
|
+
@api.get LABELS, {}, (result) =>
|
18
|
+
this.labelNodeList row for row in result.rows
|
19
|
+
|
20
|
+
labelNodeList: (label)->
|
21
|
+
text = if label.size == 1 then 'file' else 'files'
|
22
|
+
node = $("""
|
23
|
+
<li data-name="" style='display:none;'>
|
24
|
+
<span class="text"></span>
|
25
|
+
<span class="count">#{label.size} #{text}</span>
|
26
|
+
</li>
|
27
|
+
""").appendTo '#labels'
|
28
|
+
$('.text', node).text label.name
|
29
|
+
node.attr 'data-name', label.name
|
30
|
+
node.click (event) => this.selectLabel(event)
|
31
|
+
node.fadeIn(100)
|
32
|
+
|
33
|
+
selectLabel: (event) ->
|
34
|
+
name = $(event.currentTarget).attr 'data-name'
|
35
|
+
$('#labels li').removeClass 'selected'
|
36
|
+
$(event.currentTarget).addClass 'selected'
|
37
|
+
$('#files').empty()
|
38
|
+
this.findFiles label: $(event.currentTarget).attr('data-name')
|
39
|
+
|
40
|
+
findFiles: (criteria) ->
|
41
|
+
@api.get FILES, criteria, (result) =>
|
42
|
+
this.fileNode row for row in result.rows
|
43
|
+
|
44
|
+
fileNode: (file) ->
|
45
|
+
size = this.size file.size
|
46
|
+
if !file.created_at
|
47
|
+
file.created_at = Date()
|
48
|
+
time = this.date file.created_at
|
49
|
+
node = $("""
|
50
|
+
<li data-id="#{file.id}" data-name="" data-size="#{size}" data-created="#{time}">
|
51
|
+
<div class="file-icon">
|
52
|
+
<span class="size">#{size}</span>
|
53
|
+
</div>
|
54
|
+
<h2></h2>
|
55
|
+
<footer>
|
56
|
+
<span class="time">#{time}</span>
|
57
|
+
<ul class="labels"></ul>
|
58
|
+
<form class="add-label">
|
59
|
+
<div class="add-label-button"></div>
|
60
|
+
<input type="text" placeholder="Label" style="display:none;"/>
|
61
|
+
</form>
|
62
|
+
</footer>
|
63
|
+
<form class="file-form">
|
64
|
+
<fieldset>
|
65
|
+
<input class="cancel" type="submit" value="Delete"/>
|
66
|
+
</fieldset>
|
67
|
+
</form>
|
68
|
+
</li>
|
69
|
+
""").appendTo '#files'
|
70
|
+
|
71
|
+
node.data 'file', file
|
72
|
+
$('h2', node).text file.name
|
73
|
+
node.attr 'data-name', file.name
|
74
|
+
|
75
|
+
new Button $('.file-icon', node).get(0), ICONS.page2,
|
76
|
+
scale: 1.0
|
77
|
+
translation: '-2 0'
|
78
|
+
'stroke-width': 0.1
|
79
|
+
opacity: 1.0
|
80
|
+
|
81
|
+
new Button $('.add-label-button', node).get(0), ICONS.plus,
|
82
|
+
translation: '-10 -10'
|
83
|
+
scale: 0.5
|
84
|
+
|
85
|
+
$('form.file-form', node).submit => this.deleteFile node
|
86
|
+
$('form.add-label', node).submit => this.addLabel node
|
87
|
+
$('.add-label-button', node).click ->
|
88
|
+
$('form.add-label input[type="text"]', node).show()
|
89
|
+
|
90
|
+
this.labelNode node, label for label in file.labels
|
91
|
+
|
92
|
+
labelNode: (node, label) ->
|
93
|
+
labels = $('.labels', node)
|
94
|
+
item = $("""
|
95
|
+
<li data-name="">
|
96
|
+
<span class="text"></span>
|
97
|
+
<div class="remove"></div>
|
98
|
+
</li>
|
99
|
+
""").appendTo labels
|
100
|
+
$('.text', item).text label
|
101
|
+
item.attr 'data-name', label
|
102
|
+
|
103
|
+
new Button $('.remove', item).get(0), ICONS.cross,
|
104
|
+
translation: '-8 -8'
|
105
|
+
scale: 0.5
|
106
|
+
|
107
|
+
$('.remove', item).click =>
|
108
|
+
this.removeLabel node, item
|
109
|
+
|
110
|
+
addLabel: (node) ->
|
111
|
+
input = $('form.add-label input[type="text"]', node)
|
112
|
+
input.hide()
|
113
|
+
labels = (val for val in input.val().split(/,/) when val)
|
114
|
+
input.val ''
|
115
|
+
file = node.data 'file'
|
116
|
+
file.labels.push label for label in labels
|
117
|
+
@api.save FILES, file, (result) ->
|
118
|
+
this.labelNode node, label for label in labels
|
119
|
+
this.findLabels()
|
120
|
+
false
|
121
|
+
|
122
|
+
removeLabel: (node, item) ->
|
123
|
+
file = node.data 'file'
|
124
|
+
remove = item.attr 'data-name'
|
125
|
+
file.labels = (label for label in file.labels when label != remove)
|
126
|
+
@api.save FILES, file, (result) ->
|
127
|
+
item.fadeOut 200, -> item.remove()
|
128
|
+
this.findLabels()
|
129
|
+
|
130
|
+
deleteFile: (node) ->
|
131
|
+
@api.remove FILES, node.attr('data-id'), (result) =>
|
132
|
+
node.fadeOut 200, -> node.remove()
|
133
|
+
false
|
134
|
+
|
135
|
+
date: (date) ->
|
136
|
+
date = new Date date
|
137
|
+
day = 'Sun Mon Tue Wed Thu Fri Sat'.split(' ')[date.getDay()]
|
138
|
+
month = 'Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'.split(' ')[date.getMonth()]
|
139
|
+
"#{day}, #{date.getDate()} #{month}, #{date.getFullYear()} @ #{date.getHours()}:#{date.getMinutes()}"
|
140
|
+
|
141
|
+
size: (bytes) ->
|
142
|
+
kb = bytes / 1024
|
143
|
+
mb = kb / 1024
|
144
|
+
gb = mb / 1024
|
145
|
+
fmt = (num) ->
|
146
|
+
if num >= 100
|
147
|
+
Math.round num
|
148
|
+
else
|
149
|
+
num.toFixed(1).replace '.0', ''
|
150
|
+
|
151
|
+
if kb < 1
|
152
|
+
"#{bytes} b"
|
153
|
+
else if mb < 1
|
154
|
+
"#{fmt kb} k"
|
155
|
+
else if gb < 1
|
156
|
+
"#{fmt mb} m"
|
157
|
+
else
|
158
|
+
"#{fmt gb} g"
|
159
|
+
|
160
|
+
draw: ->
|
161
|
+
unless @session.connected()
|
162
|
+
window.location.hash = ''
|
163
|
+
return
|
164
|
+
|
165
|
+
$('body').attr 'id', 'files-page'
|
166
|
+
$('#container').hide().empty()
|
167
|
+
$("""
|
168
|
+
<div id="alpha" class="sidebar column y-fill">
|
169
|
+
<h2>Labels</h2>
|
170
|
+
<ul id="labels" class="selectable scroll y-fill"></ul>
|
171
|
+
<div id="alpha-controls" class="controls"></div>
|
172
|
+
</div>
|
173
|
+
<div id="beta" class="primary column x-fill y-fill">
|
174
|
+
<h2 id="files-title">Files <div id="search-files-icon"></div></h2>
|
175
|
+
<div id="search-files-form"></div>
|
176
|
+
<ul id="files" class="scroll y-fill"></ul>
|
177
|
+
</div>
|
178
|
+
<div id="charlie" class="sidebar column y-fill">
|
179
|
+
<h2>Uploads</h2>
|
180
|
+
<div id="upload-dnd" class="float">Drag files here to upload.</div>
|
181
|
+
<ul id="uploads" class="scroll y-fill"></ul>
|
182
|
+
<div id="charlie-controls" class="controls">
|
183
|
+
<form id="file-form">
|
184
|
+
<input id="open-file-chooser" type="submit" value="Select files to upload"/>
|
185
|
+
<input id="file-chooser" type="file" multiple="true" />
|
186
|
+
</form>
|
187
|
+
</div>
|
188
|
+
</div>
|
189
|
+
""").appendTo '#container'
|
190
|
+
|
191
|
+
$('#file-chooser').change (event) =>
|
192
|
+
@uploads.queue event.target.files
|
193
|
+
$('#file-chooser').val ''
|
194
|
+
|
195
|
+
$('#file-form').submit ->
|
196
|
+
$('#file-chooser').click()
|
197
|
+
false
|
198
|
+
|
199
|
+
$('#upload-dnd').bind 'dragenter', (event) ->
|
200
|
+
event.stopPropagation()
|
201
|
+
event.preventDefault()
|
202
|
+
$('#upload-dnd').css 'color', '#444'
|
203
|
+
|
204
|
+
$('#upload-dnd').bind 'dragleave', (event) ->
|
205
|
+
$('#upload-dnd').css 'color', '#ababab'
|
206
|
+
|
207
|
+
$('#upload-dnd').bind 'dragover', (event) ->
|
208
|
+
event.stopPropagation()
|
209
|
+
event.preventDefault()
|
210
|
+
|
211
|
+
$('#upload-dnd').bind 'drop', (event) =>
|
212
|
+
event.stopPropagation()
|
213
|
+
event.preventDefault()
|
214
|
+
$('#upload-dnd').css 'color', '#ababab'
|
215
|
+
@uploads.queue event.originalEvent.dataTransfer.files
|
216
|
+
|
217
|
+
this.findLabels()
|
218
|
+
this.findFiles()
|
219
|
+
|
220
|
+
$('#container').show()
|
221
|
+
layout = this.resize()
|
222
|
+
|
223
|
+
fn = ->
|
224
|
+
layout.resize()
|
225
|
+
layout.resize() # not sure why two are needed
|
226
|
+
|
227
|
+
new Filter
|
228
|
+
list: '#files'
|
229
|
+
icon: '#search-files-icon'
|
230
|
+
form: '#search-files-form'
|
231
|
+
attrs: ['data-name', 'data-created']
|
232
|
+
open: fn
|
233
|
+
close: fn
|
234
|
+
|
235
|
+
resize: ->
|
236
|
+
a = $ '#alpha'
|
237
|
+
b = $ '#beta'
|
238
|
+
c = $ '#charlie'
|
239
|
+
up = $ '#uploads'
|
240
|
+
dnd = $ '#upload-dnd'
|
241
|
+
new Layout ->
|
242
|
+
c.css 'left', a.width() + b.width()
|
243
|
+
dnd.height up.height()
|
244
|
+
dnd.css 'line-height', up.height() + 'px'
|
245
|
+
|
246
|
+
class Uploads
|
247
|
+
constructor: (options) ->
|
248
|
+
@session = options.session
|
249
|
+
@serviceJid = options.jid
|
250
|
+
@size = options.size
|
251
|
+
@complete = options.complete
|
252
|
+
@uploads = []
|
253
|
+
@sending = null
|
254
|
+
|
255
|
+
queue: (files) ->
|
256
|
+
this.add file for file in files when not this.find file
|
257
|
+
this.process()
|
258
|
+
|
259
|
+
add: (file) ->
|
260
|
+
node = this.node file
|
261
|
+
meter = $ '.meter', node
|
262
|
+
@uploads.push new Transfer
|
263
|
+
to: @serviceJid()
|
264
|
+
file: file
|
265
|
+
session: @session
|
266
|
+
progress: (pct) ->
|
267
|
+
meter.css 'width', pct + '%'
|
268
|
+
complete: =>
|
269
|
+
this.remove file
|
270
|
+
this.complete file if this.complete
|
271
|
+
@sending = null if file.name == @sending.name
|
272
|
+
this.process()
|
273
|
+
|
274
|
+
process: ->
|
275
|
+
return if @sending
|
276
|
+
if upload = @uploads[0]
|
277
|
+
@sending = upload.file
|
278
|
+
upload.start()
|
279
|
+
else
|
280
|
+
@sending = null
|
281
|
+
this.fileNode(upload.file)
|
282
|
+
|
283
|
+
find: (file) ->
|
284
|
+
(up for up in @uploads when up.file.name == file.name).shift()
|
285
|
+
|
286
|
+
remove: (file) ->
|
287
|
+
@uploads = (up for up in @uploads when up.file.name != file.name)
|
288
|
+
node = $ "#uploads li[data-file='#{file.name}']"
|
289
|
+
node.fadeOut 200, -> node.remove()
|
290
|
+
|
291
|
+
node: (file) ->
|
292
|
+
node = $("""
|
293
|
+
<li data-file="" style="display:none;">
|
294
|
+
<form class="inset">
|
295
|
+
<h2></h2>
|
296
|
+
<div class="progress">
|
297
|
+
<div class="meter"></div>
|
298
|
+
<span class="text">#{this.size file.size}</span>
|
299
|
+
<div class="cancel"></div>
|
300
|
+
</div>
|
301
|
+
</form>
|
302
|
+
</li>
|
303
|
+
""").appendTo '#uploads'
|
304
|
+
node.fadeIn 200
|
305
|
+
$('h2', node).text file.name
|
306
|
+
node.attr 'data-file', file.name
|
307
|
+
|
308
|
+
new Button $('.cancel', node).get(0), ICONS.cross,
|
309
|
+
translation: '-8 -8'
|
310
|
+
scale: 0.5
|
311
|
+
$('.cancel', node).click => this.cancel file
|
312
|
+
node
|
313
|
+
|
314
|
+
cancel: (file) ->
|
315
|
+
upload.stop() if upload = this.find file
|
@@ -0,0 +1,21 @@
|
|
1
|
+
$ ->
|
2
|
+
session = new Session()
|
3
|
+
nav = new NavBar(session)
|
4
|
+
nav.draw()
|
5
|
+
buttons =
|
6
|
+
Systems: ICONS.commandline
|
7
|
+
Services: ICONS.magic
|
8
|
+
Files: ICONS.page2
|
9
|
+
Setup: ICONS.gear2
|
10
|
+
Logout: ICONS.power
|
11
|
+
nav.addButton(label, icon) for label, icon of buttons
|
12
|
+
|
13
|
+
pages =
|
14
|
+
'/systems': new SystemsPage(session)
|
15
|
+
'/services': new ServicesPage(session)
|
16
|
+
'/files': new FilesPage(session)
|
17
|
+
'/setup': new SetupPage(session)
|
18
|
+
'/logout': new LogoutPage(session)
|
19
|
+
'default': new LoginPage(session, '/systems/')
|
20
|
+
new Router(pages).draw()
|
21
|
+
nav.select $('#nav-link-systems').parent()
|
@@ -0,0 +1,356 @@
|
|
1
|
+
class ServicesPage
|
2
|
+
SERVICES = 'http://getvines.com/protocol/services'
|
3
|
+
MEMBERS = 'http://getvines.com/protocol/services/members'
|
4
|
+
SYSTEMS = 'http://getvines.com/protocol/systems'
|
5
|
+
ATTRS = 'http://getvines.com/protocol/systems/attributes'
|
6
|
+
USERS = 'http://getvines.com/protocol/users'
|
7
|
+
|
8
|
+
constructor: (@session) ->
|
9
|
+
@api = new Api @session
|
10
|
+
@selectedService = null
|
11
|
+
@validateTimeout = null
|
12
|
+
@layout = null
|
13
|
+
@users = []
|
14
|
+
|
15
|
+
deleteService: (event) ->
|
16
|
+
this.drawBlankSlate()
|
17
|
+
this.toggleForm '#remove-contact-form'
|
18
|
+
selected = $("#services li[data-id='#{@selectedService.id}']")
|
19
|
+
@api.remove SERVICES, @selectedService.id, (result) =>
|
20
|
+
selected.fadeOut 200, ->
|
21
|
+
selected.remove()
|
22
|
+
@selectedService = null
|
23
|
+
false
|
24
|
+
|
25
|
+
icon: (member) ->
|
26
|
+
icons =
|
27
|
+
darwin: 'mac.png'
|
28
|
+
linux: 'linux.png'
|
29
|
+
windows: 'windows.png'
|
30
|
+
icon = icons[member.os] || 'run.png'
|
31
|
+
"images/#{icon}"
|
32
|
+
|
33
|
+
drawMember: (member) ->
|
34
|
+
return unless this.editorVisible()
|
35
|
+
node = $("""
|
36
|
+
<li>
|
37
|
+
<span class="icon"><img src="#{this.icon(member)}"/></span>
|
38
|
+
<span class="text"></span>
|
39
|
+
</li>
|
40
|
+
""").appendTo '#members'
|
41
|
+
$('.text', node).text member.name
|
42
|
+
|
43
|
+
operators: ->
|
44
|
+
for operator in ['like', 'not like', 'starts with', 'ends with', 'is', 'is not', '>', '>=', '<', '<=', 'and', 'or']
|
45
|
+
node = $("""
|
46
|
+
<li data-selector="#{operator}">
|
47
|
+
#{operator}
|
48
|
+
</li>
|
49
|
+
""").appendTo '#operators'
|
50
|
+
node.click (event) =>
|
51
|
+
$('#syntax').focus()
|
52
|
+
name = $(event.currentTarget).attr 'data-selector'
|
53
|
+
$('#syntax').val($('#syntax').val() + " #{name} ")
|
54
|
+
this.validateIn()
|
55
|
+
|
56
|
+
selectService: (node) ->
|
57
|
+
id = $(node).attr 'data-id'
|
58
|
+
name = $(node).attr 'data-name'
|
59
|
+
|
60
|
+
$('#services li').removeClass 'selected'
|
61
|
+
$(node).addClass 'selected'
|
62
|
+
|
63
|
+
$('#remove-service-msg').html "Are you sure you want to remove the " +
|
64
|
+
"<strong>#{name}</strong> service?"
|
65
|
+
$('#remove-service-form .buttons').fadeIn 200
|
66
|
+
@api.get SERVICES, id: id, (result) =>
|
67
|
+
@selectedService = result
|
68
|
+
this.drawEditor(result)
|
69
|
+
|
70
|
+
serviceNode: (service) ->
|
71
|
+
label = if service.size == 1 then 'system' else 'systems'
|
72
|
+
node = $("""
|
73
|
+
<li data-id="#{service.id}" data-name="" data-size="#{service.size}">
|
74
|
+
<span class="text">#{service.name}</span>
|
75
|
+
<span class="count">#{service.size} #{label}</span>
|
76
|
+
</li>
|
77
|
+
""").appendTo '#services'
|
78
|
+
node.attr 'data-name', service.name
|
79
|
+
$('.text', node).text service.name
|
80
|
+
node.click (event) => this.selectService event.currentTarget
|
81
|
+
node
|
82
|
+
|
83
|
+
findServices: ->
|
84
|
+
@api.get SERVICES, {}, (result) =>
|
85
|
+
this.serviceNode row for row in result.rows
|
86
|
+
|
87
|
+
findMembers: (id) ->
|
88
|
+
@api.get MEMBERS, id: id, (result) =>
|
89
|
+
this.drawMember row for row in result.rows
|
90
|
+
|
91
|
+
findUsers: (syntax) ->
|
92
|
+
@api.get USERS, {}, (result) =>
|
93
|
+
@users = (row for row in result.rows when !row.system)
|
94
|
+
this.drawUsers()
|
95
|
+
|
96
|
+
attributeNode: (attribute) ->
|
97
|
+
node = $('<li data-name=""></li>').appendTo '#attributes'
|
98
|
+
node.text attribute
|
99
|
+
node.attr 'data-name', attribute
|
100
|
+
node.click (event) =>
|
101
|
+
$('#syntax').focus()
|
102
|
+
name = $(event.currentTarget).attr 'data-name'
|
103
|
+
$('#syntax').val($('#syntax').val() + " #{name} ")
|
104
|
+
this.validateIn()
|
105
|
+
|
106
|
+
findAttributes: (syntax) ->
|
107
|
+
@api.get ATTRS, {}, (result) =>
|
108
|
+
this.attributeNode row for row in result.rows
|
109
|
+
|
110
|
+
validateIn: (millis)->
|
111
|
+
clearTimeout @validateTimeout
|
112
|
+
@validateTimeout = setTimeout (=> this.validate()), millis || 500
|
113
|
+
|
114
|
+
validate: ->
|
115
|
+
$('#syntax-status').text ''
|
116
|
+
|
117
|
+
# only validate if text changed
|
118
|
+
prev = $('#syntax').data 'prev'
|
119
|
+
code = $.trim $('#syntax').val()
|
120
|
+
$('#syntax').data 'prev', code
|
121
|
+
return unless code && code != prev
|
122
|
+
|
123
|
+
$('#syntax-status').text 'Searching . . .'
|
124
|
+
$('#members').empty()
|
125
|
+
@api.get2 MEMBERS, code, (result) =>
|
126
|
+
if result.ok
|
127
|
+
$('#syntax-status').text ''
|
128
|
+
this.drawMember row for row in result.rows
|
129
|
+
else
|
130
|
+
$('#syntax-status').text result.error
|
131
|
+
|
132
|
+
validateForm: ->
|
133
|
+
$('#name-error').empty()
|
134
|
+
valid = true
|
135
|
+
|
136
|
+
name = $.trim $('#name').val()
|
137
|
+
if name == ''
|
138
|
+
$('#name-error').text 'Name is required.'
|
139
|
+
valid = false
|
140
|
+
|
141
|
+
valid
|
142
|
+
|
143
|
+
save: ->
|
144
|
+
return unless this.validateForm()
|
145
|
+
users = $('#users :checked').map(-> $(this).val()).get()
|
146
|
+
accounts = $('#unix-users').val().split(',')
|
147
|
+
accounts = ($.trim u for u in accounts when $.trim(u).length > 0)
|
148
|
+
service =
|
149
|
+
name: $('#name').val()
|
150
|
+
code: $('#syntax').val()
|
151
|
+
accounts: accounts
|
152
|
+
users: users
|
153
|
+
service['id'] = $('#id').val() if $('#id').val().length > 0
|
154
|
+
|
155
|
+
@api.save SERVICES, service, (result) =>
|
156
|
+
new Notification 'Service saved successfully'
|
157
|
+
result.size = $('#members').length
|
158
|
+
$('#id').val result.id
|
159
|
+
node = $("#services li[data-id='#{result.id}']")
|
160
|
+
if node.length == 0
|
161
|
+
node = this.serviceNode result
|
162
|
+
this.selectService node
|
163
|
+
else
|
164
|
+
$('.text', node).text result.name
|
165
|
+
false
|
166
|
+
|
167
|
+
drawBlankSlate: ->
|
168
|
+
$('#beta').empty()
|
169
|
+
$("""
|
170
|
+
<form id="blank-slate">
|
171
|
+
<p>
|
172
|
+
Services are dynamically updated groups of systems based on
|
173
|
+
criteria you define. Send a command to the service and it runs
|
174
|
+
on every system in the group.
|
175
|
+
</p>
|
176
|
+
<input type="submit" id="blank-slate-add" value="Create a New Service"/>
|
177
|
+
</form>
|
178
|
+
""").appendTo '#beta'
|
179
|
+
$('#blank-slate').submit =>
|
180
|
+
this.drawEditor()
|
181
|
+
false
|
182
|
+
|
183
|
+
draw: ->
|
184
|
+
unless @session.connected()
|
185
|
+
window.location.hash = ''
|
186
|
+
return
|
187
|
+
|
188
|
+
$('body').attr 'id', 'services-page'
|
189
|
+
$('#container').hide().empty()
|
190
|
+
$("""
|
191
|
+
<div id="alpha" class="sidebar column y-fill">
|
192
|
+
<h2>Services <div id="search-services-icon"></div></h2>
|
193
|
+
<div id="search-services-form"></div>
|
194
|
+
<ul id="services" class="selectable scroll y-fill"></ul>
|
195
|
+
<div id="alpha-controls" class="controls">
|
196
|
+
<div id="add-service"></div>
|
197
|
+
<div id="remove-service"></div>
|
198
|
+
</div>
|
199
|
+
<form id="remove-service-form" class="overlay" style="display:none;">
|
200
|
+
<h2>Remove Service</h2>
|
201
|
+
<p id="remove-service-msg">Select a service in the list above to remove.</p>
|
202
|
+
<fieldset class="buttons" style="display:none;">
|
203
|
+
<input id="remove-service-cancel" type="button" value="Cancel"/>
|
204
|
+
<input id="remove-service-ok" type="submit" value="Remove"/>
|
205
|
+
</fieldset>
|
206
|
+
</form>
|
207
|
+
</div>
|
208
|
+
<div id="beta" class="primary column x-fill y-fill"></div>
|
209
|
+
<div id="charlie" class="sidebar column y-fill">
|
210
|
+
<h2>Operators</h2>
|
211
|
+
<ul id="operators"></ul>
|
212
|
+
<h2>Attributes <div id="search-attributes-icon"></div></h2>
|
213
|
+
<div id="search-attributes-form"></div>
|
214
|
+
<ul id="attributes" class="y-fill scroll"></ul>
|
215
|
+
</div>
|
216
|
+
""").appendTo '#container'
|
217
|
+
|
218
|
+
new Button '#add-service', ICONS.plus
|
219
|
+
new Button '#remove-service', ICONS.minus
|
220
|
+
|
221
|
+
this.drawBlankSlate()
|
222
|
+
|
223
|
+
$('#add-service').click => this.drawEditor()
|
224
|
+
$('#remove-service').click => this.toggleForm '#remove-service-form'
|
225
|
+
$('#remove-service-cancel').click => this.toggleForm '#remove-service-form'
|
226
|
+
$('#remove-service-form').submit => this.deleteService()
|
227
|
+
|
228
|
+
this.operators()
|
229
|
+
this.findServices()
|
230
|
+
this.findAttributes()
|
231
|
+
this.findUsers()
|
232
|
+
|
233
|
+
$('#container').show()
|
234
|
+
@layout = this.resize()
|
235
|
+
|
236
|
+
fn = =>
|
237
|
+
@layout.resize()
|
238
|
+
@layout.resize() # not sure why two are needed
|
239
|
+
|
240
|
+
new Filter
|
241
|
+
list: '#services'
|
242
|
+
icon: '#search-services-icon'
|
243
|
+
form: '#search-services-form'
|
244
|
+
attrs: ['data-name']
|
245
|
+
open: fn
|
246
|
+
close: fn
|
247
|
+
|
248
|
+
new Filter
|
249
|
+
list: '#attributes'
|
250
|
+
icon: '#search-attributes-icon'
|
251
|
+
form: '#search-attributes-form'
|
252
|
+
attrs: ['data-name']
|
253
|
+
open: fn
|
254
|
+
close: fn
|
255
|
+
|
256
|
+
drawEditor: (service) ->
|
257
|
+
return unless this.pageVisible()
|
258
|
+
|
259
|
+
unless service
|
260
|
+
@selectedService = null
|
261
|
+
$('#services li').removeClass 'selected'
|
262
|
+
|
263
|
+
$('#beta').empty()
|
264
|
+
$("""
|
265
|
+
<form id="editor-form" class="sections y-fill scroll">
|
266
|
+
<input id="id" type="hidden"/>
|
267
|
+
<div>
|
268
|
+
<section>
|
269
|
+
<h2>Service</h2>
|
270
|
+
<fieldset>
|
271
|
+
<label for="name">Name</label>
|
272
|
+
<input id="name" type="text"/>
|
273
|
+
<p id="name-error" class="error"></p>
|
274
|
+
<label for="syntax">Criteria</label>
|
275
|
+
<textarea id="syntax" placeholder="fqdn like 'www.*' and platform in ['fedora', 'mac_os_x']"></textarea>
|
276
|
+
<p id="syntax-status"></p>
|
277
|
+
</fieldset>
|
278
|
+
</section>
|
279
|
+
<section>
|
280
|
+
<h2>Members</h2>
|
281
|
+
<fieldset id="service-preview">
|
282
|
+
<ul id="members" class="scroll"></ul>
|
283
|
+
</fieldset>
|
284
|
+
</section>
|
285
|
+
<section>
|
286
|
+
<h2>Permissions</h2>
|
287
|
+
<fieldset>
|
288
|
+
<label>Users</label>
|
289
|
+
<ul id="users" class="scroll"></ul>
|
290
|
+
<label for="unix-users">Unix Accounts</label>
|
291
|
+
<input id="unix-users" type="text"/>
|
292
|
+
</fieldset>
|
293
|
+
</section>
|
294
|
+
</div>
|
295
|
+
</form>
|
296
|
+
<form id="editor-buttons">
|
297
|
+
<input id="save" type="submit" value="Save"/>
|
298
|
+
</form>
|
299
|
+
""").appendTo '#beta'
|
300
|
+
|
301
|
+
if service
|
302
|
+
this.findMembers(service.id)
|
303
|
+
$('#id').val service.id
|
304
|
+
$('#name').val service.name
|
305
|
+
$('#syntax').val service.code
|
306
|
+
$('#unix-users').val service.accounts.join(', ')
|
307
|
+
|
308
|
+
this.drawUsers() if @users.length > 0
|
309
|
+
|
310
|
+
@layout.resize()
|
311
|
+
$('#name').focus()
|
312
|
+
|
313
|
+
$('#syntax').change => this.validateIn()
|
314
|
+
$('#syntax').keyup => this.validateIn()
|
315
|
+
$('#editor-form').submit => this.save()
|
316
|
+
$('#editor-buttons').submit => this.save()
|
317
|
+
|
318
|
+
drawUsers: ->
|
319
|
+
return unless this.editorVisible()
|
320
|
+
|
321
|
+
$('#users').empty()
|
322
|
+
for user in @users
|
323
|
+
node = $("""
|
324
|
+
<li>
|
325
|
+
<input id='user-#{user.jid}' type='checkbox' value='#{user.jid}'/>
|
326
|
+
<label for='user-#{user.jid}'>#{user.jid}</label>
|
327
|
+
</li>
|
328
|
+
""").appendTo '#users'
|
329
|
+
# user creating service gets access to it by default
|
330
|
+
$('input', node).prop 'checked', true if user.jid == @session.bareJid()
|
331
|
+
|
332
|
+
if @selectedService
|
333
|
+
$('#users input[type="checkbox"]').val @selectedService.users
|
334
|
+
|
335
|
+
toggleForm: (form, fn) ->
|
336
|
+
form = $(form)
|
337
|
+
$('form.overlay').each ->
|
338
|
+
$(this).hide() unless this.id == form.attr 'id'
|
339
|
+
if form.is ':hidden'
|
340
|
+
fn() if fn
|
341
|
+
form.fadeIn 100
|
342
|
+
else
|
343
|
+
form.fadeOut 100, ->
|
344
|
+
form[0].reset()
|
345
|
+
fn() if fn
|
346
|
+
|
347
|
+
pageVisible: -> $('#services-page').length > 0
|
348
|
+
|
349
|
+
editorVisible: -> $('#services-page #editor-form').length > 0
|
350
|
+
|
351
|
+
resize: ->
|
352
|
+
a = $ '#alpha'
|
353
|
+
b = $ '#beta'
|
354
|
+
c = $ '#charlie'
|
355
|
+
new Layout ->
|
356
|
+
c.css 'left', a.width() + b.width()
|