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.
@@ -1,70 +1,109 @@
1
1
  # encoding: UTF-8
2
+
2
3
  module Vines
3
4
  class Storage
4
5
  class CouchDB < Storage
6
+ alias :_services_find_user :find_user
7
+
8
+ # Override the user's roster with an auto-populated roster containing
9
+ # the services and systems to which the user has permission to access.
10
+ def find_user(jid)
11
+ user = _services_find_user(jid)
12
+ return unless user
13
+ user.roster = []
14
+
15
+ systems, users = find_users.partition {|u| u['system'] }
16
+ systems.map! {|u| u['_id'].sub('user:', '') }
17
+ return user if systems.include?(user.jid.to_s)
18
+ services = find_services(user.jid)
19
+
20
+ users.each do |row|
21
+ id = row['_id'].sub('user:', '')
22
+ user.roster << Contact.new(
23
+ :jid => id,
24
+ :name => row['name'],
25
+ :subscription => 'both',
26
+ :groups => ['People']) unless id == jid.to_s
27
+ end
28
+
29
+ services.each do |row|
30
+ user.roster << Contact.new(
31
+ :jid => row['jid'],
32
+ :name => row['name'],
33
+ :subscription => 'to',
34
+ :groups => ['Services'])
35
+ end
36
+
37
+ find_systems(services).each do |name, groups|
38
+ id = JID.new(name.dup, user.jid.domain).to_s
39
+ user.roster << Contact.new(
40
+ :jid => id,
41
+ :name => name,
42
+ :subscription => 'to',
43
+ :groups => ['Systems', *groups]) if systems.include?(id)
44
+ end
5
45
 
6
- ALL_SERVICES = '/_design/Service/_view/by_name'.freeze
46
+ user.roster << Contact.new(
47
+ :jid => "vines.#{user.jid.domain}",
48
+ :name => 'Vines',
49
+ :subscription => 'to')
50
+ user
51
+ end
7
52
 
8
- # The existing storage class needs another method for our custom roster queries
9
- # The default escaping in URLs makes this method necessary
10
- def get_services
11
- http = EM::HttpRequest.new("#{@url}#{ALL_SERVICES}").get
12
- http.errback { yield }
53
+ # Return the User documents for all users and systems.
54
+ def find_users
55
+ url = "%s/_design/User/_view/all?reduce=false&include_docs=true" % @url
56
+ http = EM::HttpRequest.new(url).get
57
+ http.errback { yield [] }
13
58
  http.callback do
14
59
  doc = if http.response_header.status == 200
15
- JSON.parse(http.response) rescue nil
60
+ rows = JSON.parse(http.response)['rows'] rescue []
61
+ rows.map {|row| row['doc'] }
16
62
  end
17
- yield doc
63
+ yield doc || []
18
64
  end
19
65
  end
66
+ fiber :find_users
20
67
 
21
- #In order to supply the correct vines roster logic, we need to over ride the default
22
- #User creation of in the vines server. This method is much more effecient than
23
- #looking up roster memberships each time the roster is sent.
24
- def find_user(jid)
25
- jid = JID.new(jid || '').bare.to_s
26
- if jid.empty? then yield; return end
27
- get("user:#{jid}") do |doc|
28
- user = if doc && doc['type'] == 'User'
29
- User.new(:jid => jid).tap do |user|
30
- user.name, user.password = doc.values_at('name', 'password')
31
- if doc['roster'] != ""
32
- (doc['roster'] || {}).each_pair do |jid, props|
33
- user.roster << Contact.new(
34
- :jid => jid,
35
- :name => props['name'],
36
- :subscription => props['subscription'],
37
- :ask => props['ask'],
38
- :groups => props['groups'] || [])
39
- end
40
- end
41
- add_user_roster_services(user)
42
- end
68
+ # Return the Service documents to which this JID has permission to
69
+ # access.
70
+ def find_services(jid)
71
+ url = "%s/_design/Service/_view/by_user?reduce=false&key=%s" % [@url, escape(jid.to_s).to_json]
72
+ http = EM::HttpRequest.new(url).get
73
+ http.errback { yield [] }
74
+ http.callback do
75
+ doc = if http.response_header.status == 200
76
+ rows = JSON.parse(http.response)['rows'] rescue []
77
+ rows.map {|row| row['value'] }
43
78
  end
44
- yield user
79
+ yield doc || []
45
80
  end
46
81
  end
47
- fiber :find_user
82
+ fiber :find_services
48
83
 
49
- # We will go find each service that contains this user jid in the
50
- # users of the service document.
51
- def add_user_roster_services(user)
52
- self.get_services do |cdoc|
53
- if cdoc
54
- rows = cdoc['rows'].map do |row|
55
- if row['value']['users'].include?(user.jid.to_s)
56
- jid = JID.new("#{row['value']['jid']}").bare.to_s
57
- user.roster << Contact.new(
58
- :jid => jid,
59
- :name => row['value']['name'],
60
- :subscription => "both",
61
- :ask => "subscribe",
62
- :groups => ["Vines"])
84
+ # Find the systems that belong to these services. Return a Hash of
85
+ # system name to list of service names to which it belongs.
86
+ def find_systems(services)
87
+ keys = services.map {|row| [0, row['_id']] }
88
+ url = "%s/_design/System/_view/memberships?reduce=false" % @url
89
+ http = EM::HttpRequest.new(url).post(
90
+ head: {'Content-Type' => 'application/json'},
91
+ body: {keys: keys}.to_json)
92
+ http.errback { yield [] }
93
+ http.callback do
94
+ doc = if http.response_header.status == 200
95
+ rows = JSON.parse(http.response)['rows'] rescue []
96
+ Hash.new {|h, k| h[k] = [] }.tap do |systems|
97
+ rows.each do |row|
98
+ service = services.find {|s| s['_id'] == row['key'][1] }
99
+ systems[row['value']['name']] << service['name']
63
100
  end
64
101
  end
65
102
  end
103
+ yield doc || []
66
104
  end
67
105
  end
106
+ fiber :find_systems
68
107
  end
69
108
  end
70
109
  end
@@ -6,7 +6,7 @@ module Vines
6
6
  class Service < CouchRest::Model::Base
7
7
  extend Storage::CouchDB::ClassMethods
8
8
 
9
- KEYS = %w[_id name code accounts users jid created_at modified_at].freeze
9
+ KEYS = %w[_id name code accounts users jid created_at updated_at].freeze
10
10
  VIEW_ID = "_design/System".freeze
11
11
  VIEW_NAME = "System/memberships".freeze
12
12
 
@@ -6,7 +6,7 @@ module Vines
6
6
  class System < CouchRest::Model::Base
7
7
  extend Storage::CouchDB::ClassMethods
8
8
 
9
- KEYS = %w[_id ohai created_at modified_at].freeze
9
+ KEYS = %w[_id ohai created_at updated_at].freeze
10
10
  VIEW_NAME = "System/memberships".freeze
11
11
 
12
12
  attr_writer :services
@@ -6,7 +6,7 @@ module Vines
6
6
  class Upload < CouchRest::Model::Base
7
7
  extend Storage::CouchDB::ClassMethods
8
8
 
9
- KEYS = %w[_id name size labels created_at modified_at].freeze
9
+ KEYS = %w[_id name size labels created_at updated_at].freeze
10
10
 
11
11
  property :name, String
12
12
  property :size, Integer
@@ -6,7 +6,7 @@ module Vines
6
6
  class User < CouchRest::Model::Base
7
7
  extend Storage::CouchDB::ClassMethods
8
8
 
9
- KEYS = %w[_id name permissions system created_at modified_at].freeze
9
+ KEYS = %w[_id name permissions system created_at updated_at].freeze
10
10
 
11
11
  before_save :enforce_constraints
12
12
  after_destroy :remove_references
@@ -51,6 +51,16 @@ module Vines
51
51
  end
52
52
  end
53
53
 
54
+ # Grant this user all possible permissions.
55
+ def admin!
56
+ write_attribute('permissions', {
57
+ 'systems' => true,
58
+ 'services' => true,
59
+ 'files' => true,
60
+ 'users' => true
61
+ })
62
+ end
63
+
54
64
  def permissions=(perms)
55
65
  perms ||= {}
56
66
  self.manage_systems = perms['systems']
@@ -59,7 +69,7 @@ module Vines
59
69
  self.manage_users = perms['users']
60
70
  end
61
71
 
62
- def password=(desired)
72
+ def plain_password=(desired)
63
73
  desired = (desired || '').strip
64
74
  raise 'password too short' if desired.size < (system ? 128 : 8)
65
75
  write_attribute('password', BCrypt::Password.create(desired))
@@ -68,7 +78,7 @@ module Vines
68
78
  def change_password(previous, desired)
69
79
  hash = BCrypt::Password.new(password) rescue nil
70
80
  raise 'password failure' unless hash && hash == previous
71
- self.password = desired
81
+ self.plain_password = desired
72
82
  end
73
83
 
74
84
  def jid
@@ -100,20 +100,13 @@ module Vines
100
100
  end
101
101
 
102
102
  def create_views
103
- # FIXME Use views in CouchRest::Model classes to populate db
104
- designs = {}
105
-
106
103
  EM.run do
107
- http('', :put) do # create db
108
- designs.each do |name, views|
109
- get("/_design/#{name}") do |doc|
110
- doc ||= {"_id" => "_design/#{name}"}
111
- doc['language'] = 'javascript'
112
- doc['views'] = views
113
- save(doc) { EM.stop }
114
- end
104
+ Fiber.new do
105
+ CouchRest::Model::Base.subclasses.each do |klass|
106
+ klass.save_design_doc! if klass.respond_to?(:save_design_doc!)
115
107
  end
116
- end
108
+ EM.stop
109
+ end.resume
117
110
  end
118
111
  end
119
112
 
@@ -146,6 +139,9 @@ module Vines
146
139
  *url, _ = @url.split('/')
147
140
  server = CouchRest::Server.new(url.join('/'))
148
141
  CouchRest::Model::Base.database = server.database(database)
142
+ CouchRest::Model::Base.configure do |config|
143
+ config.auto_update_design_doc = false
144
+ end
149
145
  end
150
146
 
151
147
  def escape(jid)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Vines
4
4
  module Services
5
- VERSION = '0.1.0'
5
+ VERSION = '0.1.1'
6
6
  end
7
7
  end
@@ -18,6 +18,6 @@ class PriorityQueueTest < MiniTest::Unit::TestCase
18
18
  end
19
19
  assert queue.empty?
20
20
  assert_equal 0, queue.size
21
- assert_equal nums.sort {|a, b| -(a <=> b) }, popped
21
+ assert_equal nums.sort, popped
22
22
  end
23
23
  end
@@ -1,5 +1,11 @@
1
1
  class Api
2
+ USERS = 'http://getvines.com/protocol/users'
3
+
2
4
  constructor: (@session) ->
5
+ @user = null
6
+ @session.onRoster =>
7
+ this.get USERS, jid: @session.bareJid(), (result) =>
8
+ @user = result
3
9
 
4
10
  jid: -> "vines.#{@session.bareJid().split('@')[1]}"
5
11
 
@@ -9,8 +9,7 @@ class FilesPage
9
9
  jid: @api.jid
10
10
  size: this.size
11
11
  complete: (file) =>
12
- this.fileNode(file)
13
- this.findFiles name: file.name
12
+ this.findFile file.name
14
13
 
15
14
  findLabels: ->
16
15
  $('#labels').empty()
@@ -20,7 +19,7 @@ class FilesPage
20
19
  labelNodeList: (label)->
21
20
  text = if label.size == 1 then 'file' else 'files'
22
21
  node = $("""
23
- <li data-name="" style='display:none;'>
22
+ <li data-name="">
24
23
  <span class="text"></span>
25
24
  <span class="count">#{label.size} #{text}</span>
26
25
  </li>
@@ -28,7 +27,6 @@ class FilesPage
28
27
  $('.text', node).text label.name
29
28
  node.attr 'data-name', label.name
30
29
  node.click (event) => this.selectLabel(event)
31
- node.fadeIn(100)
32
30
 
33
31
  selectLabel: (event) ->
34
32
  name = $(event.currentTarget).attr 'data-name'
@@ -41,19 +39,35 @@ class FilesPage
41
39
  @api.get FILES, criteria, (result) =>
42
40
  this.fileNode row for row in result.rows
43
41
 
44
- fileNode: (file) ->
42
+ findFile: (name) ->
43
+ @api.get FILES, name: name, (result) =>
44
+ this.fileNode result
45
+
46
+ updateFileNode: (file) ->
47
+ node = $ "#files li[data-id='#{file.id}']"
45
48
  size = this.size file.size
46
- if !file.created_at
47
- file.created_at = Date()
48
49
  time = this.date file.created_at
50
+ node.data 'file', file
51
+ $('h2', node).text file.name
52
+ $('.size', node).text size
53
+ $('.time', node).text time
54
+ node.attr 'data-name', file.name
55
+ node.attr 'data-size', size
56
+ node.attr 'data-created', time
57
+
58
+ fileNode: (file) ->
59
+ if $("#files li[data-id='#{file.id}']").length > 0
60
+ this.updateFileNode file
61
+ return
62
+
49
63
  node = $("""
50
- <li data-id="#{file.id}" data-name="" data-size="#{size}" data-created="#{time}">
64
+ <li data-id="#{file.id}">
51
65
  <div class="file-icon">
52
- <span class="size">#{size}</span>
66
+ <span class="size"></span>
53
67
  </div>
54
68
  <h2></h2>
55
69
  <footer>
56
- <span class="time">#{time}</span>
70
+ <span class="time"></span>
57
71
  <ul class="labels"></ul>
58
72
  <form class="add-label">
59
73
  <div class="add-label-button"></div>
@@ -68,10 +82,6 @@ class FilesPage
68
82
  </li>
69
83
  """).appendTo '#files'
70
84
 
71
- node.data 'file', file
72
- $('h2', node).text file.name
73
- node.attr 'data-name', file.name
74
-
75
85
  new Button $('.file-icon', node).get(0), ICONS.page2,
76
86
  scale: 1.0
77
87
  translation: '-2 0'
@@ -87,7 +97,8 @@ class FilesPage
87
97
  $('.add-label-button', node).click ->
88
98
  $('form.add-label input[type="text"]', node).show()
89
99
 
90
- this.labelNode node, label for label in file.labels
100
+ this.updateFileNode file
101
+ this.labelNode(node, label) for label in file.labels
91
102
 
92
103
  labelNode: (node, label) ->
93
104
  labels = $('.labels', node)
@@ -115,8 +126,9 @@ class FilesPage
115
126
  file = node.data 'file'
116
127
  file.labels.push label for label in labels
117
128
  @api.save FILES, file, (result) ->
118
- this.labelNode node, label for label in labels
119
- this.findLabels()
129
+ for label in labels
130
+ this.labelNode node, label
131
+ this.updateLabelCount label, 1
120
132
  false
121
133
 
122
134
  removeLabel: (node, item) ->
@@ -125,10 +137,21 @@ class FilesPage
125
137
  file.labels = (label for label in file.labels when label != remove)
126
138
  @api.save FILES, file, (result) ->
127
139
  item.fadeOut 200, -> item.remove()
128
- this.findLabels()
140
+ this.updateLabelCount remove, -1
141
+
142
+ updateLabelCount: (name, inc) ->
143
+ el = $ "#labels li[data-name='#{name}'] .count"
144
+ if el.length > 0
145
+ count = parseInt(el.text().split(' ')[0]) + inc
146
+ text = if count == 1 then 'file' else 'files'
147
+ el.text "#{count} #{text}"
148
+ else
149
+ this.labelNodeList(name: name, size: 1)
129
150
 
130
151
  deleteFile: (node) ->
131
152
  @api.remove FILES, node.attr('data-id'), (result) =>
153
+ for label in node.data('file').labels
154
+ this.updateLabelCount label, -1
132
155
  node.fadeOut 200, -> node.remove()
133
156
  false
134
157
 
@@ -188,31 +211,35 @@ class FilesPage
188
211
  </div>
189
212
  """).appendTo '#container'
190
213
 
191
- $('#file-chooser').change (event) =>
192
- @uploads.queue event.target.files
193
- $('#file-chooser').val ''
214
+ if @api.user.permissions.files
215
+ $('#file-chooser').change (event) =>
216
+ @uploads.queue event.target.files
217
+ $('#file-chooser').val ''
194
218
 
195
- $('#file-form').submit ->
196
- $('#file-chooser').click()
197
- false
219
+ $('#file-form').submit ->
220
+ $('#file-chooser').click()
221
+ false
198
222
 
199
- $('#upload-dnd').bind 'dragenter', (event) ->
200
- event.stopPropagation()
201
- event.preventDefault()
202
- $('#upload-dnd').css 'color', '#444'
223
+ $('#upload-dnd').bind 'dragenter', (event) ->
224
+ event.stopPropagation()
225
+ event.preventDefault()
226
+ $('#upload-dnd').css 'color', '#444'
203
227
 
204
- $('#upload-dnd').bind 'dragleave', (event) ->
205
- $('#upload-dnd').css 'color', '#ababab'
228
+ $('#upload-dnd').bind 'dragleave', (event) ->
229
+ $('#upload-dnd').css 'color', '#ababab'
206
230
 
207
- $('#upload-dnd').bind 'dragover', (event) ->
208
- event.stopPropagation()
209
- event.preventDefault()
231
+ $('#upload-dnd').bind 'dragover', (event) ->
232
+ event.stopPropagation()
233
+ event.preventDefault()
210
234
 
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
235
+ $('#upload-dnd').bind 'drop', (event) =>
236
+ event.stopPropagation()
237
+ event.preventDefault()
238
+ $('#upload-dnd').css 'color', '#ababab'
239
+ @uploads.queue event.originalEvent.dataTransfer.files
240
+ else
241
+ $('#upload-dnd').text "Your account cannot upload files."
242
+ $('#file-form').remove()
216
243
 
217
244
  this.findLabels()
218
245
  this.findFiles()
@@ -278,7 +305,6 @@ class FilesPage
278
305
  upload.start()
279
306
  else
280
307
  @sending = null
281
- this.fileNode(upload.file)
282
308
 
283
309
  find: (file) ->
284
310
  (up for up in @uploads when up.file.name == file.name).shift()
@@ -173,9 +173,10 @@ class ServicesPage
173
173
  criteria you define. Send a command to the service and it runs
174
174
  on every system in the group.
175
175
  </p>
176
- <input type="submit" id="blank-slate-add" value="Create a New Service"/>
176
+ <input type="submit" id="blank-slate-add" value="Add Service"/>
177
177
  </form>
178
178
  """).appendTo '#beta'
179
+ $('#blank-slate-add').remove() unless @api.user.permissions.services
179
180
  $('#blank-slate').submit =>
180
181
  this.drawEditor()
181
182
  false
@@ -218,6 +219,8 @@ class ServicesPage
218
219
  new Button '#add-service', ICONS.plus
219
220
  new Button '#remove-service', ICONS.minus
220
221
 
222
+ $('#alpha-controls div').remove() unless @api.user.permissions.services
223
+
221
224
  this.drawBlankSlate()
222
225
 
223
226
  $('#add-service').click => this.drawEditor()
@@ -272,7 +275,7 @@ class ServicesPage
272
275
  <input id="name" type="text"/>
273
276
  <p id="name-error" class="error"></p>
274
277
  <label for="syntax">Criteria</label>
275
- <textarea id="syntax" placeholder="fqdn like 'www.*' and platform in ['fedora', 'mac_os_x']"></textarea>
278
+ <textarea id="syntax" placeholder="fqdn starts with 'www.' and platform is 'mac_os_x'"></textarea>
276
279
  <p id="syntax-status"></p>
277
280
  </fieldset>
278
281
  </section>
@@ -31,7 +31,7 @@ class SetupPage
31
31
  """).appendTo '#services'
32
32
  $('label', node).text service.name
33
33
  $('#services input[type="checkbox"]').val @selected.services if @selected
34
- if @selected && !@selected.permissions['services']
34
+ if @selected && !@api.user.permissions.services
35
35
  $('#services input[type="checkbox"]').prop 'disabled', true
36
36
 
37
37
  findUsers: ->
@@ -48,17 +48,15 @@ class SetupPage
48
48
 
49
49
  userNode: (user) ->
50
50
  node = $("""
51
- <li data-name="" data-jid="" id="#{user.jid}">
51
+ <li data-name="" data-jid="#{user.jid}" id="#{user.jid}">
52
52
  <span class="text"></span>
53
- <span class="jid"></span>
53
+ <span class="jid">#{user.jid}</span>
54
54
  </li>
55
55
  """).appendTo '#users'
56
56
 
57
57
  name = this.userName(user)
58
58
  $('.text', node).text name
59
- $('.jid', node).text user.jid
60
59
  node.attr 'data-name', name
61
- node.attr 'data-jid', user.jid
62
60
  node.click (event) => this.selectUser event.currentTarget
63
61
  node
64
62
 
@@ -105,10 +103,18 @@ class SetupPage
105
103
  $('#beta-header').text 'Users'
106
104
  this.drawUsers()
107
105
  this.drawUserBlankSlate()
106
+ if @api.user.permissions.users
107
+ $('#beta-controls div').show()
108
+ else
109
+ $('#beta-controls div').hide()
108
110
  when 'systems-nav'
109
111
  $('#beta-header').text 'Systems'
110
112
  this.drawUsers()
111
113
  this.drawSystemBlankSlate()
114
+ if @api.user.permissions.systems
115
+ $('#beta-controls div').show()
116
+ else
117
+ $('#beta-controls div').hide()
112
118
 
113
119
  toggleForm: (form, fn) ->
114
120
  form = $(form)
@@ -136,6 +142,12 @@ class SetupPage
136
142
  $('#password-error').text 'Password must be at least 8 characters.'
137
143
  valid = false
138
144
 
145
+ # admin updating a user's password
146
+ if @session.bareJid() != @selected.jid
147
+ if password1 != password2
148
+ $('#password-error').text 'Passwords must match.'
149
+ valid = false
150
+
139
151
  else # new user
140
152
  if node == ''
141
153
  $('#user-name-error').text 'User name is required.'
@@ -160,7 +172,7 @@ class SetupPage
160
172
  valid
161
173
 
162
174
  saveUser: ->
163
- return unless this.validateUser()
175
+ return false unless this.validateUser()
164
176
  user =
165
177
  jid: $('#jid').val()
166
178
  username: $('#user-name').val()
@@ -201,7 +213,7 @@ class SetupPage
201
213
  valid
202
214
 
203
215
  saveSystem: ->
204
- return unless this.validateSystem()
216
+ return false unless this.validateSystem()
205
217
  user =
206
218
  jid: $('#jid').val()
207
219
  username: $('#user-name').val()
@@ -229,14 +241,18 @@ class SetupPage
229
241
 
230
242
  drawUserBlankSlate: ->
231
243
  $('#charlie').empty()
244
+ msg = if @api.user.permissions.users
245
+ 'Select a user account to update or add a new user.'
246
+ else
247
+ 'Select a user account to update.'
248
+
232
249
  $("""
233
250
  <form id="blank-slate">
234
- <p>
235
- Select a user account to edit or add a new user.
236
- </p>
251
+ <p>#{msg}</p>
237
252
  <input type="submit" id="blank-slate-add" value="Add User"/>
238
253
  </form>
239
254
  """).appendTo '#charlie'
255
+ $('#blank-slate-add').remove() unless @api.user.permissions.users
240
256
  $('#blank-slate').submit =>
241
257
  this.drawUserEditor()
242
258
  false
@@ -252,6 +268,7 @@ class SetupPage
252
268
  <input type="submit" id="blank-slate-add" value="Add System"/>
253
269
  </form>
254
270
  """).appendTo '#charlie'
271
+ $('#blank-slate-add').remove() unless @api.user.permissions.systems
255
272
  $('#blank-slate').submit =>
256
273
  this.drawSystemEditor()
257
274
  false
@@ -295,6 +312,7 @@ class SetupPage
295
312
  </div>
296
313
  <div id="charlie" class="primary column x-fill y-fill"></div>
297
314
  """).appendTo '#container'
315
+
298
316
  this.drawUserBlankSlate()
299
317
 
300
318
  $('#setup li').click (event) => this.selectTask event
@@ -308,6 +326,9 @@ class SetupPage
308
326
  new Button '#add-user', ICONS.plus
309
327
  new Button '#remove-user', ICONS.minus
310
328
 
329
+ $('#beta-controls div').hide() unless @api.user.permissions.users
330
+ $('#systems-nav').hide() unless @api.user.permissions.systems
331
+
311
332
  $('#add-user').click =>
312
333
  if $('#users-nav').hasClass 'selected'
313
334
  this.drawUserEditor()
@@ -326,7 +347,7 @@ class SetupPage
326
347
  list: '#users'
327
348
  icon: '#search-users-icon'
328
349
  form: '#search-users-form'
329
- attrs: ['data-jid']
350
+ attrs: ['data-jid', 'data-name']
330
351
  open: fn
331
352
  close: fn
332
353
 
@@ -402,7 +423,9 @@ class SetupPage
402
423
  """).prependTo '#jid-fields'
403
424
 
404
425
  $('#name').focus()
405
-
426
+ if @session.bareJid() != user.jid
427
+ $('#password1-label').text 'Password'
428
+ $('#password2-label').text 'Password Again'
406
429
  $('#jid').val user.jid
407
430
  $('#name').val user.name
408
431
  $('#user-name').val user.jid.split('@')[0]