Merge pull request #44 from vincent99/reskin

Create Project
This commit is contained in:
Vincent Fiduccia 2015-02-04 10:33:15 -07:00
commit f85cb02609
32 changed files with 430 additions and 49 deletions

View File

@ -6,22 +6,45 @@ export default Ember.Mixin.create({
login: null,
size: 40,
name: 'Loading...',
name: null,
description: 'Loading...',
_avatarUrl: null,
loginOrTypeChanged: function() {
var self = this;
var session = this.get('session');
var cache = self.get('session').get('avatarCache')||{};
var cache = session.get('avatarCache')||{};
var login = this.get('login');
if ( !login )
var type = this.get('type');
var key = type + ':' + login;
if ( !type || !login )
{
return;
}
var type = this.get('type');
var key = type + ':' + login;
// Teams can't be looked up without auth...
if ( type === 'team' )
{
var entry = (session.get('teams')||[]).filterProperty('name', 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] )
{
@ -30,21 +53,21 @@ export default Ember.Mixin.create({
else
{
var url = C.GITHUB_API_URL + type + 's/' + login;
Ember.$.ajax({url: url, dataType: 'json'}).then(function(body) {
Ember.$.ajax({url: url, dataType: 'json'}).then((body) => {
cache[key] = body;
// Sub-keys don't get automatically persisted to the session...
self.get('session').set('avatarCache', cache);
session.set('avatarCache', cache);
gotInfo(body);
}, function() {
self.sendAction('notFound', login);
}, () => {
this.sendAction('notFound', login);
});
}
function gotInfo(body)
{
self.set('name', body.name);
self.set('description', body.name);
self.set('_avatarUrl', body.avatar_url);
}
}.observes('login','type').on('init'),

View File

@ -16,7 +16,7 @@
</div>
<div>
<div class="clear-fix">
<div class="clearfix">
<label><i class="fa fa-code"></i> API Keys</label>
<div style="float: right">
<button {{action "newApikey"}} class="btn btn-primary"><i class="fa fa-plus"></i> Create an API key</button>

View File

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

View File

@ -2,21 +2,39 @@
{{#if avatar}}
<div class="gh-avatar">
{{#if avatarUrl}}
<a {{bind-attr href=url}} target="_blank">
{{#if link}}
<a {{bind-attr href=url}} target="_blank">
<img {{bind-attr src=avatarUrl width=size height=size}}>
</a>
{{else}}
<img {{bind-attr src=avatarUrl width=size height=size}}>
</a>
{{/if}}
{{else}}
<div class="gh-placeholder"></div>
{{#if isTeam}}
<div class="gh-placeholder">
<i class="fa fa-2x fa-github-alt"></i>
</div>
{{else}}
<div class="gh-placeholder"></div>
{{/if}}
{{/if}}
</div>
{{/if}}
<div class="gh-block-content">
<div class="clip">
<a {{bind-attr href=url}} target="_blank">{{login}}</a>
{{#if isTeam}}
{{name}}
{{else}}
{{#if link}}
<a {{bind-attr href=url}} target="_blank">{{name}}</a>
{{else}}
{{name}}
{{/if}}
{{/if}}
</div>
<div class="text-muted clip">
{{#if name}}
{{name}}
{{#if description}}
{{description}}
{{else}}
<i>No Name</i>
{{/if}}

View File

@ -1,5 +1,6 @@
<div class="host-header">
<div {{bind-attr class=":host-overlay stateBackground"}}>
<div class="host-name">{{model.displayName}}</div>
{{resource-actions model=model choices=model.primaryActions big=true}}
</div>

View File

@ -52,8 +52,6 @@
</a>
</li>
{{/each}}
<li role="presentation" class="divider"></li>
<li>{{#link-to "projects"}}Manage Projects{{/link-to}}</li>
</ul>
</div>
</div>

View File

@ -98,7 +98,7 @@ var ContainerController = Cattle.TransitioningResourceController.extend({
choices.push(byName('purge'));
}
choices.push({ tooltip: 'Details', icon: 'fa-chevron-right', action: 'detail', enabled: true });
choices.push({ tooltip: 'Details', icon: 'fa-info-circle', action: 'detail', enabled: true });
return choices;
function byName(name) {

View File

@ -61,7 +61,7 @@ var HostController = Cattle.TransitioningResourceController.extend({
choices.push(byName('promptDelete'));
}
choices.push({ tooltip: 'Details', icon: 'fa-chevron-right', action: 'detail', enabled: true });
choices.push({ tooltip: 'Details', icon: 'fa-info', action: 'detail', enabled: true });
return choices;
function byName(name) {

View File

@ -41,7 +41,10 @@
<section>
<div class="well instances">
<label>Containers</label>
<div class="clearfix">
<div class="pull-right">{{#link-to "containers.new" (query-params hostId=model.id) class="btn btn-primary btn-sm"}}<i class="fa fa-plus"></i> Add a Container{{/link-to}}</div>
<label>Containers</label>
</div>
{{#each col in view.columns}}
<div class="instance-column">
{{#each item in col itemController="container"}}

View File

@ -53,13 +53,15 @@ export default Ember.View.extend({
var out = [];
var i;
for ( i = 0 ; i < columnCount ; i++ )
{
out.push([]);
}
var instances = this.get('context.instances');
if ( instances )
{
for ( i = 0 ; i < columnCount ; i++ )
{
out.push([]);
}
for ( i = 0 ; i < instances.get('length') ; i++ )
{

View File

@ -1,15 +1,44 @@
import Ember from 'ember';
import Cattle from 'ui/utils/cattle';
import C from 'ui/utils/constants';
var ProjectController = Cattle.TransitioningResourceController.extend({
actions: {
edit: function() {
this.transitionToRoute('project.edit',this.get('id'));
},
delete: function() {
return this.delete();
},
restore: function() {
return this.doAction('restore');
},
purge: function() {
return this.doAction('purge');
},
promptDelete: function() {
this.transitionToRoute('project.delete', this.get('id'));
},
},
isDefault: Ember.computed.equal('externalIdType', C.PROJECT_TYPE_DEFAULT),
isUser: Ember.computed.equal('externalIdType', C.PROJECT_TYPE_USER),
isTeam: Ember.computed.equal('externalIdType', C.PROJECT_TYPE_TEAM),
isOrg: Ember.computed.equal('externalIdType', C.PROJECT_TYPE_ORG),
icon: function() {
var icon = 'fa-question-circle';
switch ( this.get('externalIdType') )
{
case 'default': icon = 'fa-home'; break;
case 'project:github_user': icon = 'fa-github'; break;
case 'project:github_team': icon = 'fa-users'; break;
case 'project:github_org': icon = 'fa-building'; break;
case C.PROJECT_TYPE_DEFAULT: icon = 'fa-home'; break;
case C.PROJECT_TYPE_USER: icon = 'fa-github'; break;
case C.PROJECT_TYPE_TEAM: icon = 'fa-users'; break;
case C.PROJECT_TYPE_ORG: icon = 'fa-building'; break;
}
return icon;
@ -26,12 +55,61 @@ var ProjectController = Cattle.TransitioningResourceController.extend({
}
}.property('icon','active'),
githubType: function() {
switch (this.get('externalIdType') )
{
case C.PROJECT_TYPE_DEFAULT: return 'user';
case C.PROJECT_TYPE_USER: return 'user';
case C.PROJECT_TYPE_TEAM: return 'team';
case C.PROJECT_TYPE_ORG: return 'org';
}
}.property('externalIdType'),
githubLogin: function() {
var type = this.get('externalIdType');
if ( type === C.PROJECT_TYPE_DEFAULT )
{
return this.get('session.user');
}
return this.get('externalId');
}.property('externalIdType', 'externalId'),
active: function() {
return this.get('session.projectId') === this.get('id');
}.property('session.projectId','id')
}.property('session.projectId','id'),
availableActions: function() {
var a = this.get('actions');
var choices = [
// { tooltip: 'Edit', icon: 'fa-edit', action: 'edit', enabled: !!a.update },
{ tooltip: 'Restore', icon: 'fa-ambulance', action: 'restore', enabled: !!a.restore },
{ tooltip: 'Delete', icon: 'fa-trash-o', action: 'promptDelete', enabled: !!a.remove, altAction: 'delete' },
{ tooltip: 'Purge', icon: 'fa-fire', action: 'purge', enabled: !!a.purge },
];
return choices;
}.property('actions.{update,restore,remove,purge}'),
});
ProjectController.reopenClass({
stateMap: {
'activating': {icon: 'fa-ticket', color: 'text-danger'},
'active': {icon: 'fa-circle-o', color: 'text-info'},
'deactivating': {icon: 'fa-adjust', color: 'text-danger'},
'inactive': {icon: 'fa-stop', color: 'text-danger'},
'purged': {icon: 'fa-fire', color: 'text-danger'},
'purging': {icon: 'fa-fire', color: 'text-danger'},
'registering': {icon: 'fa-ticket', color: 'text-danger'},
'removed': {icon: 'fa-trash', color: 'text-danger'},
'removing': {icon: 'fa-trash', color: 'text-danger'},
'requested': {icon: 'fa-ticket', color: 'text-danger'},
'restoring': {icon: 'fa-trash', color: 'text-danger'},
'updating-active': {icon: 'fa-circle-o', color: 'text-info'},
'updating-inactive':{icon: 'fa-warning', color: 'text-danger'},
}
});
export default ProjectController;

View File

@ -0,0 +1,22 @@
import OverlayRoute from 'ui/pods/overlay/route';
export default OverlayRoute.extend({
renderTemplate: function() {
this.render('confirmDelete', {
into: 'application',
outlet: 'overlay',
controller: 'project'
});
},
actions: {
confirm: function() {
this.controllerFor('project').send('delete');
this.send('goToPrevious');
},
cancel: function() {
this.send('goToPrevious');
}
}
});

View File

@ -1,4 +1,9 @@
import Cattle from 'ui/utils/cattle';
export default Cattle.TransitioningResource.extend({
type: 'project',
name: null,
description: null,
externalId: null,
externalIdType: null
});

View File

@ -0,0 +1,59 @@
import Ember from 'ember';
import C from 'ui/utils/constants';
import Cattle from 'ui/utils/cattle';
export default Ember.ObjectController.extend(Cattle.NewOrEditMixin, {
actions: {
selectOwner: function(type,login) {
this.beginPropertyChanges();
this.set('externalId', login);
this.set('externalIdType', type);
this.endPropertyChanges();
},
},
typeUser: C.PROJECT_TYPE_USER,
typeTeam: C.PROJECT_TYPE_TEAM,
typeOrg: C.PROJECT_TYPE_ORG,
ownerChoices: function() {
var out = [];
var externalIdType = this.get('externalIdType');
var externalId = this.get('externalId');
out.pushObject({
type: C.PROJECT_TYPE_USER,
githubType: 'user',
login: this.get('session.user'),
active: (externalIdType === C.PROJECT_TYPE_USER && externalId === this.get('session.user')),
});
/* @TODO fix team uniqueness, https://github.com/rancherio/cattle/issues/215
out.pushObjects(this.get('session.teams').map(function(team) {
return {
type: C.PROJECT_TYPE_TEAM,
githubType: 'team',
login: team.name,
active: (externalIdType === C.PROJECT_TYPE_TEAM && externalId === team.name),
};
}));
*/
out.pushObjects(this.get('session.orgs').map(function(org) {
return {
type: C.PROJECT_TYPE_ORG,
githubType: 'org',
login: org,
active: (externalIdType === C.PROJECT_TYPE_ORG && externalId === org),
};
}));
return out;
}.property('session.user','session.teams.@each.name','session.orgs.[]','externalId','externalIdType'),
doneSaving: function() {
var out = this._super();
this.send('goToPrevious');
return out;
},
});

View File

@ -0,0 +1,29 @@
import OverlayRoute from 'ui/pods/overlay/route';
export default OverlayRoute.extend({
actions: {
cancel: function() {
this.send('goToPrevious');
},
},
model: function(/*params, transition*/) {
var model = this.get('store').createRecord({
type: 'project',
externalIdType: 'project:github_user',
externalId: this.get('session.user'),
});
return model;
},
setupController: function(controller,model) {
this._super();
controller.set('model', model);
controller.set('editing',false);
},
renderTemplate: function() {
this.render('projects/new', {into: 'application', outlet: 'overlay'});
},
});

View File

@ -0,0 +1,35 @@
<h2>Create Project</h2>
{{#if error}}
<div class="alert alert-danger">
<i style="float: left;" class="fa fa-exclamation-circle"></i>
<p style="margin-left: 50px">{{error}}</p>
</div>
{{/if}}
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="name">Name</label>
{{input id="name" type="text" value=name classNames="form-control" placeholder="e.g. app01"}}
</div>
<div class="form-group">
<label for="description">Description</label>
{{textarea id="description" value=description classNames="form-control no-resize" rows="3" placeholder="e.g. It serves the webs"}}
</div>
</div>
<div class="col-md-6">
<label for="name">Choose an Owner</label>
<div class="list-group" id="ownerChoices">
{{#each choice in ownerChoices}}
<a {{action "selectOwner" choice.type choice.login}} {{bind-attr class=":list-group-item choice.active:active"}}>
{{github-block link=false type=choice.githubType login=choice.login}}
</a>
{{/each}}
</div>
</div>
</div>
{{partial "save-cancel"}}
{{partial "overlay-close"}}

View File

@ -0,0 +1,13 @@
import Overlay from 'ui/pods/overlay/view';
export default Overlay.extend({
actions: {
overlayClose: function() {
this.get('controller').send('cancel');
},
overlayEnter: function() {
this.get('controller').send('save');
},
},
});

View File

@ -1,8 +1,19 @@
import AuthenticatedRouteMixin from 'ui/mixins/authenticated-route';
import Ember from 'ember';
export default Ember.Route.extend({
export default Ember.Route.extend(AuthenticatedRouteMixin, {
renderTemplate: function() {
this.send('setPageName','Projects');
this._super();
this.send('setPageName','Manage Projects');
},
model: function() {
return this.get('store').findAll('project');
},
actions: {
newProject: function() {
this.transitionTo('projects.new');
}
}
});

View File

@ -1 +1,49 @@
{{outlet}}
<section>
{{#if error}}
<div class="alert alert-danger">
<i style="float: left;" class="fa fa-lg fa-exclamation-circle"></i>
<p style="margin-left: 50px">{{error}}</p>
</div>
{{/if}}
<div class="well">
<p>Rancher supports grouping resources into multiple <b>projects</b>. Each project is owned by a GitHub user{{!, team,}} or organization, and has its own hosts and other resources.</p>
<p>For example, you might create separate &quot;dev&quot;, &quot;test&quot;, and &quot;production&quot; projects to keep logical environments isolated from each other, and restrict the &quot;production&quot; project to a small team of people.</p>
<p><b>Note:</b> You cannot currently delete projects. (The cloud only scales up)</p>
</div>
<div>
<div class="clearfix">
<div style="float: right">
<button {{action "newProject"}} class="btn btn-primary"><i class="fa fa-plus"></i> Create a project</button>
</div>
</div>
<table class="table fixed" style="margin-bottom: 0">
<tr>
<th width="120">State</th>
<th>Name<br/><span class="text-muted">Description</span></th>
<th>Owner</th>
<th width="140">&nbsp;</th>
</tr>
{{#each p in arrangedContent itemController="project"}}
<tr class="resource-action-hover">
<td {{bind-attr class="p.stateColor"}}>
<i {{bind-attr class=":fa p.stateIcon"}}></i> {{p.displayState}}
</td>
<td>
{{#if p.name}}{{p.displayName}}{{else}}<span class="text-muted text-italic">No name</span>{{/if}}
<p class="text-muted">{{#if p.description}}{{p.description}}{{else}}<span class="text-italic">No description</span>{{/if}}</p>
</td>
<td>
{{github-block type=p.githubType login=p.githubLogin}}
</td>
<td align="right">
{{resource-actions model=p choices=p.availableActions}}
</td>
</tr>
{{else}}
<tr><td colspan="5" class="text-center text-muted">You don't have any Projects yet.</td></tr>
{{/each}}
</table>
</div>
</section>

View File

@ -14,9 +14,15 @@ Router.map(function() {
this.route('authenticated', { path: '/'}, function() {
this.resource('settings', function() {
this.route('auth');
this.resource('projects', function() {
this.route('new');
});
});
this.resource('projects', { path: '/projects' }, function() {
this.route('new');
});
this.resource("project", { path: '/projects/:project_id' }, function() {
this.route("edit");
this.route("delete");
});
this.resource('hosts', { path: '/hosts'}, function() {

View File

@ -134,3 +134,7 @@ HR {
color: $brand-success;
font-weight: bold;
}
.list-group-item.active .text-muted {
color: inherit;
}

View File

@ -42,5 +42,12 @@
height: 40px;
border: 1px solid #aaa;
border-radius: 3px;
line-height: 40px;
text-align: center;
.fa {
line-height: 36px;
color: #333;
}
}
}

View File

@ -11,7 +11,7 @@ $instance_action: #ededed;
.hosts {
overflow: visible;
white-space: no-wrap;
white-space: nowrap;
}
.host-zone {
@ -60,7 +60,6 @@ $instance_action: #ededed;
border: 1px solid $host_border;
border-top-width: 2px;
border-radius: 5px;
min-height: 140px;
.host-header {
display: block;
@ -106,7 +105,7 @@ $instance_action: #ededed;
right: 0;
height: 74px;
z-index: 1;
padding-top: 15px;
padding-top: 5px;
text-align: center;
}
@ -140,12 +139,11 @@ $instance_action: #ededed;
.add-host {
color: #5fcf80;
background-color: transparent;
border-width: 2px;
border-style: dotted;
border: 2px dashed;
text-align: center;
cursor: pointer;
cursor: hand;
padding: 10px;
&:hover {
color: #999;

View File

@ -144,13 +144,13 @@ HEADER {
background-color: $body_bg;
.full-height {
line-height: $header_height;
line-height: $header_height - 1;
}
H2 {
font-weight: normal;
margin: 0;
line-height: $header_height;
line-height: $header_height - 1;
color: $header_text;
padding-left: 20px;
}
@ -260,7 +260,7 @@ SECTION {
font-family: Consolas, "Andale Mono", "Lucida Console", Monaco, "Courier New", Courier, monospace;
}
SECTION .well > LABEL,
SECTION .well LABEL,
.graphs LABEL {
font-weight: normal;
color: #da8456;

View File

@ -10,6 +10,10 @@ export default {
PROJECT_HEADER: 'x-api-project-id',
PROJECT_SESSION_KEY: 'projectId',
PROJECT_TYPE_DEFAULT: 'default',
PROJECT_TYPE_USER: 'project:github_user',
PROJECT_TYPE_TEAM: 'project:github_team',
PROJECT_TYPE_ORG: 'project:github_org',
NO_CHALLENGE_HEADER: 'x-api-no-challenge',
NO_CHALLENGE_VALUE: 'true',

View File

@ -39,7 +39,7 @@ EOF
# Why are you trying to do a build when there are uncommitted changes?
if [[ `git status --porcelain` ]]; then
echo "There are uncommited changes. Please check the number and try again."
# exit 1;
exit 1;
fi
echo "Environment Variables:"

View File

@ -3,7 +3,7 @@ import {
test
} from 'ember-qunit';
moduleFor('route:settings/projects/new', 'SettingsProjectsNewRoute', {
moduleFor('route:project/edit', 'ProjectEditRoute', {
// Specify the other units that are required for this test.
// needs: ['controller:foo']
});

View File

@ -0,0 +1,14 @@
import {
moduleFor,
test
} from 'ember-qunit';
moduleFor('route:project', 'ProjectRoute', {
// Specify the other units that are required for this test.
// needs: ['controller:foo']
});
test('it exists', function() {
var route = this.subject();
ok(route);
});