vines-services 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.
- 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()
|