Move signin to use AJAX. Render all flask templates with the common header. Move the header to a partial. Add account recovery.

This commit is contained in:
yackob03 2013-10-14 17:50:07 -04:00
parent e182163d34
commit 4c15072c5a
17 changed files with 653 additions and 617 deletions

View file

@ -975,4 +975,29 @@ p.editable:hover i {
.tos ul {
margin-top: 10px;
}
.form-signin input {
margin-bottom: 20px;
}
.form-signin {
text-align: center;
margin-bottom: 20px;
}
.social-alternate {
color: #777;
font-size: 3em;
margin-left: 43px;
}
.social-alternate .inner-text {
text-align: center;
position: relative;
color: white;
left: -42px;
top: -9px;
font-weight: bold;
font-size: .4em;
}

View file

@ -1,66 +0,0 @@
body {
padding-top: 40px;
padding-bottom: 40px;
background-color: #eee;
}
.signin-container {
text-align: center;
}
.form-signin {
max-width: 330px;
padding: 15px;
margin: 0 auto;
text-align: center;
}
.form-signin .form-signin-heading,
.form-signin .checkbox {
margin-bottom: 10px;
}
.form-signin .checkbox {
font-weight: normal;
}
.form-signin .form-control {
position: relative;
font-size: 16px;
height: auto;
padding: 10px;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.form-signin .form-control:focus {
z-index: 2;
}
.form-signin input[type="text"] {
margin-bottom: -1px;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
.form-signin input[type="password"] {
margin-bottom: 10px;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.alert {
max-width: 300px;
margin: 0 auto;
}
.social-alternate {
color: #777;
font-size: 3em;
margin-left: 43px;
}
.social-alternate .inner-text {
text-align: center;
position: relative;
color: white;
left: -43px;
top: -9px;
font-weight: bold;
font-size: .4em;
}

View file

@ -136,6 +136,7 @@ quayApp = angular.module('quay', ['restangular', 'angularMoment', 'angulartics',
when('/user/', {title: 'User Admin', templateUrl: '/static/partials/user-admin.html', controller: UserAdminCtrl}).
when('/guide/', {title: 'Getting Started Guide', templateUrl: '/static/partials/guide.html', controller: GuideCtrl}).
when('/plans/', {title: 'Plans and Pricing', templateUrl: '/static/partials/plans.html', controller: PlansCtrl}).
when('/signin/', {title: 'Signin', templateUrl: '/static/partials/signin.html', controller: SigninCtrl}).
when('/', {title: 'Hosted Private Docker Registry', templateUrl: '/static/partials/landing.html', controller: LandingCtrl}).
otherwise({redirectTo: '/'});
}]).

View file

@ -35,48 +35,101 @@ function getMarkedDown(string) {
return Markdown.getSanitizingConverter().makeHtml(string || '');
}
function HeaderCtrl($scope, UserService) {
function HeaderCtrl($scope, $location, UserService, Restangular) {
$scope.$watch( function () { return UserService.currentUser(); }, function (currentUser) {
$scope.user = currentUser;
}, true);
$('#repoSearch').typeahead({
name: 'repositories',
remote: {
url: '/api/find/repository?query=%QUERY',
filter: function(data) {
var datums = [];
for (var i = 0; i < data.repositories.length; ++i) {
var repo = data.repositories[i];
datums.push({
'value': repo.name,
'tokens': [repo.name, repo.namespace],
'repo': repo
});
$scope.signout = function() {
var signoutPost = Restangular.one('signout');
signoutPost.customPOST().then(function() {
UserService.load();
$location.path('/');
});
}
$scope.$on('$includeContentLoaded', function() {
// THIS IS BAD, MOVE THIS TO A DIRECTIVE
$('#repoSearch').typeahead({
name: 'repositories',
remote: {
url: '/api/find/repository?query=%QUERY',
filter: function(data) {
var datums = [];
for (var i = 0; i < data.repositories.length; ++i) {
var repo = data.repositories[i];
datums.push({
'value': repo.name,
'tokens': [repo.name, repo.namespace],
'repo': repo
});
}
return datums;
}
},
template: function (datum) {
template = '<div class="repo-mini-listing">';
template += '<i class="icon-hdd icon-large"></i>'
template += '<span class="name">' + datum.repo.namespace +'/' + datum.repo.name + '</span>'
if (datum.repo.description) {
template += '<span class="description">' + getFirstTextLine(datum.repo.description) + '</span>'
}
return datums;
}
},
template: function (datum) {
template = '<div class="repo-mini-listing">';
template += '<i class="icon-hdd icon-large"></i>'
template += '<span class="name">' + datum.repo.namespace +'/' + datum.repo.name + '</span>'
if (datum.repo.description) {
template += '<span class="description">' + getFirstTextLine(datum.repo.description) + '</span>'
}
template += '</div>'
return template;
},
template += '</div>'
return template;
},
});
});
$('#repoSearch').on('typeahead:selected', function (e, datum) {
$('#repoSearch').typeahead('setQuery', '');
document.location = '/repository/' + datum.repo.namespace + '/' + datum.repo.name
$('#repoSearch').on('typeahead:selected', function (e, datum) {
$('#repoSearch').typeahead('setQuery', '');
document.location = '/repository/' + datum.repo.namespace + '/' + datum.repo.name
});
});
}
function SigninCtrl($scope, $location, $timeout, Restangular, KeyService, UserService) {
$scope.githubClientId = KeyService.githubClientId;
var appendMixpanelId = function() {
if (mixpanel.get_distinct_id !== undefined) {
$scope.mixpanelDistinctIdClause = "&state=" + mixpanel.get_distinct_id();
} else {
// Mixpanel not yet loaded, try again later
$timeout(appendMixpanelId, 200);
}
};
appendMixpanelId();
$scope.signin = function() {
var signinPost = Restangular.one('signin');
signinPost.customPOST($scope.user).then(function() {
$scope.needsEmailVerification = false;
$scope.invalidCredentials = false;
// Redirect to the landing page
UserService.load();
$location.path('/');
}, function(result) {
$scope.needsEmailVerification = result.data.needsEmailVerification;
$scope.invalidCredentials = result.data.invalidCredentials;
});
};
$scope.sendRecovery = function() {
var signinPost = Restangular.one('recovery');
signinPost.customPOST($scope.recovery).then(function() {
$scope.invalidEmail = false;
$scope.sent = true;
}, function(result) {
$scope.invalidEmail = true;
$scope.sent = false;
});
};
};
function PlansCtrl($scope, UserService, PlanService) {
$scope.plans = PlanService.planList();
@ -328,35 +381,38 @@ function RepoAdminCtrl($scope, Restangular, $routeParams, $rootScope) {
var namespace = $routeParams.namespace;
var name = $routeParams.name;
$('#userSearch').typeahead({
name: 'users',
remote: {
url: '/api/users/%QUERY',
filter: function(data) {
var datums = [];
for (var i = 0; i < data.users.length; ++i) {
var user = data.users[i];
datums.push({
'value': user,
'tokens': [user],
'username': user
});
$scope.$on('$viewContentLoaded', function() {
// THIS IS BAD, MOVE THIS TO A DIRECTIVE
$('#userSearch').typeahead({
name: 'users',
remote: {
url: '/api/users/%QUERY',
filter: function(data) {
var datums = [];
for (var i = 0; i < data.users.length; ++i) {
var user = data.users[i];
datums.push({
'value': user,
'tokens': [user],
'username': user
});
}
return datums;
}
return datums;
}
},
template: function (datum) {
template = '<div class="user-mini-listing">';
template += '<i class="icon-user icon-large"></i>'
template += '<span class="name">' + datum.username + '</span>'
template += '</div>'
return template;
},
});
},
template: function (datum) {
template = '<div class="user-mini-listing">';
template += '<i class="icon-user icon-large"></i>'
template += '<span class="name">' + datum.username + '</span>'
template += '</div>'
return template;
},
});
$('#userSearch').on('typeahead:selected', function(e, datum) {
$('#userSearch').typeahead('setQuery', '');
$scope.addNewPermission(datum.username);
$('#userSearch').on('typeahead:selected', function(e, datum) {
$('#userSearch').typeahead('setQuery', '');
$scope.addNewPermission(datum.username);
});
});
$scope.addNewPermission = function(username) {

View file

@ -0,0 +1,53 @@
<!-- Quay -->
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-ex1-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/">
<img src="/static/img/quay-logo.png">
</a>
</div>
<!-- Collapsable stuff -->
<div class="collapse navbar-collapse navbar-ex1-collapse">
<ul class="nav navbar-nav">
<li><a ng-href="/repository/">Repositories</a></li>
<li><a ng-href="/guide/">Getting Started</a></li>
<li><a ng-href="/plans/">Plans &amp; Pricing</a></li>
</ul>
<ul class="nav navbar-nav navbar-right" ng-switch on="user.anonymous">
<form class="navbar-form navbar-left" role="search">
<div class="form-group">
<input id="repoSearch" type="text" class="form-control" placeholder="Find Repo">
</div>
</form>
<li class="dropdown" ng-switch-when="false">
<!--<button type="button" class="btn btn-default navbar-btn">Sign in</button>-->
<a href="javascript:void(0)" class="dropdown-toggle user-dropdown" data-toggle="dropdown">
<img src="//www.gravatar.com/avatar/{{ user.gravatar }}?s=32&d=identicon" />
{{ user.username }}
<span class="badge" ng-show="user.askForPassword">1</span>
<b class="caret"></b>
</a>
<ul class="dropdown-menu">
<li>
<a href="/user/">
Account Settings
<span class="badge" ng-show="user.askForPassword">1</span>
</a>
</li>
<li><a href="javascript:void(0)" ng-click="signout()">Sign out</a></li>
</ul>
</li>
<li ng-switch-default>
<a href="/signin/">Sign in</a>
</li>
</ul>
</div><!-- /.navbar-collapse -->

View file

@ -16,7 +16,7 @@
<h2>Your Top Repositories</h2>
<div class="repo-listing" ng-repeat="repository in myrepos">
<i class="icon-hdd icon-large"></i>
<a ng-href="/repository/{{repository.namespace}}/{{ repository.name }}">{{repository.namespace}}/{{repository.name}}</a>
<a ng-href="/repository/{{ repository.namespace }}/{{ repository.name }}">{{repository.namespace}}/{{repository.name}}</a>
<div class="description" ng-bind-html-unsafe="getCommentFirstLine(repository.description)"></div>
</div>
</div>

View file

@ -0,0 +1,72 @@
<div class="container signin-container">
<div class="row">
<div class="col-sm-6 col-sm-offset-3">
<div class="panel-group" id="accordion">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a class="accordion-toggle" data-toggle="collapse" data-parent="#accordion" data-target="#collapseSignin">
Sign In
</a>
</h4>
</div>
<div id="collapseSignin" class="panel-collapse collapse in">
<div class="panel-body">
<form class="form-signin" ng-submit="signin();">
<input type="text" class="form-control input-lg" placeholder="Username" ng-model="user.username" autofocus>
<input type="password" class="form-control input-lg" placeholder="Password" ng-model="user.password">
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign In</button>
<span class="social-alternate">
<i class="icon-circle"></i>
<span class="inner-text">OR</span>
</span>
<a id='github-signin-link' href="https://github.com/login/oauth/authorize?client_id={{ githubClientId }}&scope=user:email{{ mixpanelDistinctIdClause }}" class="btn btn-primary btn-lg btn-block"><i class="icon-github icon-large"></i> Sign In with GitHub</a>
</form>
<div class="alert alert-danger" ng-show="invalidCredentials">Invalid username or password.</div>
<div class="alert alert-danger" ng-show="needsEmailVerification">You must verify your email address before you can sign in.</div>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h6 class="panel-title">
<a class="accordion-toggle" data-toggle="collapse" data-parent="#accordion" data-target="#collapseForgot">
Forgot Password
</a>
</h4>
</div>
<div id="collapseForgot" class="panel-collapse collapse out">
<div class="panel-body">
<form class="form-signin" ng-submit="sendRecovery();">
<input type="text" class="form-control input-lg" placeholder="Email" ng-model="recovery.email">
<button class="btn btn-lg btn-primary btn-block" type="submit">Send Recovery Email</button>
</form>
<div class="alert alert-danger" ng-show="invalidEmail">Unable to locate account.</div>
<div class="alert alert-success" ng-show="sent">Account recovery email was sent.</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- <script type="text/javascript">
function appendMixpanelId() {
if (mixpanel.get_distinct_id !== undefined) {
var signinLink = document.getElementById("github-signin-link");
signinLink.href += ("&state=" + mixpanel.get_distinct_id());
} else {
// Mixpanel not yet loaded, try again later
window.setTimeout(appendMixpanelId, 200);
}
};
appendMixpanelId();
</script> -->