Merge remote-tracking branch 'origin/master' into comewithmeifyouwanttowork
Conflicts: data/model/legacy.py static/js/app.js
This commit is contained in:
commit
c5ca46a14b
70 changed files with 1566 additions and 630 deletions
|
@ -21,8 +21,7 @@
|
|||
|
||||
|
||||
#quay-logo {
|
||||
width: 80px;
|
||||
margin-right: 30px;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
#padding-container {
|
||||
|
@ -2568,7 +2567,7 @@ p.editable:hover i {
|
|||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.repo-build .build-log-error-element {
|
||||
.repo-build .build-log-error-element .error-message-container {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin: 10px;
|
||||
|
@ -2578,7 +2577,7 @@ p.editable:hover i {
|
|||
margin-left: 22px;
|
||||
}
|
||||
|
||||
.repo-build .build-log-error-element i.fa {
|
||||
.repo-build .build-log-error-element .error-message-container i.fa {
|
||||
color: red;
|
||||
position: absolute;
|
||||
top: 13px;
|
||||
|
@ -4598,6 +4597,27 @@ i.quay-icon {
|
|||
height: 16px;
|
||||
}
|
||||
|
||||
i.flowdock-icon {
|
||||
background-image: url(/static/img/flowdock.ico);
|
||||
background-size: 16px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
i.hipchat-icon {
|
||||
background-image: url(/static/img/hipchat.png);
|
||||
background-size: 16px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
i.slack-icon {
|
||||
background-image: url(/static/img/slack.ico);
|
||||
background-size: 16px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.external-notification-view-element {
|
||||
margin: 10px;
|
||||
padding: 6px;
|
||||
|
|
|
@ -1,4 +1,23 @@
|
|||
<span bindonce class="build-log-error-element">
|
||||
<i class="fa fa-exclamation-triangle"></i>
|
||||
<span class="error-message" bo-text="error.message"></span>
|
||||
</span>
|
||||
<div bindonce class="build-log-error-element">
|
||||
<span class="error-message-container">
|
||||
<i class="fa fa-exclamation-triangle"></i>
|
||||
<span class="error-message" bo-text="error.message"></span>
|
||||
<span ng-if="error.message == 'HTTP code: 403' && getLocalPullInfo().isLocal">
|
||||
caused by attempting to pull private repository <a href="/repository/{{ getLocalPullInfo().repo }}">{{ getLocalPullInfo().repo }}</a>
|
||||
<span ng-if="getLocalPullInfo().login">with inaccessible crdentials</span>
|
||||
<span ng-if="!getLocalPullInfo().login">without credentials</span>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
|
||||
<div class="alert alert-danger" ng-if="error.message == 'HTTP code: 403' && getLocalPullInfo().isLocal">
|
||||
<div ng-if="getLocalPullInfo().login">
|
||||
Note: The credentials <b>{{ getLocalPullInfo().login.username }}</b> for registry <b>{{ getLocalPullInfo().login.registry }}</b> cannot
|
||||
access repository <a href="/repository/{{ getLocalPullInfo().repo }}">{{ getLocalPullInfo().repo }}</a>.
|
||||
</div>
|
||||
<div ng-if="!getLocalPullInfo().login">
|
||||
Note: No robot account is specified for this build. Without such credentials, this pull will always fail. Please setup a new
|
||||
build trigger with a robot account that has access to <a href="/repository/{{ getLocalPullInfo().repo }}">{{ getLocalPullInfo().repo }}</a> or make that repository public.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -73,7 +73,7 @@
|
|||
<tr ng-if="currentMethod.fields.length"><td colspan="2"><hr></td></tr>
|
||||
|
||||
<tr ng-repeat="field in currentMethod.fields">
|
||||
<td>{{ field.title }}:</td>
|
||||
<td valign="top">{{ field.title }}:</td>
|
||||
<td>
|
||||
<div ng-switch on="field.type">
|
||||
<span ng-switch-when="email">
|
||||
|
@ -86,7 +86,11 @@
|
|||
current-entity="currentConfig[field.name]"
|
||||
ng-model="currentConfig[field.name]"
|
||||
allowed-entities="['user', 'team', 'org']"
|
||||
ng-switch-when="entity">
|
||||
ng-switch-when="entity"></div>
|
||||
|
||||
<div ng-if="getHelpUrl(field, currentConfig)" style="margin-top: 10px">
|
||||
See: <a href="{{ getHelpUrl(field, currentConfig) }}" target="_blank">{{ getHelpUrl(field, currentConfig) }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<div class="current-item">
|
||||
<div class="dropdown-select-icon-transclude"></div>
|
||||
<input type="text" class="lookahead-input form-control" placeholder="{{ placeholder }}"
|
||||
ng-readonly="!lookaheadItems || !lookaheadItems.length"></input>
|
||||
ng-readonly="!allowCustomInput"></input>
|
||||
</div>
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-default dropdown-toggle" type="button" data-toggle="dropdown">
|
||||
|
|
17
static/directives/external-login-button.html
Normal file
17
static/directives/external-login-button.html
Normal file
|
@ -0,0 +1,17 @@
|
|||
<span class="external-login-button-element">
|
||||
<span ng-if="provider == 'github'">
|
||||
<a href="javascript:void(0)" class="btn btn-primary btn-block" quay-require="['GITHUB_LOGIN']" ng-click="startSignin('github')" style="margin-bottom: 10px">
|
||||
<i class="fa fa-github fa-lg"></i>
|
||||
<span ng-if="action != 'attach'">Sign In with GitHub</span>
|
||||
<span ng-if="action == 'attach'">Attach to GitHub Account</span>
|
||||
</a>
|
||||
</span>
|
||||
|
||||
<span ng-if="provider == 'google'">
|
||||
<a href="javascript:void(0)" class="btn btn-primary btn-block" quay-require="['GOOGLE_LOGIN']" ng-click="startSignin('google')">
|
||||
<i class="fa fa-google fa-lg"></i>
|
||||
<span ng-if="action != 'attach'">Sign In with Google</span>
|
||||
<span ng-if="action == 'attach'">Attach to Google Account</span>
|
||||
</a>
|
||||
</span>
|
||||
</span>
|
|
@ -4,7 +4,7 @@
|
|||
≡
|
||||
</button>
|
||||
<a class="navbar-brand" href="/" target="{{ appLinkTarget() }}">
|
||||
<img id="quay-logo" src="/static/img/black-horizontal.svg">
|
||||
<img id="quay-logo" src="/static/img/quay-logo.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -4,18 +4,23 @@
|
|||
placeholder="Username or E-mail Address" ng-model="user.username" autofocus>
|
||||
<input type="password" class="form-control input-lg" name="password"
|
||||
placeholder="Password" ng-model="user.password">
|
||||
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign In</button>
|
||||
|
||||
<span class="social-alternate" quay-require="['GITHUB_LOGIN']">
|
||||
<i class="fa fa-circle"></i>
|
||||
<span class="inner-text">OR</span>
|
||||
</span>
|
||||
|
||||
<a id="github-signin-link" class="btn btn-primary btn-lg btn-block" href="javascript:void(0)" ng-click="showGithub()"
|
||||
quay-require="['GITHUB_LOGIN']">
|
||||
<i class="fa fa-github fa-lg"></i> Sign In with GitHub
|
||||
</a>
|
||||
</form>
|
||||
<div class="alert alert-warning" ng-show="tryAgainSoon > 0">
|
||||
Too many attempts have been made to login. Please try again in {{ tryAgainSoon }} second<span ng-if="tryAgainSoon != 1">s</span>.
|
||||
</div>
|
||||
|
||||
<span ng-show="tryAgainSoon == 0">
|
||||
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign In</button>
|
||||
|
||||
<span class="social-alternate" quay-show="Features.GITHUB_LOGIN || Features.GOOGLE_LOGIN">
|
||||
<i class="fa fa-circle"></i>
|
||||
<span class="inner-text">OR</span>
|
||||
</span>
|
||||
|
||||
<div class="external-login-button" provider="github" redirect-url="redirectUrl" sign-in-started="markStarted()"></div>
|
||||
<div class="external-login-button" provider="google" redirect-url="redirectUrl" sign-in-started="markStarted()"></div>
|
||||
</span>
|
||||
</form>
|
||||
|
||||
<div class="alert alert-danger" ng-show="invalidCredentials">Invalid username or password.</div>
|
||||
<div class="alert alert-danger" ng-show="needsEmailVerification">
|
||||
|
|
|
@ -18,10 +18,8 @@
|
|||
<i class="fa fa-circle"></i>
|
||||
<span class="inner-text">OR</span>
|
||||
</span>
|
||||
<a href="https://github.com/login/oauth/authorize?client_id={{ githubClientId }}&scope=user:email{{ github_state_clause }}"
|
||||
class="btn btn-primary btn-block" quay-require="['GITHUB_LOGIN']">
|
||||
<i class="fa fa-github fa-lg"></i> Sign In with GitHub
|
||||
</a>
|
||||
<div class="external-login-button" provider="github"></div>
|
||||
<div class="external-login-button" provider="google"></div>
|
||||
</div>
|
||||
</form>
|
||||
<div ng-show="registering" style="text-align: center">
|
||||
|
|
|
@ -29,7 +29,8 @@
|
|||
<div class="slideinout" ng-show="currentRepo">
|
||||
<div style="margin-top: 10px">Dockerfile Location:</div>
|
||||
<div class="dropdown-select" placeholder="'(Repository Root)'" selected-item="currentLocation"
|
||||
lookahead-items="locations" handle-input="handleLocationInput(input)" handle-item-selected="handleLocationSelected(datum)">
|
||||
lookahead-items="locations" handle-input="handleLocationInput(input)" handle-item-selected="handleLocationSelected(datum)"
|
||||
allow-custom-input="true">
|
||||
<!-- Icons -->
|
||||
<i class="dropdown-select-icon none-icon fa fa-folder-o fa-lg" ng-show="isInvalidLocation"></i>
|
||||
<i class="dropdown-select-icon none-icon fa fa-folder fa-lg" style="color: black;" ng-show="!isInvalidLocation"></i>
|
||||
|
|
BIN
static/img/flowdock.ico
Normal file
BIN
static/img/flowdock.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.4 KiB |
BIN
static/img/hipchat.png
Normal file
BIN
static/img/hipchat.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.1 KiB |
BIN
static/img/slack.ico
Normal file
BIN
static/img/slack.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
464
static/js/app.js
464
static/js/app.js
|
@ -153,6 +153,14 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading
|
|||
this.currentIndex_ = 0;
|
||||
}
|
||||
|
||||
_ViewArray.prototype.length = function() {
|
||||
return this.entries.length;
|
||||
};
|
||||
|
||||
_ViewArray.prototype.get = function(index) {
|
||||
return this.entries[index];
|
||||
};
|
||||
|
||||
_ViewArray.prototype.push = function(elem) {
|
||||
this.entries.push(elem);
|
||||
this.hasEntries = true;
|
||||
|
@ -384,7 +392,7 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading
|
|||
var uiService = {};
|
||||
|
||||
uiService.hidePopover = function(elem) {
|
||||
var popover = $('#signupButton').data('bs.popover');
|
||||
var popover = $(elem).data('bs.popover');
|
||||
if (popover) {
|
||||
popover.hide();
|
||||
}
|
||||
|
@ -446,6 +454,29 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading
|
|||
var pingService = {};
|
||||
var pingCache = {};
|
||||
|
||||
var invokeCallback = function($scope, pings, callback) {
|
||||
if (pings[0] == -1) {
|
||||
setTimeout(function() {
|
||||
$scope.$apply(function() {
|
||||
callback(-1, false, -1);
|
||||
});
|
||||
}, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
var sum = 0;
|
||||
for (var i = 0; i < pings.length; ++i) {
|
||||
sum += pings[i];
|
||||
}
|
||||
|
||||
// Report the average ping.
|
||||
setTimeout(function() {
|
||||
$scope.$apply(function() {
|
||||
callback(Math.floor(sum / pings.length), true, pings.length);
|
||||
});
|
||||
}, 0);
|
||||
};
|
||||
|
||||
var reportPingResult = function($scope, url, ping, callback) {
|
||||
// Lookup the cached ping data, if any.
|
||||
var cached = pingCache[url];
|
||||
|
@ -458,28 +489,15 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading
|
|||
// If an error occurred, report it and done.
|
||||
if (ping < 0) {
|
||||
cached['pings'] = [-1];
|
||||
setTimeout(function() {
|
||||
$scope.$apply(function() {
|
||||
callback(-1, false, -1);
|
||||
});
|
||||
}, 0);
|
||||
invokeCallback($scope, pings, callback);
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, add the current ping and determine the average.
|
||||
cached['pings'].push(ping);
|
||||
|
||||
var sum = 0;
|
||||
for (var i = 0; i < cached['pings'].length; ++i) {
|
||||
sum += cached['pings'][i];
|
||||
}
|
||||
|
||||
// Report the average ping.
|
||||
setTimeout(function() {
|
||||
$scope.$apply(function() {
|
||||
callback(Math.floor(sum / cached['pings'].length), true, cached['pings'].length);
|
||||
});
|
||||
}, 0);
|
||||
// Invoke the callback.
|
||||
invokeCallback($scope, cached['pings'], callback);
|
||||
|
||||
// Schedule another check if we've done less than three.
|
||||
if (cached['pings'].length < 3) {
|
||||
|
@ -515,12 +533,7 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading
|
|||
|
||||
pingService.pingUrl = function($scope, url, callback) {
|
||||
if (pingCache[url]) {
|
||||
cached = pingCache[url];
|
||||
setTimeout(function() {
|
||||
$scope.$apply(function() {
|
||||
callback(cached.result, cached.success);
|
||||
});
|
||||
}, 0);
|
||||
invokeCallback($scope, pingCache[url]['pings'], callback);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -558,6 +571,41 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading
|
|||
$provide.factory('StringBuilderService', ['$sce', 'UtilService', function($sce, UtilService) {
|
||||
var stringBuilderService = {};
|
||||
|
||||
stringBuilderService.buildUrl = function(value_or_func, metadata) {
|
||||
var url = value_or_func;
|
||||
if (typeof url != 'string') {
|
||||
url = url(metadata);
|
||||
}
|
||||
|
||||
// Find the variables to be replaced.
|
||||
var varNames = [];
|
||||
for (var i = 0; i < url.length; ++i) {
|
||||
var c = url[i];
|
||||
if (c == '{') {
|
||||
for (var j = i + 1; j < url.length; ++j) {
|
||||
var d = url[j];
|
||||
if (d == '}') {
|
||||
varNames.push(url.substring(i + 1, j));
|
||||
i = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Replace all variables found.
|
||||
for (var i = 0; i < varNames.length; ++i) {
|
||||
var varName = varNames[i];
|
||||
if (!metadata[varName]) {
|
||||
return null;
|
||||
}
|
||||
|
||||
url = url.replace('{' + varName + '}', metadata[varName]);
|
||||
}
|
||||
|
||||
return url;
|
||||
};
|
||||
|
||||
stringBuilderService.buildString = function(value_or_func, metadata) {
|
||||
var fieldIcons = {
|
||||
'inviter': 'user',
|
||||
|
@ -716,7 +764,7 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading
|
|||
return config;
|
||||
}]);
|
||||
|
||||
$provide.factory('ApiService', ['Restangular', function(Restangular) {
|
||||
$provide.factory('ApiService', ['Restangular', '$q', function(Restangular, $q) {
|
||||
var apiService = {};
|
||||
|
||||
var getResource = function(path, opt_background) {
|
||||
|
@ -830,6 +878,77 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading
|
|||
}
|
||||
};
|
||||
|
||||
var freshLoginFailCheck = function(opName, opArgs) {
|
||||
return function(resp) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
// If the error is a fresh login required, show the dialog.
|
||||
if (resp.status == 401 && resp.data['error_type'] == 'fresh_login_required') {
|
||||
var verifyNow = function() {
|
||||
var info = {
|
||||
'password': $('#freshPassword').val()
|
||||
};
|
||||
|
||||
$('#freshPassword').val('');
|
||||
|
||||
// Conduct the sign in of the user.
|
||||
apiService.verifyUser(info).then(function() {
|
||||
// On success, retry the operation. if it succeeds, then resolve the
|
||||
// deferred promise with the result. Otherwise, reject the same.
|
||||
apiService[opName].apply(apiService, opArgs).then(function(resp) {
|
||||
deferred.resolve(resp);
|
||||
}, function(resp) {
|
||||
deferred.reject(resp);
|
||||
});
|
||||
}, function(resp) {
|
||||
// Reject with the sign in error.
|
||||
deferred.reject({'data': {'message': 'Invalid verification credentials'}});
|
||||
});
|
||||
};
|
||||
|
||||
var box = bootbox.dialog({
|
||||
"message": 'It has been more than a few minutes since you last logged in, ' +
|
||||
'so please verify your password to perform this sensitive operation:' +
|
||||
'<form style="margin-top: 10px" action="javascript:void(0)">' +
|
||||
'<input id="freshPassword" class="form-control" type="password" placeholder="Current Password">' +
|
||||
'</form>',
|
||||
"title": 'Please Verify',
|
||||
"buttons": {
|
||||
"verify": {
|
||||
"label": "Verify",
|
||||
"className": "btn-success",
|
||||
"callback": verifyNow
|
||||
},
|
||||
"close": {
|
||||
"label": "Cancel",
|
||||
"className": "btn-default",
|
||||
"callback": function() {
|
||||
deferred.reject({'data': {'message': 'Verification canceled'}});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
box.bind('shown.bs.modal', function(){
|
||||
box.find("input").focus();
|
||||
box.find("form").submit(function() {
|
||||
if (!$('#freshPassword').val()) { return; }
|
||||
|
||||
box.modal('hide');
|
||||
verifyNow();
|
||||
});
|
||||
});
|
||||
|
||||
// Return a new promise. We'll accept or reject it based on the result
|
||||
// of the login.
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
// Otherwise, we just 'raise' the error via the reject method on the promise.
|
||||
return $q.reject(resp);
|
||||
};
|
||||
};
|
||||
|
||||
var buildMethodsForOperation = function(operation, resource, resourceMap) {
|
||||
var method = operation['method'].toLowerCase();
|
||||
var operationName = operation['nickname'];
|
||||
|
@ -843,7 +962,15 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading
|
|||
'ignoreLoadingBar': true
|
||||
});
|
||||
}
|
||||
return one['custom' + method.toUpperCase()](opt_options);
|
||||
|
||||
var opObj = one['custom' + method.toUpperCase()](opt_options);
|
||||
|
||||
// If the operation requires_fresh_login, then add a specialized error handler that
|
||||
// will defer the operation's result if sudo is requested.
|
||||
if (operation['requires_fresh_login']) {
|
||||
opObj = opObj.catch(freshLoginFailCheck(operationName, arguments));
|
||||
}
|
||||
return opObj;
|
||||
};
|
||||
|
||||
// If the method for the operation is a GET, add an operationAsResource method.
|
||||
|
@ -1141,6 +1268,54 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading
|
|||
'title': 'Webhook URL'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'id': 'flowdock',
|
||||
'title': 'Flowdock Team Notification',
|
||||
'icon': 'flowdock-icon',
|
||||
'fields': [
|
||||
{
|
||||
'name': 'flow_api_token',
|
||||
'type': 'string',
|
||||
'title': 'Flow API Token',
|
||||
'help_url': 'https://www.flowdock.com/account/tokens'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'id': 'hipchat',
|
||||
'title': 'HipChat Room Notification',
|
||||
'icon': 'hipchat-icon',
|
||||
'fields': [
|
||||
{
|
||||
'name': 'room_id',
|
||||
'type': 'string',
|
||||
'title': 'Room ID #'
|
||||
},
|
||||
{
|
||||
'name': 'notification_token',
|
||||
'type': 'string',
|
||||
'title': 'Notification Token'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'id': 'slack',
|
||||
'title': 'Slack Room Notification',
|
||||
'icon': 'slack-icon',
|
||||
'fields': [
|
||||
{
|
||||
'name': 'subdomain',
|
||||
'type': 'string',
|
||||
'title': 'Slack Subdomain'
|
||||
},
|
||||
{
|
||||
'name': 'token',
|
||||
'type': 'string',
|
||||
'title': 'Token',
|
||||
'help_url': 'https://{subdomain}.slack.com/services/new/incoming-webhook'
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -1396,10 +1571,41 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading
|
|||
var keyService = {}
|
||||
|
||||
keyService['stripePublishableKey'] = Config['STRIPE_PUBLISHABLE_KEY'];
|
||||
|
||||
keyService['githubClientId'] = Config['GITHUB_CLIENT_ID'];
|
||||
keyService['githubLoginClientId'] = Config['GITHUB_LOGIN_CLIENT_ID'];
|
||||
keyService['githubRedirectUri'] = Config.getUrl('/oauth2/github/callback');
|
||||
|
||||
keyService['googleLoginClientId'] = Config['GOOGLE_LOGIN_CLIENT_ID'];
|
||||
keyService['googleRedirectUri'] = Config.getUrl('/oauth2/google/callback');
|
||||
|
||||
keyService['googleLoginUrl'] = 'https://accounts.google.com/o/oauth2/auth?response_type=code&';
|
||||
keyService['githubLoginUrl'] = 'https://github.com/login/oauth/authorize?';
|
||||
|
||||
keyService['googleLoginScope'] = 'openid email';
|
||||
keyService['githubLoginScope'] = 'user:email';
|
||||
|
||||
keyService.getExternalLoginUrl = function(service, action) {
|
||||
var state_clause = '';
|
||||
if (Config.MIXPANEL_KEY && window.mixpanel) {
|
||||
if (mixpanel.get_distinct_id !== undefined) {
|
||||
state_clause = "&state=" + encodeURIComponent(mixpanel.get_distinct_id());
|
||||
}
|
||||
}
|
||||
|
||||
var client_id = keyService[service + 'LoginClientId'];
|
||||
var scope = keyService[service + 'LoginScope'];
|
||||
var redirect_uri = keyService[service + 'RedirectUri'];
|
||||
if (action == 'attach') {
|
||||
redirect_uri += '/attach';
|
||||
}
|
||||
|
||||
var url = keyService[service + 'LoginUrl'] + 'client_id=' + client_id + '&scope=' + scope +
|
||||
'&redirect_uri=' + redirect_uri + state_clause;
|
||||
|
||||
return url;
|
||||
};
|
||||
|
||||
return keyService;
|
||||
}]);
|
||||
|
||||
|
@ -1805,7 +2011,7 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading
|
|||
when('/repository/:namespace/:name/build', {templateUrl: '/static/partials/repo-build.html', controller:RepoBuildCtrl, reloadOnSearch: false}).
|
||||
when('/repository/:namespace/:name/build/:buildid/buildpack', {templateUrl: '/static/partials/build-package.html', controller:BuildPackageCtrl, reloadOnSearch: false}).
|
||||
when('/repository/', {title: 'Repositories', description: 'Public and private docker repositories list',
|
||||
templateUrl: '/static/partials/repo-list.html', controller: RepoListCtrl}).
|
||||
templateUrl: '/static/partials/repo-list.html', controller: RepoListCtrl, reloadOnSearch: false}).
|
||||
when('/user/', {title: 'Account Settings', description:'Account settings for ' + title, templateUrl: '/static/partials/user-admin.html',
|
||||
reloadOnSearch: false, controller: UserAdminCtrl}).
|
||||
when('/superuser/', {title: 'Superuser Admin Panel', description:'Admin panel for ' + title, templateUrl: '/static/partials/super-user.html',
|
||||
|
@ -2339,6 +2545,45 @@ quayApp.directive('userSetup', function () {
|
|||
});
|
||||
|
||||
|
||||
quayApp.directive('externalLoginButton', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/external-login-button.html',
|
||||
replace: false,
|
||||
transclude: true,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'signInStarted': '&signInStarted',
|
||||
'redirectUrl': '=redirectUrl',
|
||||
'provider': '@provider',
|
||||
'action': '@action'
|
||||
},
|
||||
controller: function($scope, $timeout, $interval, ApiService, KeyService, CookieService, Features, Config) {
|
||||
var getRedirectUrl = function() {
|
||||
return $scope.redirectUrl;
|
||||
};
|
||||
|
||||
$scope.startSignin = function(service) {
|
||||
$scope.signInStarted({'service': service});
|
||||
|
||||
var url = KeyService.getExternalLoginUrl(service, $scope.action || 'login');
|
||||
|
||||
// Save the redirect URL in a cookie so that we can redirect back after the service returns to us.
|
||||
var redirectURL = getRedirectUrl() || window.location.toString();
|
||||
CookieService.putPermanent('quay.redirectAfterLoad', redirectURL);
|
||||
|
||||
// Needed to ensure that UI work done by the started callback is finished before the location
|
||||
// changes.
|
||||
$timeout(function() {
|
||||
document.location = url;
|
||||
}, 250);
|
||||
};
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
||||
|
||||
|
||||
quayApp.directive('signinForm', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
|
@ -2351,33 +2596,9 @@ quayApp.directive('signinForm', function () {
|
|||
'signInStarted': '&signInStarted',
|
||||
'signedIn': '&signedIn'
|
||||
},
|
||||
controller: function($scope, $location, $timeout, ApiService, KeyService, UserService, CookieService, Features, Config) {
|
||||
var getRedirectUrl = function() {
|
||||
return $scope.redirectUrl;
|
||||
};
|
||||
|
||||
$scope.showGithub = function() {
|
||||
if (!Features.GITHUB_LOGIN) { return; }
|
||||
|
||||
$scope.markStarted();
|
||||
|
||||
var mixpanelDistinctIdClause = '';
|
||||
if (Config.MIXPANEL_KEY && mixpanel.get_distinct_id !== undefined) {
|
||||
$scope.mixpanelDistinctIdClause = "&state=" + encodeURIComponent(mixpanel.get_distinct_id());
|
||||
}
|
||||
|
||||
// Save the redirect URL in a cookie so that we can redirect back after GitHub returns to us.
|
||||
var redirectURL = getRedirectUrl() || window.location.toString();
|
||||
CookieService.putPermanent('quay.redirectAfterLoad', redirectURL);
|
||||
|
||||
// Needed to ensure that UI work done by the started callback is finished before the location
|
||||
// changes.
|
||||
$timeout(function() {
|
||||
var url = 'https://github.com/login/oauth/authorize?client_id=' + encodeURIComponent(KeyService.githubLoginClientId) +
|
||||
'&scope=user:email' + mixpanelDistinctIdClause;
|
||||
document.location = url;
|
||||
}, 250);
|
||||
};
|
||||
controller: function($scope, $location, $timeout, $interval, ApiService, KeyService, UserService, CookieService, Features, Config) {
|
||||
$scope.tryAgainSoon = 0;
|
||||
$scope.tryAgainInterval = null;
|
||||
|
||||
$scope.markStarted = function() {
|
||||
if ($scope.signInStarted != null) {
|
||||
|
@ -2385,8 +2606,29 @@ quayApp.directive('signinForm', function () {
|
|||
}
|
||||
};
|
||||
|
||||
$scope.cancelInterval = function() {
|
||||
$scope.tryAgainSoon = 0;
|
||||
|
||||
if ($scope.tryAgainInterval) {
|
||||
$interval.cancel($scope.tryAgainInterval);
|
||||
}
|
||||
|
||||
$scope.tryAgainInterval = null;
|
||||
};
|
||||
|
||||
$scope.$watch('user.username', function() {
|
||||
$scope.cancelInterval();
|
||||
});
|
||||
|
||||
$scope.$on('$destroy', function() {
|
||||
$scope.cancelInterval();
|
||||
});
|
||||
|
||||
$scope.signin = function() {
|
||||
if ($scope.tryAgainSoon > 0) { return; }
|
||||
|
||||
$scope.markStarted();
|
||||
$scope.cancelInterval();
|
||||
|
||||
ApiService.signinUser($scope.user).then(function() {
|
||||
$scope.needsEmailVerification = false;
|
||||
|
@ -2410,8 +2652,23 @@ quayApp.directive('signinForm', function () {
|
|||
window.location = (redirectUrl ? redirectUrl : '/');
|
||||
}, 500);
|
||||
}, function(result) {
|
||||
$scope.needsEmailVerification = result.data.needsEmailVerification;
|
||||
$scope.invalidCredentials = result.data.invalidCredentials;
|
||||
if (result.status == 429 /* try again later */) {
|
||||
$scope.needsEmailVerification = false;
|
||||
$scope.invalidCredentials = false;
|
||||
|
||||
$scope.cancelInterval();
|
||||
|
||||
$scope.tryAgainSoon = result.headers('Retry-After');
|
||||
$scope.tryAgainInterval = $interval(function() {
|
||||
$scope.tryAgainSoon--;
|
||||
if ($scope.tryAgainSoon <= 0) {
|
||||
$scope.cancelInterval();
|
||||
}
|
||||
}, 1000, $scope.tryAgainSoon);
|
||||
} else {
|
||||
$scope.needsEmailVerification = result.data.needsEmailVerification;
|
||||
$scope.invalidCredentials = result.data.invalidCredentials;
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
@ -2432,18 +2689,9 @@ quayApp.directive('signupForm', function () {
|
|||
|
||||
'userRegistered': '&userRegistered'
|
||||
},
|
||||
controller: function($scope, $location, $timeout, ApiService, KeyService, UserService, Config, UIService) {
|
||||
controller: function($scope, $location, $timeout, ApiService, KeyService, UserService, Config, UIService) {
|
||||
$('.form-signup').popover();
|
||||
|
||||
if (Config.MIXPANEL_KEY) {
|
||||
angulartics.waitForVendorApi(mixpanel, 500, function(loadedMixpanel) {
|
||||
var mixpanelId = loadedMixpanel.get_distinct_id();
|
||||
$scope.github_state_clause = '&state=' + mixpanelId;
|
||||
});
|
||||
}
|
||||
|
||||
$scope.githubClientId = KeyService.githubLoginClientId;
|
||||
|
||||
$scope.awaitingConfirmation = false;
|
||||
$scope.registering = false;
|
||||
|
||||
|
@ -2545,7 +2793,11 @@ quayApp.directive('dockerAuthDialog', function (Config) {
|
|||
},
|
||||
controller: function($scope, $element) {
|
||||
var updateCommand = function() {
|
||||
$scope.command = 'docker login -e="." -u="' + $scope.username +
|
||||
var escape = function(v) {
|
||||
if (!v) { return v; }
|
||||
return v.replace('$', '\\$');
|
||||
};
|
||||
$scope.command = 'docker login -e="." -u="' + escape($scope.username) +
|
||||
'" -p="' + $scope.token + '" ' + Config['SERVER_HOSTNAME'];
|
||||
};
|
||||
|
||||
|
@ -4045,9 +4297,11 @@ quayApp.directive('billingOptions', function () {
|
|||
|
||||
var save = function() {
|
||||
$scope.working = true;
|
||||
|
||||
var errorHandler = ApiService.errorDisplay('Could not change user details');
|
||||
ApiService.changeDetails($scope.organization, $scope.obj).then(function(resp) {
|
||||
$scope.working = false;
|
||||
});
|
||||
}, errorHandler);
|
||||
};
|
||||
|
||||
var checkSave = function() {
|
||||
|
@ -4207,7 +4461,7 @@ quayApp.directive('namespaceSelector', function () {
|
|||
'namespace': '=namespace',
|
||||
'requireCreate': '=requireCreate'
|
||||
},
|
||||
controller: function($scope, $element, $routeParams, CookieService) {
|
||||
controller: function($scope, $element, $routeParams, $location, CookieService) {
|
||||
$scope.namespaces = {};
|
||||
|
||||
$scope.initialize = function(user) {
|
||||
|
@ -4244,6 +4498,10 @@ quayApp.directive('namespaceSelector', function () {
|
|||
|
||||
if (newNamespace) {
|
||||
CookieService.putPermanent('quay.namespace', newNamespace);
|
||||
|
||||
if ($routeParams['namespace'] && $routeParams['namespace'] != newNamespace) {
|
||||
$location.search({'namespace': newNamespace});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -4288,9 +4546,48 @@ quayApp.directive('buildLogError', function () {
|
|||
transclude: false,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'error': '=error'
|
||||
'error': '=error',
|
||||
'entries': '=entries'
|
||||
},
|
||||
controller: function($scope, $element) {
|
||||
controller: function($scope, $element, Config) {
|
||||
$scope.getLocalPullInfo = function() {
|
||||
if ($scope.entries.__localpull !== undefined) {
|
||||
return $scope.entries.__localpull;
|
||||
}
|
||||
|
||||
var localInfo = {
|
||||
'isLocal': false
|
||||
};
|
||||
|
||||
// Find the 'pulling' phase entry, and then extra any metadata found under
|
||||
// it.
|
||||
for (var i = 0; i < $scope.entries.length; ++i) {
|
||||
var entry = $scope.entries[i];
|
||||
if (entry.type == 'phase' && entry.message == 'pulling') {
|
||||
for (var j = 0; j < entry.logs.length(); ++j) {
|
||||
var log = entry.logs.get(j);
|
||||
if (log.data && log.data.phasestep == 'login') {
|
||||
localInfo['login'] = log.data;
|
||||
}
|
||||
|
||||
if (log.data && log.data.phasestep == 'pull') {
|
||||
var repo_url = log.data['repo_url'];
|
||||
var repo_and_tag = repo_url.substring(Config.SERVER_HOSTNAME.length + 1);
|
||||
var tagIndex = repo_and_tag.lastIndexOf(':');
|
||||
var repo = repo_and_tag.substring(0, tagIndex);
|
||||
|
||||
localInfo['repo_url'] = repo_url;
|
||||
localInfo['repo'] = repo;
|
||||
|
||||
localInfo['isLocal'] = repo_url.indexOf(Config.SERVER_HOSTNAME + '/') == 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $scope.entries.__localpull = localInfo;
|
||||
};
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
|
@ -4326,6 +4623,9 @@ quayApp.directive('dropdownSelect', function ($compile) {
|
|||
'selectedItem': '=selectedItem',
|
||||
'placeholder': '=placeholder',
|
||||
'lookaheadItems': '=lookaheadItems',
|
||||
|
||||
'allowCustomInput': '@allowCustomInput',
|
||||
|
||||
'handleItemSelected': '&handleItemSelected',
|
||||
'handleInput': '&handleInput',
|
||||
|
||||
|
@ -5084,7 +5384,7 @@ quayApp.directive('createExternalNotificationDialog', function () {
|
|||
'counter': '=counter',
|
||||
'notificationCreated': '¬ificationCreated'
|
||||
},
|
||||
controller: function($scope, $element, ExternalNotificationData, ApiService, $timeout) {
|
||||
controller: function($scope, $element, ExternalNotificationData, ApiService, $timeout, StringBuilderService) {
|
||||
$scope.currentEvent = null;
|
||||
$scope.currentMethod = null;
|
||||
$scope.status = '';
|
||||
|
@ -5184,6 +5484,15 @@ quayApp.directive('createExternalNotificationDialog', function () {
|
|||
}, 1000);
|
||||
};
|
||||
|
||||
$scope.getHelpUrl = function(field, config) {
|
||||
var helpUrl = field['help_url'];
|
||||
if (!helpUrl) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return StringBuilderService.buildUrl(helpUrl, config);
|
||||
};
|
||||
|
||||
$scope.$watch('counter', function(counter) {
|
||||
if (counter) {
|
||||
$scope.clearCounter++;
|
||||
|
@ -5571,7 +5880,9 @@ quayApp.directive('locationView', function () {
|
|||
|
||||
$scope.getLocationTooltip = function(location, ping) {
|
||||
var tip = $scope.getLocationTitle(location) + '<br>';
|
||||
if (ping < 0) {
|
||||
if (ping == null) {
|
||||
tip += '(Loading)';
|
||||
} else if (ping < 0) {
|
||||
tip += '<br><b>Note: Could not contact server</b>';
|
||||
} else {
|
||||
tip += 'Estimated Ping: ' + (ping ? ping + 'ms' : '(Loading)');
|
||||
|
@ -5819,11 +6130,10 @@ quayApp.run(['$location', '$rootScope', 'Restangular', 'UserService', 'PlanServi
|
|||
|
||||
// Handle session expiration.
|
||||
Restangular.setErrorInterceptor(function(response) {
|
||||
if (response.status == 401) {
|
||||
if (response.data['session_required'] == null || response.data['session_required'] === true) {
|
||||
$('#sessionexpiredModal').modal({});
|
||||
return false;
|
||||
}
|
||||
if (response.status == 401 && response.data['error_type'] == 'invalid_token' &&
|
||||
response.data['session_required'] !== false) {
|
||||
$('#sessionexpiredModal').modal({});
|
||||
return false;
|
||||
}
|
||||
|
||||
if (response.status == 503) {
|
||||
|
|
|
@ -527,16 +527,24 @@ function RepoCtrl($scope, $sanitize, Restangular, ImageMetadataService, ApiServi
|
|||
|
||||
$scope.deleteTag = function(tagName) {
|
||||
if (!$scope.repo.can_admin) { return; }
|
||||
$('#confirmdeleteTagModal').modal('hide');
|
||||
|
||||
var params = {
|
||||
'repository': namespace + '/' + name,
|
||||
'tag': tagName
|
||||
};
|
||||
|
||||
var errorHandler = ApiService.errorDisplay('Cannot delete tag', function() {
|
||||
$('#confirmdeleteTagModal').modal('hide');
|
||||
$scope.deletingTag = false;
|
||||
});
|
||||
|
||||
$scope.deletingTag = true;
|
||||
|
||||
ApiService.deleteFullTag(null, params).then(function() {
|
||||
loadViewInfo();
|
||||
}, ApiService.errorDisplay('Cannot delete tag'));
|
||||
$('#confirmdeleteTagModal').modal('hide');
|
||||
$scope.deletingTag = false;
|
||||
}, errorHandler);
|
||||
};
|
||||
|
||||
$scope.getImagesForTagBySize = function(tag) {
|
||||
|
@ -1641,13 +1649,19 @@ function UserAdminCtrl($scope, $timeout, $location, ApiService, PlanService, Use
|
|||
UserService.updateUserIn($scope, function(user) {
|
||||
$scope.cuser = jQuery.extend({}, user);
|
||||
|
||||
if (Features.GITHUB_LOGIN && $scope.cuser.logins) {
|
||||
if ($scope.cuser.logins) {
|
||||
for (var i = 0; i < $scope.cuser.logins.length; i++) {
|
||||
if ($scope.cuser.logins[i].service == 'github') {
|
||||
var githubId = $scope.cuser.logins[i].service_identifier;
|
||||
$http.get('https://api.github.com/user/' + githubId).success(function(resp) {
|
||||
$scope.githubLogin = resp.login;
|
||||
});
|
||||
var login = $scope.cuser.logins[i];
|
||||
login.metadata = login.metadata || {};
|
||||
|
||||
if (login.service == 'github') {
|
||||
$scope.hasGithubLogin = true;
|
||||
$scope.githubLogin = login.metadata['service_username'];
|
||||
}
|
||||
|
||||
if (login.service == 'google') {
|
||||
$scope.hasGoogleLogin = true;
|
||||
$scope.googleLogin = login.metadata['service_username'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1665,7 +1679,6 @@ function UserAdminCtrl($scope, $timeout, $location, ApiService, PlanService, Use
|
|||
$scope.convertStep = 0;
|
||||
$scope.org = {};
|
||||
$scope.githubRedirectUri = KeyService.githubRedirectUri;
|
||||
$scope.githubClientId = KeyService.githubLoginClientId;
|
||||
$scope.authorizedApps = null;
|
||||
|
||||
$scope.logsShown = 0;
|
||||
|
@ -1763,7 +1776,7 @@ function UserAdminCtrl($scope, $timeout, $location, ApiService, PlanService, Use
|
|||
$scope.changeEmailForm.$setPristine();
|
||||
}, function(result) {
|
||||
$scope.updatingUser = false;
|
||||
UIService.showFormError('#changeEmailForm', result);
|
||||
UIService.showFormError('#changeEmailForm', result);
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -1773,7 +1786,8 @@ function UserAdminCtrl($scope, $timeout, $location, ApiService, PlanService, Use
|
|||
$scope.updatingUser = true;
|
||||
$scope.changePasswordSuccess = false;
|
||||
|
||||
ApiService.changeUserDetails($scope.cuser).then(function() {
|
||||
ApiService.changeUserDetails($scope.cuser).then(function(resp) {
|
||||
|
||||
$scope.updatingUser = false;
|
||||
$scope.changePasswordSuccess = true;
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
<a ng-href="/repository/{{ repository.namespace }}/{{ repository.name }}">{{repository.namespace}}/{{repository.name}}</a>
|
||||
<div class="markdown-view description" content="repository.description" first-line-only="true"></div>
|
||||
</div>
|
||||
<a href="/repository/?namespace={{ user.username }}">See All Repositories</a>
|
||||
<a href="/repository/?namespace={{ namespace }}">See All Repositories</a>
|
||||
</div>
|
||||
|
||||
<!-- No Repos -->
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
<a ng-href="/repository/{{ repository.namespace }}/{{ repository.name }}">{{repository.namespace}}/{{repository.name}}</a>
|
||||
<div class="markdown-view description" content="repository.description" first-line-only="true"></div>
|
||||
</div>
|
||||
<a href="/repository/?namespace={{ user.username }}">See All Repositories</a>
|
||||
<a href="/repository/?namespace={{ namespace }}">See All Repositories</a>
|
||||
</div>
|
||||
|
||||
<!-- No Repos -->
|
||||
|
|
|
@ -77,7 +77,7 @@
|
|||
<span class="container-content build-log-phase" phase="container"></span>
|
||||
</div>
|
||||
<div ng-switch-when="error">
|
||||
<span class="container-content build-log-error" error="container"></span>
|
||||
<span class="container-content build-log-error" error="container" entries="logEntries"></span>
|
||||
</div>
|
||||
<div ng-switch-when="command">
|
||||
<span class="container-content build-log-command" command="container"></span>
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
<li quay-classes="{'!Features.BILLING': 'active'}"><a href="javascript:void(0)" data-toggle="tab" data-target="#email">Account E-mail</a></li>
|
||||
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#robots">Robot Accounts</a></li>
|
||||
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#password">Change Password</a></li>
|
||||
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#github" quay-require="['GITHUB_LOGIN']">GitHub Login</a></li>
|
||||
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#external" quay-show="Features.GITHUB_LOGIN || Features.GOOGLE_LOGIN">External Logins</a></li>
|
||||
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#authorized" ng-click="loadAuthedApps()">Authorized Applications</a></li>
|
||||
<li quay-show="Features.USER_LOG_ACCESS || hasPaidBusinessPlan">
|
||||
<a href="javascript:void(0)" data-toggle="tab" data-target="#logs" ng-click="loadLogs()">Usage Logs</a>
|
||||
|
@ -138,13 +138,14 @@
|
|||
|
||||
<!-- Change password tab -->
|
||||
<div id="password" class="tab-pane">
|
||||
<div class="loading" ng-show="updatingUser">
|
||||
<div class="quay-spinner 3x"></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="panel">
|
||||
<div class="panel-title">Change Password</div>
|
||||
|
||||
<div class="loading" ng-show="updatingUser">
|
||||
<div class="quay-spinner 3x"></div>
|
||||
</div>
|
||||
|
||||
<span class="help-block" ng-show="changePasswordSuccess">Password changed successfully</span>
|
||||
|
||||
<div ng-show="!updatingUser" class="panel-body">
|
||||
|
@ -162,25 +163,52 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Github tab -->
|
||||
<div id="github" class="tab-pane" quay-require="['GITHUB_LOGIN']">
|
||||
<!-- External Login tab -->
|
||||
<div id="external" class="tab-pane" quay-show="Features.GITHUB_LOGIN || Features.GOOGLE_LOGIN">
|
||||
<div class="loading" ng-show="!cuser">
|
||||
<div class="quay-spinner 3x"></div>
|
||||
</div>
|
||||
<div class="row" ng-show="cuser">
|
||||
|
||||
<!-- Github -->
|
||||
<div class="row" quay-show="cuser && Features.GITHUB_LOGIN">
|
||||
<div class="panel">
|
||||
<div class="panel-title">GitHub Login:</div>
|
||||
<div class="panel-body">
|
||||
<div ng-show="githubLogin" class="lead col-md-8">
|
||||
<div ng-show="hasGithubLogin && githubLogin" class="lead col-md-8">
|
||||
<i class="fa fa-github fa-lg" style="margin-right: 6px;" data-title="GitHub" bs-tooltip="tooltip.title"></i>
|
||||
<b><a href="https://github.com/{{githubLogin}}" target="_blank">{{githubLogin}}</a></b>
|
||||
</div>
|
||||
<div ng-show="!githubLogin" class="col-md-8">
|
||||
<a href="https://github.com/login/oauth/authorize?client_id={{ githubClientId }}&scope=user:email{{ github_state_clause }}&redirect_uri={{ githubRedirectUri }}/attach" class="btn btn-primary"><i class="fa fa-github fa-lg"></i> Connect with GitHub</a>
|
||||
<div ng-show="hasGithubLogin && !githubLogin" class="lead col-md-8">
|
||||
<i class="fa fa-github fa-lg" style="margin-right: 6px;" data-title="GitHub" bs-tooltip="tooltip.title"></i>
|
||||
Account attached to Github Account
|
||||
</div>
|
||||
<div ng-show="!hasGithubLogin" class="col-md-4">
|
||||
<span class="external-login-button" provider="github" action="attach"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Google -->
|
||||
<div class="row" quay-show="cuser && Features.GOOGLE_LOGIN">
|
||||
<div class="panel">
|
||||
<div class="panel-title">Google Login:</div>
|
||||
<div class="panel-body">
|
||||
<div ng-show="hasGoogleLogin && googleLogin" class="lead col-md-8">
|
||||
<i class="fa fa-google fa-lg" style="margin-right: 6px;" data-title="Google" bs-tooltip="tooltip.title"></i>
|
||||
<b>{{ googleLogin }}</b>
|
||||
</div>
|
||||
<div ng-show="hasGoogleLogin && !googleLogin" class="lead col-md-8">
|
||||
<i class="fa fa-google fa-lg" style="margin-right: 6px;" data-title="Google" bs-tooltip="tooltip.title"></i>
|
||||
Account attached to Google Account
|
||||
</div>
|
||||
<div ng-show="!hasGoogleLogin" class="col-md-4">
|
||||
<span class="external-login-button" provider="google" action="attach"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Robot accounts tab -->
|
||||
|
|
|
@ -391,7 +391,10 @@
|
|||
</span>?
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="modal-body" ng-show="deletingTag">
|
||||
<div class="quay-spinner"></div>
|
||||
</div>
|
||||
<div class="modal-body" ng-show="!deletingTag">
|
||||
Are you sure you want to delete tag
|
||||
<span class="label tag" ng-class="tagToDelete == currentTag.name ? 'label-success' : 'label-default'">
|
||||
{{ tagToDelete }}
|
||||
|
@ -401,7 +404,7 @@
|
|||
The following images and any other images not referenced by a tag will be deleted:
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="modal-footer" ng-show="!deletingTag">
|
||||
<button type="button" class="btn btn-primary" ng-click="deleteTag(tagToDelete)">Delete Tag</button>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||
</div>
|
||||
|
|
Reference in a new issue