mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-27 18:52:14 +03:00
Merge pull request #3189 from morficus/user-managment-screen-first-pass
User managment screen, first pass
This commit is contained in:
commit
7f33eb3f41
@ -30,6 +30,7 @@
|
|||||||
"validator": true,
|
"validator": true,
|
||||||
"ic": true,
|
"ic": true,
|
||||||
"_": true,
|
"_": true,
|
||||||
"NProgress": true
|
"NProgress": true,
|
||||||
|
"moment": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
18
core/client/controllers/settings/users/index.js
Normal file
18
core/client/controllers/settings/users/index.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
/*global alert */
|
||||||
|
var UsersIndexController = Ember.ArrayController.extend({
|
||||||
|
activeUsers: function () {
|
||||||
|
return this.content.filterBy('status', 'active');
|
||||||
|
}.property('model'),
|
||||||
|
|
||||||
|
invitedUsers: function () {
|
||||||
|
return this.content.filterBy('status', 'invited');
|
||||||
|
}.property('model'),
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
addUser: function () {
|
||||||
|
alert('@TODO: needs to show the "add user" modal - see issue #3079 on GitHub');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default UsersIndexController;
|
@ -3,7 +3,10 @@ var SettingsUserController = Ember.ObjectController.extend({
|
|||||||
|
|
||||||
user: Ember.computed.alias('model'),
|
user: Ember.computed.alias('model'),
|
||||||
|
|
||||||
|
email: Ember.computed.readOnly('user.email'),
|
||||||
|
|
||||||
coverDefault: '/shared/img/user-cover.png',
|
coverDefault: '/shared/img/user-cover.png',
|
||||||
|
|
||||||
cover: function () {
|
cover: function () {
|
||||||
// @TODO: add {{asset}} subdir path
|
// @TODO: add {{asset}} subdir path
|
||||||
var cover = this.get('user.cover');
|
var cover = this.get('user.cover');
|
||||||
@ -19,10 +22,31 @@ var SettingsUserController = Ember.ObjectController.extend({
|
|||||||
|
|
||||||
image: function () {
|
image: function () {
|
||||||
// @TODO: add {{asset}} subdir path
|
// @TODO: add {{asset}} subdir path
|
||||||
return 'background-image: url(' + this.getWithDefault('user.image', '/shared/img/user-image.png') + ')';
|
return 'background-image: url(' + this.getWithDefault('user.image', '/shared/img/user-image.png') + ')';
|
||||||
}.property('user.image'),
|
}.property('user.image'),
|
||||||
|
|
||||||
|
imageUrl: function () {
|
||||||
|
// @TODO: add {{asset}} subdir path
|
||||||
|
return this.getWithDefault('user.image', '/shared/img/user-image.png');
|
||||||
|
}.property('user.image'),
|
||||||
|
|
||||||
|
last_login: function () {
|
||||||
|
return moment(this.get('user.last_login')).fromNow();
|
||||||
|
}.property('user.last_login'),
|
||||||
|
|
||||||
|
created_at: function () {
|
||||||
|
return moment(this.get('user.created_at')).fromNow();
|
||||||
|
}.property('user.created_at'),
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
|
revoke: function () {
|
||||||
|
alert('@TODO: revoke users invitation');
|
||||||
|
},
|
||||||
|
|
||||||
|
resend: function () {
|
||||||
|
alert('@TODO: resend users invitation');
|
||||||
|
},
|
||||||
|
|
||||||
save: function () {
|
save: function () {
|
||||||
var user = this.get('user'),
|
var user = this.get('user'),
|
||||||
self = this;
|
self = this;
|
@ -30,7 +30,9 @@ Router.map(function () {
|
|||||||
});
|
});
|
||||||
this.resource('settings', function () {
|
this.resource('settings', function () {
|
||||||
this.route('general');
|
this.route('general');
|
||||||
this.route('user');
|
this.resource('settings.users', { path: '/users' }, function () {
|
||||||
|
this.route('user', { path: '/:slug' });
|
||||||
|
});
|
||||||
this.route('apps');
|
this.route('apps');
|
||||||
});
|
});
|
||||||
this.route('debug');
|
this.route('debug');
|
||||||
|
3
core/client/routes/settings/users.js
Normal file
3
core/client/routes/settings/users.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
var UsersRoute = Ember.Route.extend(Ember.SimpleAuth.AuthenticatedRouteMixin);
|
||||||
|
|
||||||
|
export default UsersRoute;
|
8
core/client/routes/settings/users/index.js
Normal file
8
core/client/routes/settings/users/index.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
var UsersIndexRoute = Ember.Route.extend(Ember.SimpleAuth.AuthenticatedRouteMixin, {
|
||||||
|
|
||||||
|
model: function () {
|
||||||
|
return this.store.find('user');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default UsersIndexRoute;
|
@ -18,7 +18,7 @@
|
|||||||
<span class="name">{{session.user.name}}</span>
|
<span class="name">{{session.user.name}}</span>
|
||||||
{{/gh-popover-button}}
|
{{/gh-popover-button}}
|
||||||
{{#gh-popover tagName="ul" classNames="overlay" name="user-menu" closeOnClick="true"}}
|
{{#gh-popover tagName="ul" classNames="overlay" name="user-menu" closeOnClick="true"}}
|
||||||
<li class="usermenu-profile">{{#link-to "settings.user"}}Your Profile{{/link-to}}</li>
|
<li class="usermenu-profile">{{#link-to "settings.users.user" session.user.slug}}Your Profile{{/link-to}}</li>
|
||||||
<li class="divider"></li>
|
<li class="divider"></li>
|
||||||
<li class="usermenu-help"><a href="http://support.ghost.org/">Help / Support</a></li>
|
<li class="usermenu-help"><a href="http://support.ghost.org/">Help / Support</a></li>
|
||||||
<li class="divider"></li>
|
<li class="divider"></li>
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
{{/view}}
|
{{/view}}
|
||||||
|
|
||||||
{{#view "item-view" tagName="li" class="users"}}
|
{{#view "item-view" tagName="li" class="users"}}
|
||||||
{{#link-to "settings.user"}}User{{/link-to}}
|
{{#link-to "settings.users"}}Users{{/link-to}}
|
||||||
{{/view}}
|
{{/view}}
|
||||||
|
|
||||||
{{#if showApps}}
|
{{#if showApps}}
|
||||||
|
63
core/client/templates/settings/users/index.hbs
Normal file
63
core/client/templates/settings/users/index.hbs
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
<header class="fade-in">
|
||||||
|
<button class="button-back">Back</button>
|
||||||
|
<h2 class="title">Users</h2>
|
||||||
|
<section class="page-actions">
|
||||||
|
<a class="button-add" href="#" {{action "addUser"}} >New User</a>
|
||||||
|
</section>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section class="content fade-in settings-users">
|
||||||
|
<section class="object-list">
|
||||||
|
|
||||||
|
<h4 class="object-list-title">Invited users</h4>
|
||||||
|
|
||||||
|
{{#each invitedUsers itemController="settings/users/user"}}
|
||||||
|
<div class="object-list-item">
|
||||||
|
<span class="object-list-item-icon icon-mail">ic</span>
|
||||||
|
|
||||||
|
<div class="object-list-item-body">
|
||||||
|
<span class="name">{{email}}</span><br>
|
||||||
|
<span class="description">Invitation sent: {{created_at}}</span>
|
||||||
|
</div>
|
||||||
|
<aside class="object-list-item-aside">
|
||||||
|
<a class="object-list-action" href="#" {{action "revoke"}}>Revoke</a>
|
||||||
|
<a class="object-list-action" href="#" {{action "resend"}}>Resend</a>
|
||||||
|
</aside>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<div class="object-list-item">
|
||||||
|
No invited users.
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="object-list">
|
||||||
|
|
||||||
|
<h4 class="object-list-title">Active users</h4>
|
||||||
|
|
||||||
|
|
||||||
|
{{#each activeUsers itemController="settings/users/user"}}
|
||||||
|
<div class="object-list-item">
|
||||||
|
<img class="object-list-item-figure"
|
||||||
|
src="{{unbound imageUrl}}"
|
||||||
|
alt="Photo of {{unbound name}}" />
|
||||||
|
|
||||||
|
<div class="object-list-item-body">
|
||||||
|
{{#link-to 'settings.users.user' slug class="ember-view name" }}
|
||||||
|
{{user.name}}
|
||||||
|
{{/link-to}}
|
||||||
|
<br>
|
||||||
|
<span class="description">Last seen: {{unbound last_login}}</span>
|
||||||
|
</div>
|
||||||
|
<!-- @TODO: replace these with real access level once API and data model are updated -->
|
||||||
|
<aside class="object-list-item-aside">
|
||||||
|
<span class="role-label editor">Editor</span>
|
||||||
|
<span class="role-label owner">Owner</span>
|
||||||
|
</aside>
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
|
</section>
|
||||||
|
</section>
|
@ -30,6 +30,7 @@ var DEBUG = false, // TOGGLE THIS TO GET MORE SCREENSHOTS
|
|||||||
url = 'http://' + host + (noPort ? '/' : ':' + port + '/'),
|
url = 'http://' + host + (noPort ? '/' : ':' + port + '/'),
|
||||||
newUser = {
|
newUser = {
|
||||||
name: 'Test User',
|
name: 'Test User',
|
||||||
|
slug: 'test-user',
|
||||||
email: email,
|
email: email,
|
||||||
password: password
|
password: password
|
||||||
},
|
},
|
||||||
|
@ -54,7 +54,7 @@ CasperTest.begin('Admin navigation bar is correct', 27, function suite(test) {
|
|||||||
test.assertExists('#usermenu li.usermenu-profile a', 'Profile menu item exists');
|
test.assertExists('#usermenu li.usermenu-profile a', 'Profile menu item exists');
|
||||||
test.assertSelectorHasText('#usermenu li.usermenu-profile a', 'Your Profile',
|
test.assertSelectorHasText('#usermenu li.usermenu-profile a', 'Your Profile',
|
||||||
'Profile menu item has correct text');
|
'Profile menu item has correct text');
|
||||||
test.assertEquals(profileHref, '/ghost/settings/user/', 'Profile href is correct');
|
test.assertEquals(profileHref, '/ghost/settings/users/' + newUser.slug + '/', 'Profile href is correct');
|
||||||
|
|
||||||
test.assertExists('#usermenu li.usermenu-help a', 'Help menu item exists');
|
test.assertExists('#usermenu li.usermenu-help a', 'Help menu item exists');
|
||||||
test.assertSelectorHasText('#usermenu li.usermenu-help a', 'Help / Support', 'Help menu item has correct text');
|
test.assertSelectorHasText('#usermenu li.usermenu-help a', 'Help / Support', 'Help menu item has correct text');
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
// These classes relate to elements which only appear when a given tab is loaded.
|
// These classes relate to elements which only appear when a given tab is loaded.
|
||||||
// These are used to check that a switch to a tab is complete, or that we are on the right tab.
|
// These are used to check that a switch to a tab is complete, or that we are on the right tab.
|
||||||
var generalTabDetector = '.settings-content form#settings-general',
|
var generalTabDetector = '.settings-content form#settings-general',
|
||||||
userTabDetector = '.settings-content form.user-profile';
|
usersTabDetector = '.settings-content .settings-users';
|
||||||
|
|
||||||
CasperTest.begin('Settings screen is correct', 17, function suite(test) {
|
CasperTest.begin('Settings screen is correct', 17, function suite(test) {
|
||||||
casper.thenOpenAndWaitForPageLoad('settings', function testTitleAndUrl() {
|
casper.thenOpenAndWaitForPageLoad('settings', function testTitleAndUrl() {
|
||||||
@ -30,11 +30,11 @@ CasperTest.begin('Settings screen is correct', 17, function suite(test) {
|
|||||||
|
|
||||||
casper.then(function testSwitchingTabs() {
|
casper.then(function testSwitchingTabs() {
|
||||||
casper.thenClick('.settings-menu .users a');
|
casper.thenClick('.settings-menu .users a');
|
||||||
casper.waitForSelector(userTabDetector, function then () {
|
casper.waitForSelector(usersTabDetector, function then () {
|
||||||
// assert that the right menu item is active
|
// assert that the right menu item is active
|
||||||
test.assertExists('.settings-menu .users.active', 'User tab is active');
|
test.assertExists('.settings-menu .users.active', 'User tab is active');
|
||||||
test.assertDoesntExist('.settings-menu .general.active', 'General tab is not active');
|
test.assertDoesntExist('.settings-menu .general.active', 'General tab is not active');
|
||||||
}, casper.failOnTimeout(test, 'waitForSelector `userTabDetector` timed out'));
|
}, casper.failOnTimeout(test, 'waitForSelector `usersTabDetector` timed out'));
|
||||||
|
|
||||||
casper.thenClick('.settings-menu .general a');
|
casper.thenClick('.settings-menu .general a');
|
||||||
casper.waitForSelector(generalTabDetector, function then () {
|
casper.waitForSelector(generalTabDetector, function then () {
|
||||||
@ -368,4 +368,4 @@ CasperTest.begin('General settings validation is correct', 7, function suite(tes
|
|||||||
// casper.waitForSelectorTextChange('.notification-error', function onSuccess() {
|
// casper.waitForSelectorTextChange('.notification-error', function onSuccess() {
|
||||||
// test.assertSelectorHasText('.notification-error', 'is too long');
|
// test.assertSelectorHasText('.notification-error', 'is too long');
|
||||||
// }, casper.failOnTimeout(test, 'Location field length error did not appear', 2000));
|
// }, casper.failOnTimeout(test, 'Location field length error did not appear', 2000));
|
||||||
//});
|
//});
|
||||||
|
Loading…
Reference in New Issue
Block a user