Skip to content

Commit

Permalink
User Settings SSH, profile and admin pages
Browse files Browse the repository at this point in the history
  * User can add/delete ssh keys (/settings/ssh)
  * User can change password, email (/settings/admin)
  * User can change name (/settings/profile)
  * Styles match dashboard
  * Customized ssh-key adapter's buildURL for DELETEs

fixes aptible#66 fixes aptible#69
  • Loading branch information
bantic committed Jan 27, 2015
1 parent 4a67624 commit 52b44bf
Show file tree
Hide file tree
Showing 24 changed files with 1,104 additions and 4 deletions.
31 changes: 31 additions & 0 deletions app/adapters/ssh-key.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import AuthAdapter from './auth';
import buildURLWithPrefixMap from '../utils/build-url-with-prefix-map';
import Ember from 'ember';

export default AuthAdapter.extend({
buildURL: buildURLWithPrefixMap({
'users': 'user.id'
}),

// To delete an ssh-key, the adapter should send a DELETE to /ssh_keys/:id,
// but it POSTs to /users/:user_id/ssh_keys.
// Override deleteRecord to remove the "/users/:user_id" from the
// url for a DELETE.
deleteRecord: function(store, type, record){
var id = Ember.get(record, 'id');
var userId = Ember.get(record, 'user.id');

var url = this.buildURL(type.typeKey, id, record);

if (userId) {
url = url.replace("/users/" + userId, "");
}

return this.ajax(url, "DELETE");
},

// In URLs and JSON payloads, use "ssh_keys" instead of "sshKeys"
pathForType: function(type){
return Ember.String.pluralize( Ember.String.decamelize(type) );
}
});
9 changes: 9 additions & 0 deletions app/models/ssh-key.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import DS from 'ember-data';

export default DS.Model.extend({
name: DS.attr('string'),
sshPublicKey: DS.attr('string'),
publicKeyFingerprint: DS.attr('string'),

user: DS.belongsTo('user', {async:true})
});
10 changes: 9 additions & 1 deletion app/models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,18 @@ export default DS.Model.extend({
username: DS.attr('string'),
password: DS.attr('string'),

// used when changing a user's password. Set as an `attr` so that it
// will be sent to the API
currentPassword: DS.attr('string'),

// not persisted, used when changing a user's password
passwordConfirmation: null,

// relationships
token: DS.belongsTo('token', {async: true}),
roles: DS.hasMany('role', {async:true}),
organizations: DS.hasMany('organizations', {async:true}),
organizations: DS.hasMany('organization', {async:true}),
sshKeys: DS.hasMany('ssh-key', {async:true}),

// check ability, returns a promise
// e.g.: user.can('manage', stack).then(function(boolean){ ... });
Expand Down
6 changes: 6 additions & 0 deletions app/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ Router.map(function() {
this.route("logout");
this.route("signup");

this.route('settings', {}, function(){
this.route('admin');
this.route('profile');
this.route('ssh');
});

this.route("verify", {
path: "verify/:verification_code"
});
Expand Down
10 changes: 10 additions & 0 deletions app/settings/admin/controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import Ember from 'ember';

var and = Ember.computed.and;

export default Ember.Controller.extend({
isSavingPassword: and('changingPassword',
'session.currentUser.isSaving'),
isSavingEmail: and('changingEmail',
'session.currentUser.isSaving')
});
58 changes: 58 additions & 0 deletions app/settings/admin/route.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import Ember from 'ember';

export default Ember.Route.extend({
actions: {
changePassword: function(){
var controller = this.controller;

// Changing password is a 2-step process. We show the
// "enter current password" input after clicking the button once
if (!controller.get('changingPassword')) {
controller.set('changingPassword', true);
return;
}

controller.set('passwordError', null);

var user = this.session.get('currentUser');
user.save().then(function(){
controller.set('changingPassword', false);
user.set('passwordConfirmation', null);
user.set('currentPassword', null);

// TODO this will leave the user in a dirty state.
user.set('password', null);
}).catch(function(e){
var message = e.responseJSON ? e.responseJSON.message : e.message;
if (!message) { message = 'Unknown error'; }
controller.set('passwordError',message);
});
},

changeEmail: function(){
var controller = this.controller;

// Changing email is a 2-step process. We show the
// "enter current password" input after clicking the button once
if (!controller.get('changingEmail')) {
controller.set('changingEmail', true);
return;
}

controller.set('emailError', null);

var user = this.session.get('currentUser');
user.save().catch(function(e){
var message = e.responseJSON ? e.responseJSON.message : e.message;
if (!message) { message = 'Unknown error'; }
controller.set('emailError',message);
}).finally(function(){

// Clears the current password input
user.set('currentPassword', null);

controller.set('changingEmail', false);
});
}
}
});
84 changes: 84 additions & 0 deletions app/settings/admin/template.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<div class="panel-group">
<div class="panel panel-default">
<div class="panel-heading"><h3>Change Your Password</h3></div>
<div class="panel-body">
{{#if passwordError}}
<div class="alert alert-warning">
{{passwordError}}
</div>
{{/if}}
<form action="">
<div class="form-group">
<label for="password">New password</label>
{{input type="password"
class="form-control" placholder="New password"
value=session.currentUser.password name="password"}}
</div>
<div class="form-group">
<label for="confirm-password">Confirm new password</label>
{{input type="password"
class="form-control" placholder="New password"
value=session.currentUser.passwordConfirmation
name="confirm-password"}}
</div>
{{#if changingPassword}}
<div class="form-group">
<label for="current-password">Current password</label>
{{input type="password"
class="form-control" placholder="Current password"
value=session.currentUser.currentPassword
name="current-password"}}
</div>
{{/if}}
<div class="form-group">
<button {{action 'changePassword'}}
{{bind-attr disabled=session.currentUser.isSaving}}
class="btn btn-primary" type="submit">

{{#if isSavingPassword}}
Saving
{{else}}
Change password
{{/if}}
</button>
</div>
</form>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading"><h3>Change Your Email</h3></div>
<div class="panel-body">
{{#if emailError}}
<div class="alert alert-warning">
{{emailError}}
</div>
{{/if}}
<form>
<div class="form-group">
<label for="email">Email</label>
{{input type="email"
class="form-control" placeholder="Email"
value=session.currentUser.email name="email"}}
</div>
{{#if changingEmail}}
<div class="form-group">
<label for="password">Please enter your current password</label>
{{input type="password"
class="form-control" placeholder="Current password"
value=session.currentUser.currentPassword
name="current-password"}}
</div>
{{/if}}
<div class="form-group">
<button {{action 'changeEmail'}} class="btn btn-primary" type="submit">
{{#if isSavingEmail}}
Saving
{{else}}
Change email
{{/if}}
</button>
</div>
</form>
</div>
</div>
</div>
7 changes: 7 additions & 0 deletions app/settings/index/route.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Ember from 'ember';

export default Ember.Route.extend({
redirect: function(){
this.replaceWith('settings.profile');
}
});
17 changes: 17 additions & 0 deletions app/settings/profile/route.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Ember from 'ember';

export default Ember.Route.extend({
setupController: function(controller){
var user = this.session.get('currentUser');

controller.set('userName', user.get('name'));
},

actions: {
submit: function(newName){
var user = this.session.get('currentUser');
user.set('name', newName);
user.save();
}
}
});
45 changes: 45 additions & 0 deletions app/settings/profile/template.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<div class="panel-group">
<div class="panel panel-default">
<div class="panel-heading"><h3>Your Profile</h3></div>
<div class="panel-body">
<form action="">
<div class="form-group clearfix update-gravatar">
<label class="block">Profile Picture</label>
<div class="gravatar lg">
{{gravatar-image email=session.currentUser.email}}
</div>

<div class="input-group update-gravatar-info">
<h5>
Use <a href="https://en.gravatar.com/">Gravatar</a> to update your profile picture.
</h5>
<h6>
Your gravatar email is <strong class='email'>{{session.currentUser.email}}</strong>
</h6>
<div>
<a class="btn btn-xs btn-default" href="https://en.gravatar.com/" target="_blank">Change</a>
</div>
</div>
</div>

<div class="form-group">
<label for="name">Name</label>

{{input value=userName name="name" class="form-control"}}
</div>

<div class="form-group">
<button {{action "submit" userName}}
{{bind-attr disabled=session.currentUser.isSaving}}
class="btn btn-primary" type="submit">
{{#if session.currentUser.isSaving}}
Saving
{{else}}
Update profile
{{/if}}
</button>
</div>
</form>
</div>
</div>
</div>
13 changes: 13 additions & 0 deletions app/settings/ssh/controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import Ember from 'ember';

export default Ember.Controller.extend({
error: null,
newKey: null,

// do not show the key that the user is in the progress of creating
validKeys: function(){
return this.get('model').filter(function(key){
return key.get('isLoaded') && !key.get('isDirty');
});
}.property('[email protected]', '[email protected]')
});
53 changes: 53 additions & 0 deletions app/settings/ssh/route.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import Ember from 'ember';

export default Ember.Route.extend({
model: function(){
var user = this.session.get('currentUser');

return user.get('sshKeys');
},

setupController: function(controller, model){
controller.set('model', model);
},

actions: {
addKey: function(){
this.controller.set('newKey', this.store.createRecord('ssh-key', {
user: this.session.get('currentUser')
}));
},

cancelSaveKey: function(){
this.controller.get('newKey').deleteRecord();
this.controller.set('newKey', null);
this.controller.set('error', null);
},

saveKey: function(){
var controller = this.controller;

if (controller.get('newKey.isSaving')) { return; }

var key = this.controller.get('newKey');

key.save().then(function(){
controller.set('error', null); // clear error
controller.set('newKey', null); // clear key
}).catch(function(e){
controller.set('error', e.message);
});
},

deleteKey: function(key){
var controller = this.controller;

key.destroyRecord().then(function(){
controller.set('error', null);
}).catch(function(){
controller.set('error', 'There was an error deleting this key.');
key.rollback();
});
}
}
});
Loading

0 comments on commit 52b44bf

Please sign in to comment.