Projects WIP

This commit is contained in:
Vincent Fiduccia 2015-04-21 18:54:00 -07:00
parent d6ae030828
commit 82d4b86456
25 changed files with 426 additions and 304 deletions

View File

@ -42,6 +42,7 @@ export default Ember.Route.extend({
error: function(err) {
this.controllerFor('application').set('error',err);
this.set('app.showArticles',false);
this.transitionTo('failWhale');
console.log('Application ' + err.stack);
},

View File

@ -13,11 +13,17 @@ export default Ember.Route.extend(AuthenticatedRouteMixin, {
var session = this.get('session');
// Load schemas
return store.find('schema', null, {url: 'schemas'}).then((schemas) => {
session.set(C.SESSION.ACCOUNT_ID, schemas.xhr.getResponseHeader(C.HEADER.ACCOUNT_ID));
var headers = {};
headers[C.HEADER.PROJECT] = C.HEADER.PROJECT_USER_SCOPE;
return store.find('schema', null, {url: 'schemas', headers: headers}).then((schemas) => {
if ( schemas && schemas.xhr )
{
// Save the account ID into session
session.set(C.SESSION.ACCOUNT_ID, schemas.xhr.getResponseHeader(C.HEADER.ACCOUNT_ID));
}
var type = session.get(C.SESSION.USER_TYPE);
var isAdmin = (type === C.USER.TYPE_ADMIN) || (!type && store.hasRecordFor('schema','githubconfig'));
var isAdmin = (type === C.USER.TYPE_ADMIN) || (!this.get('app.authenticationEnabled'));
this.set('app.isAuthenticationAdmin', isAdmin);
return store.find('project', null, {forceReload: true});
@ -40,7 +46,12 @@ export default Ember.Route.extend(AuthenticatedRouteMixin, {
var active = ActiveArrayProxy.create({sourceContent: model});
controller.set('projects', active);
this.selectDefaultProject(controller);
},
selectDefaultProject: function(controller) {
var session = this.get('session');
var active = controller.get('projects');
// Figure out the active project
var project = null;
@ -77,8 +88,7 @@ export default Ember.Route.extend(AuthenticatedRouteMixin, {
else
{
// @TODO Then cry? What happens if you delete the last project?
session.set(C.SESSION.PROJECT, undefined);
controller.set('project', null);
this.send('logout');
}
},
@ -97,18 +107,28 @@ export default Ember.Route.extend(AuthenticatedRouteMixin, {
}
},
selectDefaultProject: function() {
return this.selectDefaultProject(this.get('controller'));
},
refreshProjectDropdown: function() {
this.get('store').find('project', null, {forceReload: true}).then((res) => {
this.set('controller.projects.sourceContent', res);
this.selectDefaultProject(this.get('controller'));
});
},
switchProject: function(projectId) {
switchProject: function(projectId,route) {
this.get('session').set(C.SESSION.PROJECT, projectId);
this.get('store').reset();
this.transitionTo('index');
if ( !projectId )
{
this.selectDefaultProject();
}
this.transitionTo(route||'index');
},
setPageLayout: function(opt) {
this.controller.set('pageName', opt.label || '');
this.controller.set('backRoute', opt.backRoute || null);

View File

@ -1,7 +1,7 @@
import Ember from 'ember';
import GithubUserInfoMixin from 'ui/mixins/github-user-info';
import GithubInfoMixin from 'ui/mixins/github-info';
export default Ember.Component.extend(GithubUserInfoMixin,{
export default Ember.Component.extend(GithubInfoMixin,{
classNames: ['gh-avatar'],
link: true,
});

View File

@ -1,10 +1,8 @@
import Ember from 'ember';
import GithubUserInfoMixin from 'ui/mixins/github-user-info';
import GithubInfoMixin from 'ui/mixins/github-info';
export default Ember.Component.extend(GithubUserInfoMixin,{
export default Ember.Component.extend(GithubInfoMixin,{
classNames: ['gh-block'],
avatar: true,
link: true,
isTeam: Ember.computed.equal('type','team'),
});

View File

@ -33,10 +33,18 @@
{{/if}}
</div>
<div class="gh-block-detail clip">
{{#if description}}
{{description}}
{{#if isTeam}}
{{#if org}}
Team in <a {{bind-attr href=orgUrl}} target="_blank">{{org}}</a> org
{{else}}
You are not a member of this team.
{{/if}}
{{else}}
<i>No Name</i>
{{#if description}}
{{description}}
{{else}}
<i>No Name</i>
{{/if}}
{{/if}}
</div>
</div>

View File

@ -0,0 +1,64 @@
import Ember from 'ember';
export default Ember.Component.extend({
allowTeams: true,
checking: false,
addInput: '',
actions: {
add: function() {
if ( this.get('checking') )
{
return;
}
this.set('checking', true);
var input = this.get('addInput').trim();
this.get('github').find('user_or_org', input).then((info) => {
this.set('addInput','');
this.send('addObject', info);
}).catch(() => {
this.sendAction('onError','Unable to find: ' + input);
}).finally(() => {
this.set('checking', false);
});
},
addObject: function(info) {
this.sendAction('action', Ember.Object.create({
id: info.get('id'),
type: info.get('type'),
}));
}
},
addDisabled: function() {
return this.get('checking') || this.get('addInput').trim().length === 0;
}.property('addInput','checking'),
orgChoices: function() {
var orgs = (this.get('session.orgs')||[]).slice().sort().map(function(id) {
return Ember.Object.create({
id: id,
type: 'org',
teams: []
});
});
(this.get('session.teams')||[]).forEach(function(team) {
var org = orgs.filterProperty('id', team.org)[0];
if ( org )
{
org.teams.pushObject(Ember.Object.create({
id: team.id,
type: 'team',
name: team.name,
}));
}
});
return orgs;
}.property('session.orgs.[]','session.teams.@each.id'),
});

View File

@ -0,0 +1,32 @@
<form {{action "add" on="submit"}}>
<div class="input-group">
{{input type="text" value=addInput placeholder="Add a GitHub user or organization name" class="form-control"}}
<div class="input-group-btn">
{{#if checking}}
<button class="btn btn-primary btn-disabled"><i class="fa fa-circle-o-notch fa-spin"></i></button>
{{else}}
<button class="btn btn-primary" {{action "add"}}><i class="ss-plus"></i></button>
{{/if}}
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-expanded="false"><span class="caret"></span></button>
<ul class="dropdown-menu dropdown-menu-right" role="menu" style="min-width: 250px; max-height: 300px; overflow-y: auto;">
<li role="presentation" class="dropdown-header">Your Organizations and Teams</li>
{{#each item in orgChoices}}
<li>
<a {{action "addObject" item}}>
{{github-block login=item.id link=false}}
</a>
</li>
{{#if allowTeams}}
{{#each team in item.teams}}
<li style="margin-left: 15px;">
<a {{action "addObject" team}}>
<i class="ss-users"></i> {{team.name}}
</a>
</li>
{{/each}}
{{/if}}
{{/each}}
</ul>
</div>
</div>
</form>

View File

@ -11,7 +11,8 @@ export default Ember.Component.extend({
},
showAccessWarning: function() {
return !this.get('app.authenticationEnabled') &&
return this.get('app.showArticles') !== false &&
!this.get('app.authenticationEnabled') &&
this.get('prefs.'+C.ACCESS_WARNING) !== false;
}.property('app.authenticationEnabled','prefs.'+C.ACCESS_WARNING)
});

View File

@ -49,8 +49,9 @@
</div>
<div class="right" style="padding-right: 0;">
<div class="btn-group">
<button type="button" class="btn btn-link dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
<div class="btn-group project-btn">
<div class="text-muted">Project:</div>
<button type="button" class="btn btn-link dropdown-toggle clip" data-toggle="dropdown" aria-expanded="false">
{{#if project}}
{{#with project as p controller="project"}}
<i {{bind-attr class=":fa-fw p.icon"}}></i>&nbsp;{{p.displayName}}
@ -59,18 +60,18 @@
<span class="caret"></span>
</button>
<ul class="dropdown-menu dropdown-menu-right" role="menu">
<li role="presentation" class="dropdown-header">Available Projects:</li>
<li>{{#link-to "projects"}}Manage Projects{{/link-to}}</li>
<li role="presentation" class="divider"></li>
<li role="presentation" class="dropdown-header">Your Projects:</li>
{{#each p in projectChoices itemController="project"}}
<li {{bind-attr class="p.active:disabled p.active:selected"}}>
<a {{action "switchProject" p.id}}>
<i {{bind-attr class=":fa-fw p.listIcon"}}></i>
<a {{action "switchProject" p.id}} class="clip">
<i {{bind-attr class=":fa-fw p.icon"}}></i>
&nbsp;
{{p.displayName}}
</a>
</li>
{{/each}}
<li role="presentation" class="divider"></li>
<li>{{#link-to "projects"}}Manage Projects{{/link-to}}</li>
</ul>
</div>
</div>

View File

@ -0,0 +1,19 @@
import Github from 'ui/utils/github';
export function initialize(container, application) {
var github = Github.create({
// Session isn't automatically injected into GitHub
session: container.lookup('session:main'),
});
// Inject GitHub lookup as 'github' property
container.register('github:main', github, {instantiate: false});
application.inject('controller', 'github', 'github:main');
application.inject('component', 'github', 'github:main');
}
export default {
name: 'github',
after: 'session',
initialize: initialize
};

View File

@ -19,12 +19,17 @@ export function initialize(container, application) {
// Please don't send us www-authenticate headers
out[C.HEADER.NO_CHALLENGE] = C.HEADER.NO_CHALLENGE_VALUE;
// Send the token as the Authorization header
var authValue = session.get(C.SESSION.TOKEN);
if ( authValue )
{
// Send the token as the Authorization header if present
out[C.HEADER.AUTH] = C.HEADER.AUTH_TYPE + ' ' + authValue;
}
else
{
// And something else if not present, so the browser can't send cached basic creds
out[C.HEADER.AUTH] = 'None';
}
// Send the current project id as a header if in a project
var projectId = session.get(C.SESSION.PROJECT);

View File

@ -58,7 +58,14 @@ export default Ember.Controller.extend({
},
}).then(function(res) {
var auth = JSON.parse(res.xhr.responseText);
session.setProperties(auth);
var interesting = {};
C.TOKEN_TO_SESSION_KEYS.forEach((key) => {
if ( typeof auth[key] !== 'undefined' )
{
interesting[key] = auth[key];
}
});
session.setProperties(interesting);
session.set(C.LOGGED_IN, true);
var transition = app.get('afterLoginTransition');
if ( transition )

View File

@ -2,49 +2,16 @@ import Ember from 'ember';
import C from 'ui/utils/constants';
import Cattle from 'ui/utils/cattle';
import Util from 'ui/utils/util';
import GithubLookup from 'ui/utils/github-lookup';
export default Ember.Mixin.create(Cattle.NewOrEditMixin, {
githubLookup: null,
isAdding: false,
addMemberInput: null,
actions: {
addMember: function(item) {
if ( item && typeof item === 'object' )
{
this.send('maybeAddMember', Ember.Object.create({
externalId: item.get('id'),
externalIdType: item.get('type'),
role: C.PROJECT.ROLE_MEMBER,
}));
}
else if ( item && item.length )
{
var lookup = this.get('githubLookup');
if ( !lookup )
{
lookup = GithubLookup.create();
this.set('githubLookup', lookup);
}
checkMember: function(obj) {
var member = Ember.Object.create({
externalId: obj.get('id'),
externalIdType: C.PROJECT.FROM_GITHUB[ obj.get('type') ],
role: C.PROJECT.ROLE_MEMBER
});
this.set('isAdding', true);
lookup.find('user', item).then((info) => {
this.set('addMemberInput','');
this.send('maybeAddMember', Ember.Object.create({
externalId: info.get('id'),
externalIdType: (info.get('type') === 'user' ? C.PROJECT.TYPE_USER : C.PROJECT.TYPE_ORG),
role: C.PROJECT.ROLE_MEMBER,
}));
}).catch(() => {
this.send('error','Unable to find user/org: ' + item);
}).finally(() => {
this.set('isAdding', false);
});
}
},
maybeAddMember: function(member) {
var existing = this.get('members')
.filterProperty('externalIdType', member.get('externalIdType'))
.filterProperty('externalId', member.get('externalId'));
@ -73,30 +40,6 @@ export default Ember.Mixin.create(Cattle.NewOrEditMixin, {
});
}.property(),
orgChoices: function() {
var orgs = this.get('session.orgs').slice().sort().map(function(id) {
return Ember.Object.create({
id: id,
type: C.PROJECT.TYPE_ORG,
teams: []
});
});
this.get('session.teams').forEach(function(team) {
var org = orgs.filterProperty('id', team.org)[0];
if ( org )
{
org.teams.pushObject(Ember.Object.create({
id: team.id,
type: C.PROJECT.TYPE_TEAM,
name: team.name,
}));
}
});
return orgs;
}.property('session.orgs.[]','session.teams.[]'),
hasOwner: function() {
return this.get('members').filterProperty('role', C.PROJECT.ROLE_OWNER).get('length') > 0;
}.property('members.@each.role'),
@ -107,7 +50,7 @@ export default Ember.Mixin.create(Cattle.NewOrEditMixin, {
if ( !this.get('hasOwner') )
{
errors.push('A project must have at least one owner');
errors.push('You must add at least one owner');
}
if ( errors.length )

95
app/mixins/github-info.js Normal file
View File

@ -0,0 +1,95 @@
import Ember from 'ember';
import C from 'ui/utils/constants';
import Util from 'ui/utils/util';
export default Ember.Mixin.create({
type: 'user_or_org',
login: null, // This can't be called id because Ember uses that property..
size: 40,
fallback: null,
name: null,
description: 'Loading...',
org: null,
_avatarUrl: null,
isTeam: Ember.computed.equal('type','team'),
loginOrTypeChanged: function() {
var login = this.get('login');
var type = this.get('type');
var fallback = this.get('fallback');
if ( !type || !login )
{
return;
}
this.setProperties({
'name': login,
'description': 'Loading...',
'_avatarUrl': null,
});
this.get('github').find(type, login).then((entry) => {
this.setProperties({
name: entry.name,
description: entry.description,
_avatarUrl: entry.avatarUrl,
org: entry.org,
});
}).catch((err) => {
if ( fallback && this.get('type') === 'team' )
{
this.setProperties({
name: "(A team you don't have access to)",
org: fallback,
_avatarUrl: null
});
}
else
{
this.setProperties({
name: login,
description: 'Error: ' + err,
org: null,
_avatarUrl: null
});
}
});
}.observes('login','type','fallback').on('init'),
avatarUrl: function(){
var url = this.get('_avatarUrl');
if ( url )
{
url = Util.addQueryParam(url, 's', this.get('size'));
}
return url;
}.property('_avatarUrl','size'),
orgUrl: function() {
var org = this.get('org');
if ( org && this.get('type') === 'team' )
{
return C.GITHUB.URL + 'orgs/' + encodeURIComponent(org);
}
}.property('type','org'),
url: function() {
if ( this.get('type') === 'team' )
{
var entry = this.get('github').teamById(this.get('login'));
if ( entry && entry.slug )
{
return C.GITHUB.URL + 'orgs/' + encodeURIComponent(entry.org) + '/teams/' + encodeURIComponent(entry.slug);
}
}
else
{
return C.GITHUB.URL + encodeURIComponent(this.get('login'));
}
}.property('login'),
});

View File

@ -1,111 +0,0 @@
import Ember from 'ember';
import C from 'ui/utils/constants';
import Util from 'ui/utils/util';
export default Ember.Mixin.create({
type: 'user',
login: null,
size: 40,
name: null,
description: 'Loading...',
_avatarUrl: null,
loginOrTypeChanged: function() {
var self = this;
var session = this.get('session');
var cache = session.get('githubCache')||{};
var login = this.get('login');
var type = this.get('type');
var key = type + ':' + login;
if ( !type || !login )
{
return;
}
// Teams can't be looked up without auth...
if ( type === 'team' )
{
var entry = (session.get('teams')||[]).filterProperty('id', login)[0];
this.set('_avatarUrl', null);
if ( entry )
{
this.set('name', entry.name);
this.set('description', entry.org + ' team');
}
else
{
this.set('name', '('+ login + ')');
this.set('description', '(Unknown team id)');
}
return;
}
this.set('name', login);
if ( cache[key] )
{
gotInfo(cache[key]);
}
else
{
var url = C.GITHUB.PROXY_URL + type + 's/' + login;
this.request(url).then((body) => {
cache[key] = body;
// Sub-keys don't get automatically persisted to the session...
session.set('githubCache', cache);
gotInfo(body);
}, (/*err*/) => {
this.sendAction('notFound', login);
});
}
function gotInfo(body)
{
self.set('description', body.name);
self.set('_avatarUrl', body.avatar_url);
}
}.observes('login','type').on('init'),
avatarUrl: function(){
var url = this.get('_avatarUrl');
if ( url )
{
url = Util.addQueryParam(url, 's', this.get('size'));
}
return url;
}.property('_avatarUrl','size'),
url: function() {
if ( this.get('type') === 'team' )
{
var entry = (this.get('session.teams')||[]).filterProperty('id', this.get('login'))[0];
if ( entry && entry.slug )
{
return C.GITHUB.URL + 'orgs/' + encodeURIComponent(entry.org) + '/teams/' + encodeURIComponent(entry.slug);
}
}
else
{
return C.GITHUB.URL + encodeURIComponent(this.get('login'));
}
}.property('login'),
request: function(url) {
var headers = {};
var authValue = this.get('session').get(C.SESSION.TOKEN);
if ( authValue )
{
headers[C.HEADER.AUTH] = C.HEADER.AUTH_TYPE + ' ' + authValue;
}
return Ember.$.ajax({url: url, headers: headers, dataType: 'json'});
},
});

View File

@ -11,9 +11,9 @@ var ProjectController = Cattle.TransitioningResourceController.extend({
delete: function() {
return this.delete().then(() => {
// If you're in the project that was deleted, go back to the default project
if ( this.get('id') === this.get('session.projectId') )
if ( this.get('id') === this.get('session.'+ C.SESSION.PROJECT) )
{
this.send('switchProject', undefined);
window.location.href = window.location.href;
}
});
},
@ -23,7 +23,12 @@ var ProjectController = Cattle.TransitioningResourceController.extend({
},
deactivate: function() {
return this.doAction('deactivate');
return this.doAction('deactivate').then(() => {
if ( this.get('id') === this.get('session.'+ C.SESSION.PROJECT) )
{
window.location.href = window.location.href;
}
});
},
switchTo: function() {
@ -36,7 +41,11 @@ var ProjectController = Cattle.TransitioningResourceController.extend({
isOrg: Ember.computed.equal('externalIdType', C.PROJECT.TYPE_ORG),
icon: function() {
if ( this.get('active') )
if ( this.get('isDefault') )
{
return 'ss-home';
}
else if ( this.get('active') )
{
return 'ss-openfolder';
}
@ -44,22 +53,15 @@ var ProjectController = Cattle.TransitioningResourceController.extend({
{
return 'ss-folder';
}
}.property('active'),
}.property('active','isDefault'),
listIcon: function() {
if ( this.get('active') )
{
return 'ss-check';
}
else
{
return this.get('icon');
}
}.property('icon','active'),
isDefault: function() {
return this.get('session.' + C.SESSION.PROJECT_DEFAULT) === this.get('id');
}.property('session.' + C.SESSION.PROJECT_DEFAULT, 'id'),
active: function() {
return this.get('session.projectId') === this.get('id');
}.property('session.projectId','id'),
return this.get('session.' + C.SESSION.PROJECT) === this.get('id');
}.property('session' + C.SESSION.PROJECT, 'id'),
canRemove: function() {
return !!this.get('actions.remove') && ['removing','removed','purging','purged'].indexOf(this.get('state')) === -1;
@ -75,14 +77,10 @@ var ProjectController = Cattle.TransitioningResourceController.extend({
{ divider: true },
{ label: 'Restore', icon: '', action: 'restore', enabled: !!a.restore },
{ label: 'Purge', icon: '', action: 'purge', enabled: !!a.purge },
{ divider: true },
{ label: 'Edit', icon: '', action: 'edit', enabled: !!a.update },
];
if ( this.get('app.isAuthenticationAdmin') )
{
choices.pushObject({label: 'Switch to Project', icon: 'ss-openfolder', action: 'switchTo', enabled: this.get('state') === 'active' });
}
choices.pushObject({label: 'Switch to this Project', icon: '', action: 'switchTo', enabled: this.get('state') === 'active' });
return choices;
}.property('actions.{activate,deactivate,update,restore,purge}','canRemove'),

View File

@ -4,4 +4,11 @@ export default Ember.Route.extend({
model: function(params /*, transition*/) {
return this.get('store').find('project', params.project_id);
},
actions: {
didTransition: function() {
this._super();
this.send('setPageLayout', {label: 'All Projects', backRoute: 'projects'});
},
}
});

View File

@ -7,13 +7,17 @@ export default Ember.ObjectController.extend({
isTeam: Ember.computed.equal('externalIdType', C.PROJECT.TYPE_TEAM),
isOrg: Ember.computed.equal('externalIdType', C.PROJECT.TYPE_ORG),
isMyRancher: function() {
return this.get('externalIdType') === C.PROJECT.TYPE_RANCHER && this.get('externalId') === this.get('session').get(C.SESSION.ACCOUNT_ID);
}.property('externalId','externalIdType'),
githubType: function() {
switch ( this.get('externalIdType') )
{
case C.PROJECT.TYPE_USER: return 'user';
case C.PROJECT.TYPE_TEAM: return 'team';
case C.PROJECT.TYPE_ORG: return 'org';
case C.PROJECT.TYPE_RANCHER: return '';
case C.PROJECT.TYPE_RANCHER: return null;
}
}.property('type'),

View File

@ -22,44 +22,20 @@
{{partial "form-divider"}}
{{#if app.authenticationEnabled}}
<div class="row form-group">
<div class="col-sm-12 col-md-2 form-label">
<label>Members</label>
</div>
<div class="col-sm-12 col-md-8">
<form {{action "addMember" on="submit"}}>
<div class="input-group">
{{input type="text" value=addMemberInput placeholder="Add a GitHub user or organization name" class="form-control"}}
<div class="input-group-btn">
{{#if isAdding}}
<button class="btn btn-primary btn-disabled"><i class="ss-loading fa-spin"></i></button>
{{else}}
<button class="btn btn-primary" {{action "addMember" addMemberInput}}><i class="ss-plus"></i></button>
{{/if}}
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-expanded="false"><span class="caret"></span></button>
<ul class="dropdown-menu dropdown-menu-right" role="menu" style="min-width: 250px; max-height: 300px; overflow-y: auto;">
<li role="presentation" class="dropdown-header">Your Organizations and Teams</li>
{{#each item in orgChoices}}
<li>
<a {{action "addMember" item}}>
{{github-block type="org" login=item.id link=false}}
</a>
</li>
{{#each team in item.teams}}
<li style="margin-left: 15px;">
<a {{action "addMember" team}}>
<i class="ss-users"></i> {{team.name}}
</a>
</li>
{{/each}}
{{/each}}
</ul>
</div>
</div>
</form>
</div>
<div class="row form-group">
<div class="col-sm-12 col-md-2 form-label">
<label>Members</label>
</div>
<div class="col-sm-12 col-md-8">
{{#if app.authenticationEnabled}}
{{input-github action="checkMember" onError="error"}}
{{else}}
<p class="help-block">Access Control is not enabled. Everybody is Admin and will be able to use this project.</p>
{{/if}}
</div>
</div>
{{#if app.authenticationEnabled}}
<div class="row">
<div class="col-sm-12 col-md-8 col-md-offset-2">
{{#if members.length}}
@ -76,25 +52,43 @@
{{#each member in members itemController="projectmember"}}
<tr>
<td>
{{github-block type=member.githubType login=member.externalId}}
{{#if member.isRancher}}
{{#if member.isMyRancher}}
My Rancher Account
{{else}}
Rancher Account: {{member.name}} ({{member.externalId}})
{{/if}}
{{else}}
{{github-block type=member.githubType login=member.externalId fallback=member.name}}
{{/if}}
</td>
<td>{{member.displayType}}</td>
<td>
{{view "select"
class="form-control input-sm"
content=roleOptions
value=member.role
optionValuePath="content.value"
optionLabelPath="content.label"
}}
{{#if member.isRancher}}
{{uc-first member.role}}
{{else}}
{{view "select"
class="form-control input-sm"
content=roleOptions
value=member.role
optionValuePath="content.value"
optionLabelPath="content.label"
}}
{{/if}}
</td>
<td>
{{#if app.authenticationEnabled}}
<button class="btn-circle-x btn-sm pull-right gh-action" {{action "removeMember" member}}></button>
{{else}}
&nbsp;
{{/if}}
</td>
<td><button class="btn-circle-x btn-sm pull-right gh-action" {{action "removeMember" member}}></button></td>
</tr>
{{/each}}
</tbody>
</table>
{{else}}
<div class="text-muted">No members.</div>
<div class="text-muted">No members yet, add at least one owner.</div>
{{/if}}
</div>
</div>

View File

@ -3,6 +3,7 @@ import Ember from 'ember';
export default Ember.View.extend({
didInsertElement: function() {
$('BODY').addClass('white');
this.$('INPUT')[0].focus();
this._super();
},

View File

@ -54,7 +54,7 @@
<ul class="list-unstyled gh-block-list">
{{#each user in allowedUsers}}
<li>
{{#github-block type="user" login=user notFound="userNotFound"}}
{{#github-block login=user notFound="userNotFound"}}
<button class="btn-circle-x btn-sm pull-right gh-action" {{action "removeUser" user}}></button>
{{/github-block}}
</li>
@ -88,7 +88,7 @@
<ul class="list-unstyled gh-block-list">
{{#each org in allowedOrganizations}}
<li>
{{#github-block type="org" login=org notFound="orgNotFound"}}
{{#github-block login=org notFound="orgNotFound"}}
<button class="btn-circle-x btn-sm pull-right gh-action" {{action "removeOrg" org}}></button>
{{/github-block}}
</li>

View File

@ -258,6 +258,25 @@ HEADER {
}
}
}
.project-btn {
border: 1px solid #ddd;
margin-right: 20px;
padding: 0 10px;
border-radius: 3px;
max-width: 200px;
.dropdown-toggle {
padding: 0;
max-width: 100%;
}
.dropdown-menu {
max-height: 300px;
max-width: 300px;
overflow-y: auto;
}
}
}
/**********

View File

@ -8,6 +8,7 @@
<div>{{message}}</div>
<div style="margin-top: 50px;">
<a href="#" onclick="window.location.href = window.location.href; return false;">Reload</a> to try again
or <a class="hand" {{action "logout"}}>log out</a>
</div>
</section>
</div>

View File

@ -2,12 +2,6 @@ export default {
LOGGED_IN: 'isLoggedIn',
ACCESS_WARNING: 'accessWarning',
GITHUB: {
URL: 'https://www.github.com/',
API_URL: 'https://api.github.com/',
PROXY_URL: '/github/',
},
SESSION: {
TOKEN: 'jwt',
USER_ID: 'user',
@ -15,8 +9,13 @@ export default {
USER_TYPE: 'userType',
PROJECT: 'projectId',
PROJECT_DEFAULT: 'defaultProject',
GITHUB_CACHE: 'githubCache',
GITHUB_ORGS: 'orgs',
GITHUB_TEAMS: 'teams',
},
TOKEN_TO_SESSION_KEYS: ['accountId', 'defaultProject','jwt','orgs','teams','user','userType'],
HEADER: {
AUTH: 'authorization',
AUTH_TYPE: 'Bearer',
@ -35,6 +34,12 @@ export default {
TYPE_ADMIN: 'admin',
},
GITHUB: {
URL: 'https://www.github.com/',
API_URL: 'https://api.github.com/',
PROXY_URL: '/github/',
},
PROJECT: {
TYPE_RANCHER: 'rancher_id',
TYPE_USER: 'github_user',
@ -42,6 +47,11 @@ export default {
TYPE_ORG: 'github_org',
ROLE_MEMBER: 'member',
ROLE_OWNER: 'owner',
FROM_GITHUB: {
'user': 'github_user',
'team': 'github_team',
'org': 'github_org',
},
},
SETTING: {

View File

@ -3,42 +3,29 @@ import C from 'ui/utils/constants';
export default Ember.Object.extend({
find: function(type, id) {
var session = window.l('session:main');
var cache = session.get('githubCache')||{};
if ( type === 'team' )
{
var entry = (session.get('teams')||[]).filterProperty('id', id)[0];
var entry = this.teamById(id);
if ( entry )
{
return Ember.RSVP.resolve(Ember.Object.create({
id: id,
name: entry.name,
type: 'team',
description: entry.org + ' team',
org: entry.org,
avatarUrl: null,
}));
}
else
{
return Ember.RSVP.resolve(Ember.Object.create({
id: id,
name: '(' + id + ')',
type: 'team',
description: '(Unknown team)',
avatarUrl: null
}));
return Ember.RSVP.reject('Team ' + id + ' not found');
}
}
else
{
type = 'user_or_org';
}
var key = type +':'+ id;
if ( cache[id] )
var cached = this.getCache(id);
if ( cached )
{
return Ember.RSVP.resolve(cache[id]);
return Ember.RSVP.resolve(cached);
}
var url = C.GITHUB.PROXY_URL + 'users/' + id;
@ -51,18 +38,36 @@ export default Ember.Object.extend({
avatarUrl: body.avatar_url,
});
cache[key] = out;
// Sub-keys don't get automatically persisted to the session...
session.set('githubCache', cache);
this.setCache(id,out);
return out;
});
},
getCache: function(id) {
var cache = this.get('session').get(C.SESSION.GITHUB_CACHE)||{};
var entry = cache[id];
if ( entry )
{
return Ember.Object.create(entry);
}
},
setCache: function(id, value) {
var session = this.get('session');
var cache = session.get(C.SESSION.GITHUB_CACHE)||{};
cache[id] = value;
// Sub-keys don't get automatically persisted to the session...
session.set(C.SESSION.GITHUB_CACHE, cache);
},
teamById: function(id) {
return (this.get('session.teams')||[]).filterProperty('id', id)[0];
},
request: function(url) {
var headers = {};
var session = window.l('session:main');
var session = this.get('session');
var authValue = session.get(C.SESSION.TOKEN);
if ( authValue )