2013-11-05 00:36:56 +00:00
function getFirstTextLine ( commentString ) {
if ( ! commentString ) { return '' ; }
var lines = commentString . split ( '\n' ) ;
var MARKDOWN _CHARS = {
'#' : true ,
'-' : true ,
'>' : true ,
'`' : true
} ;
for ( var i = 0 ; i < lines . length ; ++ i ) {
// Skip code lines.
if ( lines [ i ] . indexOf ( ' ' ) == 0 ) {
continue ;
}
// Skip empty lines.
if ( $ . trim ( lines [ i ] ) . length == 0 ) {
continue ;
}
// Skip control lines.
if ( MARKDOWN _CHARS [ $ . trim ( lines [ i ] ) [ 0 ] ] ) {
continue ;
}
return getMarkedDown ( lines [ i ] ) ;
}
return '' ;
}
2013-11-06 19:19:56 +00:00
function getRestUrl ( args ) {
var url = '' ;
for ( var i = 0 ; i < arguments . length ; ++ i ) {
if ( i > 0 ) {
url += '/' ;
}
url += encodeURI ( arguments [ i ] )
}
return url ;
}
2013-11-05 00:36:56 +00:00
function getMarkedDown ( string ) {
return Markdown . getSanitizingConverter ( ) . makeHtml ( string || '' ) ;
}
2013-10-01 20:42:20 +00:00
// Start the application code itself.
2013-11-19 00:03:35 +00:00
quayApp = angular . module ( 'quay' , [ 'ngRoute' , 'restangular' , 'angularMoment' , 'angulartics' , 'angulartics.mixpanel' , '$strap.directives' , 'ngCookies' ] , function ( $provide ) {
2013-11-08 22:50:42 +00:00
$provide . factory ( 'UserService' , [ 'Restangular' , function ( Restangular ) {
2013-09-26 23:59:58 +00:00
var userResponse = {
verified : false ,
anonymous : true ,
username : null ,
2013-10-10 17:44:34 +00:00
email : null ,
askForPassword : false ,
2013-11-08 03:44:34 +00:00
organizations : [ ]
2013-09-26 23:59:58 +00:00
}
var userService = { }
2013-11-12 00:26:56 +00:00
userService . load = function ( opt _callback ) {
2013-09-26 23:59:58 +00:00
var userFetch = Restangular . one ( 'user/' ) ;
userFetch . get ( ) . then ( function ( loadedUser ) {
userResponse = loadedUser ;
2013-10-03 19:46:22 +00:00
2013-10-03 20:16:10 +00:00
if ( ! userResponse . anonymous ) {
mixpanel . identify ( userResponse . username ) ;
mixpanel . people . set ( {
'$email' : userResponse . email ,
'$username' : userResponse . username ,
'verified' : userResponse . verified
} ) ;
2013-10-10 20:34:59 +00:00
mixpanel . people . set _once ( {
'$created' : new Date ( )
} )
2013-10-03 20:16:10 +00:00
}
2013-11-12 00:26:56 +00:00
if ( opt _callback ) {
opt _callback ( ) ;
}
2013-09-26 23:59:58 +00:00
} ) ;
} ;
2013-11-08 22:50:42 +00:00
userService . getOrganization = function ( name ) {
if ( ! userResponse || ! userResponse . organizations ) { return null ; }
for ( var i = 0 ; i < userResponse . organizations . length ; ++ i ) {
var org = userResponse . organizations [ i ] ;
if ( org . name == name ) {
return org ;
}
}
2013-11-01 23:13:58 +00:00
2013-11-08 22:50:42 +00:00
return null ;
2013-11-01 23:13:58 +00:00
} ;
2013-09-26 23:59:58 +00:00
userService . currentUser = function ( ) {
return userResponse ;
2013-11-08 22:50:42 +00:00
} ;
2013-09-26 23:59:58 +00:00
// Load the user the first time.
userService . load ( ) ;
return userService ;
2013-10-04 18:35:51 +00:00
} ] ) ;
2013-10-08 17:57:48 +00:00
$provide . factory ( 'KeyService' , [ '$location' , function ( $location ) {
var keyService = { }
if ( $location . host ( ) === 'quay.io' ) {
keyService [ 'stripePublishableKey' ] = 'pk_live_P5wLU0vGdHnZGyKnXlFG4oiu' ;
2013-10-10 18:42:14 +00:00
keyService [ 'githubClientId' ] = '5a8c08b06c48d89d4d1e' ;
2013-10-08 17:57:48 +00:00
} else {
keyService [ 'stripePublishableKey' ] = 'pk_test_uEDHANKm9CHCvVa2DLcipGRh' ;
2013-10-10 18:42:14 +00:00
keyService [ 'githubClientId' ] = 'cfbc4aca88e5c1b40679' ;
2013-10-08 17:57:48 +00:00
}
return keyService ;
} ] ) ;
2013-11-08 22:50:42 +00:00
$provide . factory ( 'PlanService' , [ 'Restangular' , 'KeyService' , 'UserService' , function ( Restangular , KeyService , UserService ) {
2013-11-01 23:13:58 +00:00
var plans = null ;
2013-10-04 18:35:51 +00:00
var planDict = { } ;
2013-11-15 23:17:12 +00:00
var planService = { } ;
var listeners = [ ] ;
2013-10-04 18:35:51 +00:00
2013-11-15 23:17:12 +00:00
planService . registerListener = function ( obj , callback ) {
listeners . push ( { 'obj' : obj , 'callback' : callback } ) ;
} ;
planService . unregisterListener = function ( obj ) {
for ( var i = 0 ; i < listeners . length ; ++ i ) {
if ( listeners [ i ] . obj == obj ) {
listeners . splice ( i , 1 ) ;
break ;
}
}
} ;
2013-11-19 22:06:17 +00:00
planService . handleCardError = function ( resp ) {
if ( ! planService . isCardError ( resp ) ) { return ; }
bootbox . dialog ( {
"message" : resp . data . carderror ,
"title" : "Credit card issue" ,
"buttons" : {
"close" : {
"label" : "Close" ,
"className" : "btn-primary"
}
}
} ) ;
} ;
planService . isCardError = function ( resp ) {
return resp && resp . data && resp . data . carderror ;
} ;
2013-11-01 23:13:58 +00:00
planService . verifyLoaded = function ( callback ) {
if ( plans ) {
callback ( plans ) ;
return ;
}
var getPlans = Restangular . one ( 'plans' ) ;
getPlans . get ( ) . then ( function ( data ) {
2013-11-05 19:40:45 +00:00
var i = 0 ;
for ( i = 0 ; i < data . user . length ; i ++ ) {
planDict [ data . user [ i ] . stripeId ] = data . user [ i ] ;
2013-11-01 23:13:58 +00:00
}
2013-11-05 19:40:45 +00:00
for ( i = 0 ; i < data . business . length ; i ++ ) {
planDict [ data . business [ i ] . stripeId ] = data . business [ i ] ;
}
plans = data ;
2013-11-01 23:13:58 +00:00
callback ( plans ) ;
} , function ( ) { callback ( [ ] ) ; } ) ;
} ;
2013-11-08 03:08:23 +00:00
planService . getMatchingBusinessPlan = function ( callback ) {
planService . getPlans ( function ( ) {
planService . getSubscription ( null , function ( sub ) {
var plan = planDict [ sub . plan ] ;
if ( ! plan ) {
planService . getMinimumPlan ( 0 , true , callback ) ;
return ;
}
var count = Math . max ( sub . usedPrivateRepos , plan . privateRepos ) ;
planService . getMinimumPlan ( count , true , callback ) ;
} , function ( ) {
planService . getMinimumPlan ( 0 , true , callback ) ;
} ) ;
} ) ;
} ;
2013-11-05 19:40:45 +00:00
planService . getPlans = function ( callback ) {
2013-11-01 23:13:58 +00:00
planService . verifyLoaded ( callback ) ;
2013-10-28 21:08:26 +00:00
} ;
2013-10-04 18:35:51 +00:00
2013-11-01 23:13:58 +00:00
planService . getPlan = function ( planId , callback ) {
planService . verifyLoaded ( function ( ) {
2013-11-06 23:14:22 +00:00
if ( planDict [ planId ] ) {
callback ( planDict [ planId ] ) ;
}
2013-11-01 23:13:58 +00:00
} ) ;
2013-10-28 21:08:26 +00:00
} ;
2013-11-05 19:40:45 +00:00
planService . getMinimumPlan = function ( privateCount , isBusiness , callback ) {
2013-11-01 23:13:58 +00:00
planService . verifyLoaded ( function ( ) {
2013-11-05 19:40:45 +00:00
var planSource = plans . user ;
if ( isBusiness ) {
planSource = plans . business ;
}
for ( var i = 0 ; i < planSource . length ; i ++ ) {
var plan = planSource [ i ] ;
2013-11-01 23:13:58 +00:00
if ( plan . privateRepos >= privateCount ) {
callback ( plan ) ;
return ;
}
2013-10-28 21:08:26 +00:00
}
2013-11-01 23:13:58 +00:00
callback ( null ) ;
} ) ;
2013-10-28 21:08:26 +00:00
} ;
2013-11-15 23:17:12 +00:00
planService . getSubscription = function ( orgname , success , failure ) {
var url = planService . getSubscriptionUrl ( orgname ) ;
2013-11-06 22:30:20 +00:00
var getSubscription = Restangular . one ( url ) ;
getSubscription . get ( ) . then ( success , failure ) ;
} ;
2013-10-28 21:08:26 +00:00
2013-11-06 22:30:20 +00:00
planService . getSubscriptionUrl = function ( orgname ) {
return orgname ? getRestUrl ( 'organization' , orgname , 'plan' ) : 'user/plan' ;
} ;
planService . setSubscription = function ( orgname , planId , success , failure , opt _token ) {
var subscriptionDetails = {
plan : planId
} ;
2013-10-28 21:08:26 +00:00
2013-11-06 22:30:20 +00:00
if ( opt _token ) {
subscriptionDetails [ 'token' ] = opt _token . id ;
}
var url = planService . getSubscriptionUrl ( orgname ) ;
var createSubscriptionRequest = Restangular . one ( url ) ;
2013-11-15 23:17:12 +00:00
createSubscriptionRequest . customPUT ( subscriptionDetails ) . then ( function ( resp ) {
success ( resp ) ;
planService . getPlan ( planId , function ( plan ) {
for ( var i = 0 ; i < listeners . length ; ++ i ) {
listeners [ i ] [ 'callback' ] ( plan ) ;
}
} ) ;
} , failure ) ;
2013-11-06 22:30:20 +00:00
} ;
2013-11-15 23:58:47 +00:00
planService . getCardInfo = function ( orgname , callback ) {
var url = orgname ? getRestUrl ( 'organization' , orgname , 'card' ) : 'user/card' ;
var getCard = Restangular . one ( url ) ;
getCard . customGET ( ) . then ( function ( resp ) {
callback ( resp . card ) ;
} , function ( ) {
callback ( { 'is_valid' : false } ) ;
} ) ;
} ;
planService . changePlan = function ( $scope , orgname , planId , callbacks ) {
2013-11-09 01:32:56 +00:00
if ( callbacks [ 'started' ] ) {
2013-11-15 23:58:47 +00:00
callbacks [ 'started' ] ( ) ;
2013-11-09 01:32:56 +00:00
}
2013-11-15 23:58:47 +00:00
planService . getPlan ( planId , function ( plan ) {
planService . getCardInfo ( orgname , function ( cardInfo ) {
if ( plan . price > 0 && ! cardInfo . last4 ) {
planService . showSubscribeDialog ( $scope , orgname , planId , callbacks ) ;
return ;
}
2013-11-19 22:06:17 +00:00
planService . setSubscription ( orgname , planId , callbacks [ 'success' ] , function ( resp ) {
planService . handleCardError ( resp ) ;
callbacks [ 'failure' ] ( resp ) ;
} ) ;
2013-11-15 23:58:47 +00:00
} ) ;
} ) ;
2013-11-06 22:30:20 +00:00
} ;
2013-11-15 23:17:12 +00:00
planService . changeCreditCard = function ( $scope , orgname , callbacks ) {
if ( callbacks [ 'opening' ] ) {
callbacks [ 'opening' ] ( ) ;
}
2013-11-19 22:06:17 +00:00
var submitted = false ;
2013-11-15 23:17:12 +00:00
var submitToken = function ( token ) {
2013-11-19 22:06:17 +00:00
if ( submitted ) { return ; }
submitted = true ;
2013-11-15 23:17:12 +00:00
$scope . $apply ( function ( ) {
if ( callbacks [ 'started' ] ) {
callbacks [ 'started' ] ( ) ;
}
var cardInfo = {
'token' : token . id
} ;
var url = orgname ? getRestUrl ( 'organization' , orgname , 'card' ) : 'user/card' ;
var changeCardRequest = Restangular . one ( url ) ;
2013-11-19 22:06:17 +00:00
changeCardRequest . customPOST ( cardInfo ) . then ( callbacks [ 'success' ] , function ( resp ) {
planService . handleCardError ( resp ) ;
callbacks [ 'failure' ] ( resp ) ;
} ) ;
2013-11-15 23:17:12 +00:00
} ) ;
} ;
var email = planService . getEmail ( orgname ) ;
StripeCheckout . open ( {
key : KeyService . stripePublishableKey ,
address : false ,
email : email ,
currency : 'usd' ,
name : 'Update credit card' ,
description : 'Enter your credit card number' ,
panelLabel : 'Update' ,
token : submitToken ,
image : 'static/img/quay-icon-stripe.png' ,
opened : function ( ) { $scope . $apply ( function ( ) { callbacks [ 'opened' ] ( ) } ) ; } ,
closed : function ( ) { $scope . $apply ( function ( ) { callbacks [ 'closed' ] ( ) } ) ; }
} ) ;
} ;
planService . getEmail = function ( orgname ) {
var email = null ;
if ( UserService . currentUser ( ) ) {
email = UserService . currentUser ( ) . email ;
if ( orgname ) {
org = UserService . getOrganization ( orgname ) ;
if ( org ) {
emaiil = org . email ;
}
}
}
return email ;
} ;
2013-11-09 01:32:56 +00:00
planService . showSubscribeDialog = function ( $scope , orgname , planId , callbacks ) {
if ( callbacks [ 'opening' ] ) {
callbacks [ 'opening' ] ( ) ;
}
2013-11-19 22:06:17 +00:00
var submitted = false ;
2013-11-06 22:30:20 +00:00
var submitToken = function ( token ) {
2013-11-19 22:06:17 +00:00
if ( submitted ) { return ; }
submitted = true ;
2013-11-06 22:30:20 +00:00
mixpanel . track ( 'plan_subscribe' ) ;
2013-10-28 21:08:26 +00:00
$scope . $apply ( function ( ) {
2013-11-09 01:32:56 +00:00
if ( callbacks [ 'started' ] ) {
callbacks [ 'started' ] ( ) ;
}
2013-11-15 20:31:05 +00:00
planService . setSubscription ( orgname , planId , callbacks [ 'success' ] , callbacks [ 'failure' ] , token ) ;
2013-10-28 21:08:26 +00:00
} ) ;
} ;
2013-11-01 23:13:58 +00:00
planService . getPlan ( planId , function ( planDetails ) {
2013-11-15 23:17:12 +00:00
var email = planService . getEmail ( orgname ) ;
2013-11-01 23:13:58 +00:00
StripeCheckout . open ( {
key : KeyService . stripePublishableKey ,
address : false ,
2013-11-08 22:50:42 +00:00
email : email ,
2013-11-01 23:13:58 +00:00
amount : planDetails . price ,
currency : 'usd' ,
name : 'Quay ' + planDetails . title + ' Subscription' ,
description : 'Up to ' + planDetails . privateRepos + ' private repositories' ,
panelLabel : 'Subscribe' ,
2013-11-06 19:55:40 +00:00
token : submitToken ,
2013-11-09 01:32:56 +00:00
image : 'static/img/quay-icon-stripe.png' ,
opened : function ( ) { $scope . $apply ( function ( ) { callbacks [ 'opened' ] ( ) } ) ; } ,
closed : function ( ) { $scope . $apply ( function ( ) { callbacks [ 'closed' ] ( ) } ) ; }
2013-11-01 23:13:58 +00:00
} ) ;
2013-10-28 21:08:26 +00:00
} ) ;
} ;
2013-10-04 18:35:51 +00:00
return planService ;
} ] ) ;
2013-09-26 23:59:58 +00:00
} ) .
2013-10-01 23:37:33 +00:00
directive ( 'match' , function ( $parse ) {
return {
require : 'ngModel' ,
link : function ( scope , elem , attrs , ctrl ) {
scope . $watch ( function ( ) {
return $parse ( attrs . match ) ( scope ) === ctrl . $modelValue ;
} , function ( currentValue ) {
ctrl . $setValidity ( 'mismatch' , currentValue ) ;
} ) ;
}
} ;
} ) .
2013-10-17 18:29:47 +00:00
directive ( 'onresize' , function ( $window , $parse ) {
return function ( scope , element , attr ) {
var fn = $parse ( attr . onresize ) ;
var notifyResized = function ( ) {
scope . $apply ( function ( ) {
fn ( scope ) ;
} ) ;
} ;
angular . element ( $window ) . on ( 'resize' , null , notifyResized ) ;
scope . $on ( '$destroy' , function ( ) {
angular . element ( $window ) . off ( 'resize' , null , notifyResized ) ;
} ) ;
} ;
} ) .
2013-10-03 19:46:22 +00:00
config ( [ '$routeProvider' , '$locationProvider' , '$analyticsProvider' ,
function ( $routeProvider , $locationProvider , $analyticsProvider ) {
2013-10-03 20:16:10 +00:00
$analyticsProvider . virtualPageviews ( true ) ;
2013-10-10 23:06:04 +00:00
$locationProvider . html5Mode ( true ) ;
2013-10-14 02:06:31 +00:00
// WARNING WARNING WARNING
// If you add a route here, you must add a corresponding route in thr endpoints/web.py
// index rule to make sure that deep links directly deep into the app continue to work.
// WARNING WARNING WARNING
2013-10-03 20:16:10 +00:00
$routeProvider .
2013-11-20 21:17:47 +00:00
when ( '/repository/:namespace/:name' , { templateUrl : '/static/partials/view-repo.html' , controller : RepoCtrl ,
hideFooter : true , reloadOnSearch : false } ) .
when ( '/repository/:namespace/:name/tag/:tag' , { templateUrl : '/static/partials/view-repo.html' , controller : RepoCtrl ,
hideFooter : true } ) .
2013-10-19 02:28:46 +00:00
when ( '/repository/:namespace/:name/image/:image' , { templateUrl : '/static/partials/image-view.html' , controller : ImageViewCtrl } ) .
2013-10-03 20:16:10 +00:00
when ( '/repository/:namespace/:name/admin' , { templateUrl : '/static/partials/repo-admin.html' , controller : RepoAdminCtrl } ) .
2013-11-19 00:03:35 +00:00
when ( '/repository/' , { title : 'Repositories' , description : 'Public and private docker repositories list' ,
templateUrl : '/static/partials/repo-list.html' , controller : RepoListCtrl } ) .
when ( '/user/' , { title : 'Account Settings' , description : 'Account settings for Quay' , templateUrl : '/static/partials/user-admin.html' , controller : UserAdminCtrl } ) .
when ( '/guide/' , { title : 'Guide' , description : 'Guide to using private docker repositories on Quay' , templateUrl : '/static/partials/guide.html' , controller : GuideCtrl } ) .
when ( '/plans/' , { title : 'Plans and Pricing' , description : 'Plans and pricing for private docker repositories on Quay' ,
templateUrl : '/static/partials/plans.html' , controller : PlansCtrl } ) .
when ( '/signin/' , { title : 'Sign In' , description : 'Sign into Quay' , templateUrl : '/static/partials/signin.html' , controller : SigninCtrl } ) .
when ( '/new/' , { title : 'Create new repository' , description : 'Create a new public or private docker repository, optionally constructing from a dockerfile' ,
templateUrl : '/static/partials/new-repo.html' , controller : NewRepoCtrl } ) .
when ( '/organizations/' , { title : 'Organizations' , description : 'Private docker repository hosting for businesses and organizations' ,
templateUrl : '/static/partials/organizations.html' , controller : OrgsCtrl } ) .
when ( '/organizations/new/' , { title : 'New Organization' , description : 'Create a new organization on Quay' ,
templateUrl : '/static/partials/new-organization.html' , controller : NewOrgCtrl } ) .
2013-10-31 22:17:26 +00:00
when ( '/organization/:orgname' , { templateUrl : '/static/partials/org-view.html' , controller : OrgViewCtrl } ) .
when ( '/organization/:orgname/admin' , { templateUrl : '/static/partials/org-admin.html' , controller : OrgAdminCtrl } ) .
when ( '/organization/:orgname/teams/:teamname' , { templateUrl : '/static/partials/team-view.html' , controller : TeamViewCtrl } ) .
2013-10-17 21:45:08 +00:00
when ( '/v1/' , { title : 'Activation information' , templateUrl : '/static/partials/v1-page.html' , controller : V1Ctrl } ) .
2013-10-09 22:33:25 +00:00
when ( '/' , { title : 'Hosted Private Docker Registry' , templateUrl : '/static/partials/landing.html' , controller : LandingCtrl } ) .
2013-10-03 20:16:10 +00:00
otherwise ( { redirectTo : '/' } ) ;
} ] ) .
2013-09-24 22:21:14 +00:00
config ( function ( RestangularProvider ) {
RestangularProvider . setBaseUrl ( '/api/' ) ;
2013-09-26 21:59:20 +00:00
} ) ;
2013-11-05 00:36:56 +00:00
quayApp . directive ( 'markdownView' , function ( ) {
var directiveDefinitionObject = {
priority : 0 ,
templateUrl : '/static/directives/markdown-view.html' ,
replace : false ,
transclude : false ,
restrict : 'C' ,
scope : {
'content' : '=content' ,
'firstLineOnly' : '=firstLineOnly'
} ,
2013-11-19 00:03:35 +00:00
controller : function ( $scope , $element , $sce ) {
2013-11-05 00:36:56 +00:00
$scope . getMarkedDown = function ( content , firstLineOnly ) {
if ( firstLineOnly ) {
content = getFirstTextLine ( content ) ;
}
2013-11-19 00:03:35 +00:00
return $sce . trustAsHtml ( getMarkedDown ( content ) ) ;
2013-11-05 00:36:56 +00:00
} ;
}
} ;
return directiveDefinitionObject ;
} ) ;
2013-10-22 05:26:14 +00:00
quayApp . directive ( 'repoCircle' , function ( ) {
var directiveDefinitionObject = {
priority : 0 ,
templateUrl : '/static/directives/repo-circle.html' ,
replace : false ,
transclude : false ,
restrict : 'C' ,
scope : {
'repo' : '=repo'
} ,
controller : function ( $scope , $element ) {
2013-10-26 20:03:11 +00:00
}
} ;
return directiveDefinitionObject ;
} ) ;
2013-11-07 20:19:52 +00:00
quayApp . directive ( 'signinForm' , function ( ) {
var directiveDefinitionObject = {
priority : 0 ,
templateUrl : '/static/directives/signin-form.html' ,
replace : false ,
transclude : true ,
restrict : 'C' ,
scope : {
'redirectUrl' : '=redirectUrl'
} ,
controller : function ( $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 specified page or the landing page
UserService . load ( ) ;
$location . path ( $scope . redirectUrl ? $scope . redirectUrl : '/' ) ;
} , function ( result ) {
$scope . needsEmailVerification = result . data . needsEmailVerification ;
$scope . invalidCredentials = result . data . invalidCredentials ;
} ) ;
} ;
}
} ;
return directiveDefinitionObject ;
} ) ;
2013-11-08 03:08:23 +00:00
quayApp . directive ( 'plansTable' , function ( ) {
var directiveDefinitionObject = {
priority : 0 ,
templateUrl : '/static/directives/plans-table.html' ,
replace : false ,
transclude : true ,
restrict : 'C' ,
scope : {
'plans' : '=plans' ,
'currentPlan' : '=currentPlan'
} ,
controller : function ( $scope , $element ) {
$scope . setPlan = function ( plan ) {
$scope . currentPlan = plan ;
} ;
}
} ;
return directiveDefinitionObject ;
} ) ;
2013-11-04 19:56:54 +00:00
quayApp . directive ( 'organizationHeader' , function ( ) {
var directiveDefinitionObject = {
priority : 0 ,
templateUrl : '/static/directives/organization-header.html' ,
replace : false ,
2013-11-05 21:15:04 +00:00
transclude : true ,
2013-11-04 19:56:54 +00:00
restrict : 'C' ,
scope : {
'organization' : '=organization' ,
'teamName' : '=teamName'
} ,
controller : function ( $scope , $element ) {
}
} ;
return directiveDefinitionObject ;
} ) ;
2013-11-05 00:59:28 +00:00
quayApp . directive ( 'markdownInput' , function ( ) {
var counter = 0 ;
var directiveDefinitionObject = {
priority : 0 ,
templateUrl : '/static/directives/markdown-input.html' ,
replace : false ,
transclude : false ,
restrict : 'C' ,
scope : {
'content' : '=content' ,
'canWrite' : '=canWrite' ,
'contentChanged' : '=contentChanged' ,
'fieldTitle' : '=fieldTitle'
} ,
controller : function ( $scope , $element ) {
var elm = $element [ 0 ] ;
$scope . id = ( counter ++ ) ;
$scope . editContent = function ( ) {
if ( ! $scope . canWrite ) { return ; }
if ( ! $scope . markdownDescriptionEditor ) {
var converter = Markdown . getSanitizingConverter ( ) ;
var editor = new Markdown . Editor ( converter , '-description-' + $scope . id ) ;
editor . run ( ) ;
$scope . markdownDescriptionEditor = editor ;
}
$ ( '#wmd-input-description-' + $scope . id ) [ 0 ] . value = $scope . content ;
$ ( elm ) . find ( '.modal' ) . modal ( { } ) ;
} ;
$scope . saveContent = function ( ) {
$scope . content = $ ( '#wmd-input-description-' + $scope . id ) [ 0 ] . value ;
$ ( elm ) . find ( '.modal' ) . modal ( 'hide' ) ;
if ( $scope . contentChanged ) {
$scope . contentChanged ( $scope . content ) ;
}
} ;
}
} ;
return directiveDefinitionObject ;
} ) ;
2013-11-19 00:03:35 +00:00
quayApp . directive ( 'repoSearch' , function ( ) {
var number = 0 ;
var directiveDefinitionObject = {
priority : 0 ,
templateUrl : '/static/directives/repo-search.html' ,
replace : false ,
transclude : false ,
restrict : 'C' ,
scope : {
} ,
controller : function ( $scope , $element , $location , UserService , Restangular ) {
var searchToken = 0 ;
$scope . $watch ( function ( ) { return UserService . currentUser ( ) ; } , function ( currentUser ) {
++ searchToken ;
} , true ) ;
var element = $ ( $element [ 0 ] . childNodes [ 0 ] ) ;
element . typeahead ( {
name : 'repositories' ,
remote : {
url : '/api/find/repository?query=%QUERY' ,
replace : function ( url , uriEncodedQuery ) {
url = url . replace ( '%QUERY' , uriEncodedQuery ) ;
url += '&cb=' + searchToken ;
return url ;
} ,
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="fa fa-hdd fa-lg"></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 ;
}
} ) ;
element . on ( 'typeahead:selected' , function ( e , datum ) {
element . typeahead ( 'setQuery' , '' ) ;
document . location = '/repository/' + datum . repo . namespace + '/' + datum . repo . name ;
} ) ;
}
} ;
return directiveDefinitionObject ;
} ) ;
quayApp . directive ( 'headerBar' , function ( ) {
var number = 0 ;
var directiveDefinitionObject = {
priority : 0 ,
templateUrl : '/static/directives/header-bar.html' ,
replace : false ,
transclude : false ,
restrict : 'C' ,
scope : {
} ,
controller : function ( $scope , $element , $location , UserService , Restangular ) {
$scope . $watch ( function ( ) { return UserService . currentUser ( ) ; } , function ( currentUser ) {
$scope . user = currentUser ;
} , true ) ;
$scope . signout = function ( ) {
var signoutPost = Restangular . one ( 'signout' ) ;
signoutPost . customPOST ( ) . then ( function ( ) {
UserService . load ( ) ;
$location . path ( '/' ) ;
} ) ;
} ;
$scope . appLinkTarget = function ( ) {
if ( $ ( "div[ng-view]" ) . length === 0 ) {
return "_self" ;
}
return "" ;
} ;
}
} ;
return directiveDefinitionObject ;
} ) ;
2013-11-02 01:48:10 +00:00
quayApp . directive ( 'entitySearch' , function ( ) {
var number = 0 ;
var directiveDefinitionObject = {
priority : 0 ,
templateUrl : '/static/directives/entity-search.html' ,
replace : false ,
transclude : false ,
restrict : 'C' ,
scope : {
'organization' : '=organization' ,
'inputTitle' : '=inputTitle' ,
2013-11-04 19:56:54 +00:00
'entitySelected' : '=entitySelected'
2013-11-02 01:48:10 +00:00
} ,
controller : function ( $scope , $element ) {
if ( ! $scope . entitySelected ) { return ; }
number ++ ;
var input = $element [ 0 ] . firstChild ;
$scope . organization = $scope . organization || '' ;
$ ( input ) . typeahead ( {
name : 'entities' + number ,
remote : {
url : '/api/entities/%QUERY' ,
replace : function ( url , uriEncodedQuery ) {
url = url . replace ( '%QUERY' , uriEncodedQuery ) ;
if ( $scope . organization ) {
url += '?organization=' + encodeURIComponent ( $scope . organization ) ;
}
return url ;
} ,
filter : function ( data ) {
var datums = [ ] ;
for ( var i = 0 ; i < data . results . length ; ++ i ) {
var entity = data . results [ i ] ;
datums . push ( {
'value' : entity . name ,
'tokens' : [ entity . name ] ,
'entity' : entity
} ) ;
}
return datums ;
}
} ,
template : function ( datum ) {
template = '<div class="entity-mini-listing">' ;
if ( datum . entity . kind == 'user' ) {
template += '<i class="fa fa-user fa-lg"></i>' ;
} else if ( datum . entity . kind == 'team' ) {
template += '<i class="fa fa-group fa-lg"></i>' ;
}
template += '<span class="name">' + datum . value + '</span>' ;
2013-11-05 22:10:14 +00:00
if ( datum . entity . is _org _member !== undefined && ! datum . entity . is _org _member ) {
2013-11-02 01:48:10 +00:00
template += '<div class="alert-warning warning">This user is outside your organization</div>' ;
}
template += '</div>' ;
return template ;
} ,
} ) ;
$ ( input ) . on ( 'typeahead:selected' , function ( e , datum ) {
$ ( input ) . typeahead ( 'setQuery' , '' ) ;
$scope . entitySelected ( datum . entity ) ;
} ) ;
$scope . $watch ( 'inputTitle' , function ( title ) {
input . setAttribute ( 'placeholder' , title ) ;
} ) ;
}
} ;
return directiveDefinitionObject ;
} ) ;
2013-11-05 19:47:46 +00:00
quayApp . directive ( 'roleGroup' , function ( ) {
var directiveDefinitionObject = {
priority : 0 ,
templateUrl : '/static/directives/role-group.html' ,
replace : false ,
transclude : false ,
restrict : 'C' ,
scope : {
'roles' : '=roles' ,
'currentRole' : '=currentRole' ,
'roleChanged' : '&roleChanged'
} ,
controller : function ( $scope , $element ) {
$scope . setRole = function ( role ) {
2013-11-08 04:35:27 +00:00
if ( $scope . currentRole == role ) { return ; }
if ( $scope . roleChanged ) {
2013-11-05 19:47:46 +00:00
$scope . roleChanged ( { 'role' : role } ) ;
2013-11-08 04:35:27 +00:00
} else {
$scope . currentRole = role ;
2013-11-05 19:47:46 +00:00
}
} ;
}
} ;
return directiveDefinitionObject ;
} ) ;
2013-11-15 19:42:31 +00:00
quayApp . directive ( 'billingOptions' , function ( ) {
var directiveDefinitionObject = {
priority : 0 ,
templateUrl : '/static/directives/billing-options.html' ,
replace : false ,
transclude : false ,
restrict : 'C' ,
scope : {
'user' : '=user' ,
'organization' : '=organization'
} ,
2013-11-15 23:17:12 +00:00
controller : function ( $scope , $element , PlanService , Restangular ) {
2013-11-15 19:42:31 +00:00
$scope . invoice _email = false ;
2013-11-15 23:17:12 +00:00
$scope . currentCard = null ;
// Listen to plan changes.
PlanService . registerListener ( this , function ( plan ) {
if ( plan && plan . price > 0 ) {
update ( ) ;
}
} ) ;
$scope . $on ( '$destroy' , function ( ) {
PlanService . unregisterListener ( this ) ;
} ) ;
$scope . changeCard = function ( ) {
2013-11-19 22:06:17 +00:00
var previousCard = $scope . currentCard ;
2013-11-15 23:17:12 +00:00
$scope . changingCard = true ;
var callbacks = {
'opened' : function ( ) { $scope . changingCard = true ; } ,
'closed' : function ( ) { $scope . changingCard = false ; } ,
'started' : function ( ) { $scope . currentCard = null ; } ,
'success' : function ( resp ) {
$scope . currentCard = resp . card ;
$scope . changingCard = false ;
} ,
2013-11-19 22:06:17 +00:00
'failure' : function ( resp ) {
2013-11-15 23:17:12 +00:00
$scope . changingCard = false ;
2013-11-19 22:06:17 +00:00
$scope . currentCard = previousCard ;
if ( ! PlanService . isCardError ( resp ) ) {
$ ( '#cannotchangecardModal' ) . modal ( { } ) ;
}
2013-11-15 23:17:12 +00:00
}
} ;
PlanService . changeCreditCard ( $scope , $scope . organization ? $scope . organization . name : null , callbacks ) ;
} ;
$scope . getCreditImage = function ( creditInfo ) {
2013-11-15 23:34:54 +00:00
if ( ! creditInfo || ! creditInfo . type ) { return 'credit.png' ; }
2013-11-15 23:17:12 +00:00
var kind = creditInfo . type . toLowerCase ( ) || 'credit' ;
var supported = {
'american express' : 'amex' ,
'credit' : 'credit' ,
'diners club' : 'diners' ,
'discover' : 'discover' ,
'jcb' : 'jcb' ,
'mastercard' : 'mastercard' ,
'visa' : 'visa'
} ;
kind = supported [ kind ] || 'credit' ;
return kind + '.png' ;
} ;
2013-11-15 19:42:31 +00:00
var update = function ( ) {
if ( ! $scope . user && ! $scope . organization ) { return ; }
$scope . obj = $scope . user ? $scope . user : $scope . organization ;
$scope . invoice _email = $scope . obj . invoice _email ;
2013-11-15 23:17:12 +00:00
// Load the credit card information.
2013-11-15 23:58:47 +00:00
PlanService . getCardInfo ( $scope . organization ? $scope . organization . name : null , function ( card ) {
$scope . currentCard = card ;
2013-11-15 23:17:12 +00:00
} ) ;
2013-11-15 19:42:31 +00:00
} ;
var save = function ( ) {
$scope . working = true ;
var url = $scope . organization ? getRestUrl ( 'organization' , $scope . organization . name ) : 'user/' ;
var conductSave = Restangular . one ( url ) ;
conductSave . customPUT ( $scope . obj ) . then ( function ( resp ) {
$scope . working = false ;
} ) ;
} ;
var checkSave = function ( ) {
if ( ! $scope . obj ) { return ; }
if ( $scope . obj . invoice _email != $scope . invoice _email ) {
$scope . obj . invoice _email = $scope . invoice _email ;
save ( ) ;
}
} ;
$scope . $watch ( 'invoice_email' , checkSave ) ;
$scope . $watch ( 'organization' , update ) ;
$scope . $watch ( 'user' , update ) ;
}
} ;
return directiveDefinitionObject ;
} ) ;
2013-11-06 22:30:20 +00:00
quayApp . directive ( 'planManager' , function ( ) {
var directiveDefinitionObject = {
priority : 0 ,
templateUrl : '/static/directives/plan-manager.html' ,
replace : false ,
transclude : false ,
restrict : 'C' ,
scope : {
'user' : '=user' ,
2013-11-06 23:14:22 +00:00
'organization' : '=organization' ,
2013-11-15 23:17:12 +00:00
'readyForPlan' : '&readyForPlan' ,
'planChanged' : '&planChanged'
2013-11-06 22:30:20 +00:00
} ,
controller : function ( $scope , $element , PlanService , Restangular ) {
var hasSubscription = false ;
$scope . getActiveSubClass = function ( ) {
return 'active' ;
} ;
$scope . changeSubscription = function ( planId ) {
if ( $scope . planChanging ) { return ; }
2013-11-09 01:32:56 +00:00
var callbacks = {
'opening' : function ( ) { $scope . planChanging = true ; } ,
'started' : function ( ) { $scope . planChanging = true ; } ,
'opened' : function ( ) { $scope . planChanging = true ; } ,
'closed' : function ( ) { $scope . planChanging = false ; } ,
'success' : subscribedToPlan ,
2013-11-19 22:06:17 +00:00
'failure' : function ( resp ) {
$scope . planChanging = false ;
}
2013-11-09 01:32:56 +00:00
} ;
2013-11-15 23:58:47 +00:00
PlanService . changePlan ( $scope , $scope . organization , planId , callbacks ) ;
2013-11-06 22:30:20 +00:00
} ;
$scope . cancelSubscription = function ( ) {
$scope . changeSubscription ( getFreePlan ( ) ) ;
} ;
var subscribedToPlan = function ( sub ) {
$scope . subscription = sub ;
if ( sub . plan != getFreePlan ( ) ) {
hasSubscription = true ;
}
PlanService . getPlan ( sub . plan , function ( subscribedPlan ) {
$scope . subscribedPlan = subscribedPlan ;
$scope . planUsagePercent = sub . usedPrivateRepos * 100 / $scope . subscribedPlan . privateRepos ;
2013-11-15 23:17:12 +00:00
if ( $scope . planChanged ) {
$scope . planChanged ( { 'plan' : subscribedPlan } ) ;
}
2013-11-06 22:30:20 +00:00
if ( sub . usedPrivateRepos > $scope . subscribedPlan . privateRepos ) {
2013-11-06 22:59:16 +00:00
$scope . limit = 'over' ;
} else if ( sub . usedPrivateRepos == $scope . subscribedPlan . privateRepos ) {
$scope . limit = 'at' ;
2013-11-06 22:30:20 +00:00
} else if ( sub . usedPrivateRepos >= $scope . subscribedPlan . privateRepos * 0.7 ) {
2013-11-06 22:59:16 +00:00
$scope . limit = 'near' ;
2013-11-06 22:30:20 +00:00
} else {
2013-11-06 22:59:16 +00:00
$scope . limit = 'none' ;
2013-11-06 22:30:20 +00:00
}
if ( ! $scope . chart ) {
$scope . chart = new RepositoryUsageChart ( ) ;
$scope . chart . draw ( 'repository-usage-chart' ) ;
}
$scope . chart . update ( sub . usedPrivateRepos || 0 , $scope . subscribedPlan . privateRepos || 0 ) ;
$scope . planChanging = false ;
$scope . planLoading = false ;
} ) ;
} ;
var getFreePlan = function ( ) {
for ( var i = 0 ; i < $scope . plans . length ; ++ i ) {
if ( $scope . plans [ i ] . price == 0 ) {
return $scope . plans [ i ] . stripeId ;
}
}
return 'free' ;
} ;
var update = function ( ) {
$scope . planLoading = true ;
if ( ! $scope . plans ) { return ; }
PlanService . getSubscription ( $scope . organization , subscribedToPlan , function ( ) {
// User/Organization has no subscription.
subscribedToPlan ( { 'plan' : getFreePlan ( ) } ) ;
} ) ;
} ;
var loadPlans = function ( ) {
2013-11-07 20:33:56 +00:00
if ( $scope . plans || $scope . loadingPlans ) { return ; }
2013-11-06 22:59:16 +00:00
if ( ! $scope . user && ! $scope . organization ) { return ; }
2013-11-06 23:14:22 +00:00
2013-11-07 20:33:56 +00:00
$scope . loadingPlans = true ;
2013-11-06 22:30:20 +00:00
PlanService . getPlans ( function ( plans ) {
$scope . plans = plans [ $scope . organization ? 'business' : 'user' ] ;
update ( ) ;
2013-11-06 23:14:22 +00:00
if ( $scope . readyForPlan ) {
var planRequested = $scope . readyForPlan ( ) ;
2013-11-07 20:33:56 +00:00
if ( planRequested && planRequested != getFreePlan ( ) ) {
2013-11-06 23:14:22 +00:00
$scope . changeSubscription ( planRequested ) ;
}
}
2013-11-06 22:30:20 +00:00
} ) ;
} ;
// Start the initial download.
2013-11-06 22:59:16 +00:00
$scope . planLoading = true ;
loadPlans ( ) ;
2013-11-06 22:30:20 +00:00
2013-11-07 00:19:21 +00:00
$scope . $watch ( 'organization' , loadPlans ) ;
$scope . $watch ( 'user' , loadPlans ) ;
2013-11-06 22:30:20 +00:00
}
} ;
return directiveDefinitionObject ;
} ) ;
2013-11-01 21:35:26 +00:00
quayApp . directive ( 'namespaceSelector' , function ( ) {
var directiveDefinitionObject = {
priority : 0 ,
templateUrl : '/static/directives/namespace-selector.html' ,
replace : false ,
transclude : false ,
restrict : 'C' ,
scope : {
'user' : '=user' ,
2013-11-07 05:49:13 +00:00
'namespace' : '=namespace' ,
'requireCreate' : '=requireCreate'
2013-11-01 21:35:26 +00:00
} ,
2013-11-08 20:58:21 +00:00
controller : function ( $scope , $element , $routeParams , $cookieStore ) {
2013-11-07 05:49:13 +00:00
$scope . namespaces = { } ;
$scope . initialize = function ( user ) {
var namespaces = { } ;
namespaces [ user . username ] = user ;
2013-11-07 06:48:58 +00:00
if ( user . organizations ) {
for ( var i = 0 ; i < user . organizations . length ; ++ i ) {
namespaces [ user . organizations [ i ] . name ] = user . organizations [ i ] ;
}
2013-11-07 05:49:13 +00:00
}
2013-11-08 20:58:21 +00:00
var initialNamespace = $routeParams [ 'namespace' ] || $cookieStore . get ( 'quay.currentnamespace' ) || $scope . user . username ;
2013-11-07 05:49:13 +00:00
$scope . namespaces = namespaces ;
$scope . setNamespace ( $scope . namespaces [ initialNamespace ] ) ;
} ;
2013-11-01 21:35:26 +00:00
$scope . setNamespace = function ( namespaceObj ) {
if ( ! namespaceObj ) {
2013-11-07 05:49:13 +00:00
namespaceObj = $scope . namespaces [ $scope . user . username ] ;
}
if ( $scope . requireCreate && ! namespaceObj . can _create _repo ) {
namespaceObj = $scope . namespaces [ $scope . user . username ] ;
2013-11-01 21:35:26 +00:00
}
2013-11-07 05:49:13 +00:00
var newNamespace = namespaceObj . name || namespaceObj . username ;
2013-11-01 21:35:26 +00:00
$scope . namespaceObj = namespaceObj ;
2013-11-07 05:49:13 +00:00
$scope . namespace = newNamespace ;
$cookieStore . put ( 'quay.currentnamespace' , newNamespace ) ;
2013-11-01 21:35:26 +00:00
} ;
$scope . $watch ( 'user' , function ( user ) {
2013-11-07 05:49:13 +00:00
$scope . user = user ;
$scope . initialize ( user ) ;
2013-11-01 21:35:26 +00:00
} ) ;
}
} ;
return directiveDefinitionObject ;
} ) ;
2013-10-26 20:03:11 +00:00
quayApp . directive ( 'buildStatus' , function ( ) {
var directiveDefinitionObject = {
priority : 0 ,
templateUrl : '/static/directives/build-status.html' ,
replace : false ,
transclude : false ,
restrict : 'C' ,
scope : {
'build' : '=build'
} ,
controller : function ( $scope , $element ) {
$scope . getBuildProgress = function ( buildInfo ) {
switch ( buildInfo . status ) {
case 'building' :
return ( buildInfo . current _command / buildInfo . total _commands ) * 100 ;
break ;
case 'pushing' :
2013-10-28 17:31:43 +00:00
var imagePercentDecimal = ( buildInfo . image _completion _percent / 100 ) ;
2013-10-28 17:33:19 +00:00
return ( ( buildInfo . current _image + imagePercentDecimal ) / buildInfo . total _images ) * 100 ;
2013-10-26 20:03:11 +00:00
break ;
case 'complete' :
return 100 ;
break ;
case 'initializing' :
case 'starting' :
case 'waiting' :
return 0 ;
break ;
}
return - 1 ;
} ;
$scope . getBuildMessage = function ( buildInfo ) {
switch ( buildInfo . status ) {
case 'initializing' :
return 'Starting Dockerfile build' ;
break ;
case 'starting' :
case 'waiting' :
case 'building' :
return 'Building image from Dockerfile' ;
break ;
case 'pushing' :
return 'Pushing image built from Dockerfile' ;
break ;
case 'complete' :
return 'Dockerfile build completed and pushed' ;
break ;
case 'error' :
return 'Dockerfile build failed: ' + buildInfo . message ;
break ;
}
} ;
2013-10-22 05:26:14 +00:00
}
} ;
return directiveDefinitionObject ;
} ) ;
2013-11-05 22:20:43 +00:00
// Note: ngBlur is not yet in Angular stable, so we add it manaully here.
quayApp . directive ( 'ngBlur' , function ( ) {
return function ( scope , elem , attrs ) {
elem . bind ( 'blur' , function ( ) {
scope . $apply ( attrs . ngBlur ) ;
} ) ;
} ;
} ) ;
2013-11-18 22:11:06 +00:00
quayApp . run ( [ '$location' , '$rootScope' , 'Restangular' , 'UserService' , '$http' ,
function ( $location , $rootScope , Restangular , UserService , $http ) {
2013-11-12 00:26:56 +00:00
Restangular . setErrorInterceptor ( function ( response ) {
if ( response . status == 401 ) {
$ ( '#sessionexpiredModal' ) . modal ( { } ) ;
return false ;
2013-11-12 00:03:18 +00:00
}
2013-11-12 00:26:56 +00:00
return true ;
2013-11-12 00:03:18 +00:00
} ) ;
2013-09-26 23:07:25 +00:00
$rootScope . $on ( '$routeChangeSuccess' , function ( event , current , previous ) {
if ( current . $$route . title ) {
$rootScope . title = current . $$route . title ;
}
2013-11-19 00:03:35 +00:00
if ( current . $$route . description ) {
$rootScope . description = current . $$route . description ;
} else {
$rootScope . description = 'Hosted private docker repositories. Includes full user management and history. Free for public repositories.' ;
}
2013-11-20 21:17:47 +00:00
$rootScope . hideFooter = ! ! current . $$route . hideFooter ;
2013-09-26 23:07:25 +00:00
} ) ;
2013-11-18 22:11:06 +00:00
var initallyChecked = false ;
window . _ _isLoading = function ( ) {
if ( ! initallyChecked ) {
initallyChecked = true ;
return true ;
}
return $http . pendingRequests . length > 0 ;
} ;
2013-09-27 20:12:51 +00:00
} ] ) ;