initial import for Open Source 🎉
This commit is contained in:
parent
1898c361f3
commit
9c0dd3b722
2048 changed files with 218743 additions and 0 deletions
17
static/js/pages/about.js
Normal file
17
static/js/pages/about.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
import billOfMaterials from "../../../bill-of-materials.json"
|
||||
|
||||
(function() {
|
||||
/**
|
||||
* About page.
|
||||
*/
|
||||
angular.module('quayPages').config(['pages', function(pages) {
|
||||
pages.create('about', 'about.html', AboutCtrl, {
|
||||
'title': 'About Us',
|
||||
'description': 'About Us'
|
||||
});
|
||||
}]);
|
||||
|
||||
function AboutCtrl($scope){
|
||||
$scope.billOfMaterials = billOfMaterials
|
||||
}
|
||||
}());
|
85
static/js/pages/app-list.js
Normal file
85
static/js/pages/app-list.js
Normal file
|
@ -0,0 +1,85 @@
|
|||
(function() {
|
||||
/**
|
||||
* Application listing page. Shows all applications for all visibile namespaces.
|
||||
*/
|
||||
angular.module('quayPages').config(['pages', function(pages) {
|
||||
pages.create('app-list', 'app-list.html', AppListCtrl, {
|
||||
'newLayout': true,
|
||||
'title': 'Applications',
|
||||
'description': 'View and manage applications'
|
||||
})
|
||||
}]);
|
||||
|
||||
function AppListCtrl($scope, $sanitize, $q, Restangular, UserService, ApiService, Features,
|
||||
StateService) {
|
||||
$scope.namespace = null;
|
||||
$scope.page = 1;
|
||||
$scope.publicPageCount = null;
|
||||
$scope.allRepositories = {};
|
||||
$scope.loading = true;
|
||||
$scope.resources = [];
|
||||
$scope.Features = Features;
|
||||
$scope.inReadOnlyMode = StateService.inReadOnlyMode();
|
||||
|
||||
// When loading the UserService, if the user is logged in, create a list of
|
||||
// relevant namespaces and collect the relevant repositories.
|
||||
UserService.updateUserIn($scope, function(user) {
|
||||
$scope.loading = false;
|
||||
if (!user.anonymous) {
|
||||
// Add our user to our list of namespaces.
|
||||
$scope.namespaces = [{
|
||||
'name': user.username,
|
||||
'avatar': user.avatar
|
||||
}];
|
||||
|
||||
// Add each org to our list of namespaces.
|
||||
user.organizations.map(function(org) {
|
||||
$scope.namespaces.push({
|
||||
'name': org.name,
|
||||
'avatar': org.avatar
|
||||
});
|
||||
});
|
||||
|
||||
// Load the repos.
|
||||
loadRepos();
|
||||
}
|
||||
});
|
||||
|
||||
$scope.isOrganization = function(namespace) {
|
||||
return !!UserService.getOrganization(namespace);
|
||||
};
|
||||
|
||||
// Finds a duplicate repo if it exists. If it doesn't, inserts the repo.
|
||||
var findDuplicateRepo = function(repo) {
|
||||
var found = $scope.allRepositories[repo.namespace + '/' + repo.name];
|
||||
if (found) {
|
||||
return found;
|
||||
} else {
|
||||
$scope.allRepositories[repo.namespace + '/' + repo.name] = repo;
|
||||
return repo;
|
||||
}
|
||||
};
|
||||
|
||||
var loadRepos = function() {
|
||||
if (!$scope.user || $scope.user.anonymous || $scope.namespaces.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.namespaces.map(function(namespace) {
|
||||
var options = {
|
||||
'namespace': namespace.name,
|
||||
'last_modified': true,
|
||||
'popularity': true,
|
||||
'repo_kind': 'application',
|
||||
'public': true,
|
||||
};
|
||||
|
||||
namespace.repositories = ApiService.listReposAsResource().withOptions(options).get(function(resp) {
|
||||
return resp.repositories.map(findDuplicateRepo);
|
||||
});
|
||||
|
||||
$scope.resources.push(namespace.repositories);
|
||||
});
|
||||
};
|
||||
}
|
||||
})();
|
45
static/js/pages/app-view.js
Normal file
45
static/js/pages/app-view.js
Normal file
|
@ -0,0 +1,45 @@
|
|||
(function() {
|
||||
/**
|
||||
* Application view page.
|
||||
*/
|
||||
angular.module('quayPages').config(['pages', function(pages) {
|
||||
pages.create('app-view', 'app-view.html', AppViewCtrl, {
|
||||
'newLayout': true,
|
||||
'title': '{{ namespace }}/{{ name }}',
|
||||
'description': 'Application {{ namespace }}/{{ name }}'
|
||||
});
|
||||
}]);
|
||||
|
||||
function AppViewCtrl($scope, $routeParams, $rootScope, ApiService, UtilService) {
|
||||
$scope.namespace = $routeParams.namespace;
|
||||
$scope.name = $routeParams.name;
|
||||
|
||||
$scope.viewScope = {};
|
||||
$scope.settingsShown = 0;
|
||||
|
||||
$scope.showSettings = function() {
|
||||
$scope.settingsShown++;
|
||||
};
|
||||
|
||||
var loadRepository = function() {
|
||||
var params = {
|
||||
'repository': $scope.namespace + '/' + $scope.name,
|
||||
'repo_kind': 'application',
|
||||
'includeStats': true,
|
||||
'includeTags': false
|
||||
};
|
||||
|
||||
$scope.repositoryResource = ApiService.getRepoAsResource(params).get(function(repo) {
|
||||
if (repo != undefined) {
|
||||
$scope.repository = repo;
|
||||
$scope.viewScope.repository = repo;
|
||||
|
||||
// Update the page description for SEO
|
||||
$rootScope.description = UtilService.getFirstMarkdownLineAsString(repo.description);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
loadRepository();
|
||||
}
|
||||
})();
|
47
static/js/pages/billing.js
Normal file
47
static/js/pages/billing.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
(function() {
|
||||
/**
|
||||
* Billing plans page.
|
||||
*/
|
||||
angular.module('quayPages').config(['pages', function(pages) {
|
||||
pages.create('billing', 'billing.html', BillingCtrl, {
|
||||
'title': 'Billing',
|
||||
'description': 'Billing',
|
||||
'newLayout': true
|
||||
});
|
||||
}]);
|
||||
|
||||
/**
|
||||
* Billing invoices page.
|
||||
*/
|
||||
angular.module('quayPages').config(['pages', function(pages) {
|
||||
pages.create('invoices', 'invoices.html', BillingCtrl, {
|
||||
'title': 'Billing Invoices',
|
||||
'description': 'Billing Invoices',
|
||||
'newLayout': true
|
||||
});
|
||||
}]);
|
||||
|
||||
|
||||
function BillingCtrl($scope, ApiService, $routeParams, UserService) {
|
||||
$scope.orgname = $routeParams['orgname'];
|
||||
$scope.username = $routeParams['username'];
|
||||
|
||||
var loadEntity = function() {
|
||||
if ($scope.orgname) {
|
||||
$scope.entityResource = ApiService.getOrganizationAsResource({'orgname': $scope.orgname}).get(function(org) {
|
||||
$scope.organization = org;
|
||||
});
|
||||
} else {
|
||||
UserService.updateUserIn($scope, function(currentUser) {
|
||||
$scope.entityResource = ApiService.getUserInformationAsResource({'username': $scope.username}).get(function(user) {
|
||||
$scope.invaliduser = !currentUser || currentUser.username != $scope.username;
|
||||
$scope.viewuser = user;
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Load the user or organization.
|
||||
loadEntity();
|
||||
}
|
||||
}());
|
78
static/js/pages/build-view.js
Normal file
78
static/js/pages/build-view.js
Normal file
|
@ -0,0 +1,78 @@
|
|||
(function() {
|
||||
/**
|
||||
* Build view page. Displays the view of a particular build for a repository.
|
||||
*/
|
||||
angular.module('quayPages').config(['pages', function(pages) {
|
||||
pages.create('build-view', 'build-view.html', BuildViewCtrl, {
|
||||
newLayout: true,
|
||||
title: 'Build {{ build.display_name }}',
|
||||
description: 'Logs and status for build {{ build.display_name }}'
|
||||
});
|
||||
}]);
|
||||
|
||||
function BuildViewCtrl($scope, ApiService, $routeParams, AngularPollChannel, CookieService,
|
||||
$location, StateService) {
|
||||
$scope.inReadOnlyMode = StateService.inReadOnlyMode();
|
||||
$scope.namespace = $routeParams.namespace;
|
||||
$scope.name = $routeParams.name;
|
||||
$scope.build_uuid = $routeParams.buildid;
|
||||
|
||||
if (!CookieService.get('quay.showBuildLogTimestamps')) {
|
||||
$scope.showLogTimestamps = true;
|
||||
} else {
|
||||
$scope.showLogTimestamps = CookieService.get('quay.showBuildLogTimestamps') == 'true';
|
||||
}
|
||||
|
||||
var loadBuild = function() {
|
||||
var params = {
|
||||
'repository': $scope.namespace + '/' + $scope.name,
|
||||
'build_uuid': $scope.build_uuid
|
||||
};
|
||||
|
||||
$scope.buildResource = ApiService.getRepoBuildAsResource(params).get(function(build) {
|
||||
$scope.build = build;
|
||||
$scope.originalBuild = build;
|
||||
});
|
||||
};
|
||||
|
||||
var loadRepository = function() {
|
||||
var params = {
|
||||
'repository': $scope.namespace + '/' + $scope.name,
|
||||
'includeTags': false
|
||||
};
|
||||
|
||||
$scope.repoResource = ApiService.getRepoAsResource(params).get(function(repo) {
|
||||
$scope.repo = repo;
|
||||
}, ApiService.errorDisplay('Cannot load repository'));
|
||||
};
|
||||
|
||||
// Page startup:
|
||||
loadRepository();
|
||||
loadBuild();
|
||||
|
||||
$scope.askCancelBuild = function(build) {
|
||||
bootbox.confirm('Are you sure you want to cancel this build?', function(r) {
|
||||
if (r) {
|
||||
var params = {
|
||||
'repository': $scope.namespace + '/' + $scope.name,
|
||||
'build_uuid': build.id
|
||||
};
|
||||
|
||||
ApiService.cancelRepoBuild(null, params).then(function () {
|
||||
$location.path('/repository/' + $scope.namespace + '/' + $scope.name);
|
||||
|
||||
}, ApiService.errorDisplay('Cannot cancel build'));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.toggleTimestamps = function() {
|
||||
$scope.showLogTimestamps = !$scope.showLogTimestamps;
|
||||
CookieService.putPermanent('quay.showBuildLogTimestamps', $scope.showLogTimestamps);
|
||||
};
|
||||
|
||||
$scope.setUpdatedBuild = function(build) {
|
||||
$scope.build = build;
|
||||
};
|
||||
}
|
||||
})();
|
40
static/js/pages/confirm-invite.js
Normal file
40
static/js/pages/confirm-invite.js
Normal file
|
@ -0,0 +1,40 @@
|
|||
(function() {
|
||||
/**
|
||||
* Page for confirming an invite to a team.
|
||||
*/
|
||||
angular.module('quayPages').config(['pages', function(pages) {
|
||||
pages.create('confirm-invite', 'confirm-invite.html', ConfirmInviteCtrl, {
|
||||
'title': 'Confirm Invitation'
|
||||
});
|
||||
}]);
|
||||
|
||||
function ConfirmInviteCtrl($scope, $location, UserService, ApiService, NotificationService) {
|
||||
// Monitor any user changes and place the current user into the scope.
|
||||
$scope.loading = false;
|
||||
$scope.inviteCode = $location.search()['code'] || '';
|
||||
|
||||
UserService.updateUserIn($scope, function(user) {
|
||||
if (!user.anonymous && !$scope.loading) {
|
||||
// Make sure to not redirect now that we have logged in. We'll conduct the redirect
|
||||
// manually.
|
||||
$scope.redirectUrl = null;
|
||||
$scope.loading = true;
|
||||
|
||||
var params = {
|
||||
'code': $location.search()['code']
|
||||
};
|
||||
|
||||
ApiService.acceptOrganizationTeamInvite(null, params).then(function(resp) {
|
||||
NotificationService.update();
|
||||
UserService.load();
|
||||
$location.path('/organization/' + resp.org + '/teams/' + resp.team);
|
||||
}, function(resp) {
|
||||
$scope.loading = false;
|
||||
$scope.invalid = ApiService.getErrorMessage(resp, 'Invalid confirmation code');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$scope.redirectUrl = window.location.href;
|
||||
}
|
||||
})();
|
55
static/js/pages/contact.js
Normal file
55
static/js/pages/contact.js
Normal file
|
@ -0,0 +1,55 @@
|
|||
(function() {
|
||||
/**
|
||||
* Contact details page. The contacts are configurable.
|
||||
*/
|
||||
angular.module('quayPages').config(['pages', function(pages) {
|
||||
pages.create('contact', 'contact.html', ContactCtrl, {
|
||||
'title': 'Contact Us'
|
||||
});
|
||||
}]);
|
||||
|
||||
function ContactCtrl($scope, Config) {
|
||||
$scope.Config = Config;
|
||||
$scope.colsize = Math.floor(12 / Config.CONTACT_INFO.length);
|
||||
|
||||
$scope.getKind = function(contactInfo) {
|
||||
var colon = contactInfo.indexOf(':');
|
||||
var scheme = contactInfo.substr(0, colon);
|
||||
if (scheme == 'https' || scheme == 'http') {
|
||||
if (contactInfo.indexOf('//twitter.com/') > 0) {
|
||||
return 'twitter';
|
||||
}
|
||||
|
||||
return 'url';
|
||||
}
|
||||
|
||||
return scheme;
|
||||
};
|
||||
|
||||
$scope.getTitle = function(contactInfo) {
|
||||
switch ($scope.getKind(contactInfo)) {
|
||||
case 'url':
|
||||
return contactInfo;
|
||||
|
||||
case 'twitter':
|
||||
var parts = contactInfo.split('/');
|
||||
return '@' + parts[parts.length - 1];
|
||||
|
||||
case 'tel':
|
||||
return contactInfo.substr('tel:'.length);
|
||||
|
||||
case 'irc':
|
||||
// irc://chat.freenode.net:6665/quayio
|
||||
var parts = contactInfo.substr('irc://'.length).split('/');
|
||||
var server = parts[0];
|
||||
if (server.indexOf('freenode') > 0) {
|
||||
server = 'Freenode';
|
||||
}
|
||||
return server + ': #' + parts[parts.length - 1];
|
||||
|
||||
case 'mailto':
|
||||
return contactInfo.substr('mailto:'.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
34
static/js/pages/create-repository-notification.js
Normal file
34
static/js/pages/create-repository-notification.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
(function() {
|
||||
/**
|
||||
* Create repository notification page.
|
||||
*/
|
||||
angular.module('quayPages').config(['pages', function(pages) {
|
||||
pages.create('create-repository-notification', 'create-repository-notification.html', CreateRepoNotificationCtrl, {
|
||||
'newLayout': true,
|
||||
'title': 'Create Repo Notification: {{ namespace }}/{{ name }}',
|
||||
'description': 'Create repository notification for repository {{ namespace }}/{{ name }}'
|
||||
})
|
||||
}]);
|
||||
|
||||
function CreateRepoNotificationCtrl($scope, $routeParams, $location, ApiService) {
|
||||
$scope.namespace = $routeParams.namespace;
|
||||
$scope.name = $routeParams.name;
|
||||
|
||||
var loadRepository = function() {
|
||||
var params = {
|
||||
'repository': $scope.namespace + '/' + $scope.name,
|
||||
'includeTags': false
|
||||
};
|
||||
|
||||
$scope.repositoryResource = ApiService.getRepoAsResource(params).get(function(repo) {
|
||||
$scope.repository = repo;
|
||||
});
|
||||
};
|
||||
|
||||
loadRepository();
|
||||
|
||||
$scope.notificationCreated = function() {
|
||||
$location.url('repository/' + $scope.namespace + '/' + $scope.name + '?tab=settings');
|
||||
};
|
||||
}
|
||||
})();
|
17
static/js/pages/error-view.js
Normal file
17
static/js/pages/error-view.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
(function() {
|
||||
/**
|
||||
* Error view page.
|
||||
*/
|
||||
angular.module('quayPages').config(['pages', function(pages) {
|
||||
pages.create('error-view', 'error-view.html', ErrorViewCtrl, {
|
||||
'title': '{{info.error_message || "Error"}}',
|
||||
'description': 'Error',
|
||||
'newLayout': false
|
||||
});
|
||||
}]);
|
||||
|
||||
function ErrorViewCtrl($scope, ApiService, $routeParams, $rootScope, UserService) {
|
||||
$scope.info = window.__error_info;
|
||||
$scope.code = window.__error_code || 404;
|
||||
}
|
||||
}());
|
86
static/js/pages/incomplete-setup.js
Normal file
86
static/js/pages/incomplete-setup.js
Normal file
|
@ -0,0 +1,86 @@
|
|||
(function() {
|
||||
/**
|
||||
* The Incomplete Setup page provides information to the user about what's wrong with the current configuration
|
||||
*/
|
||||
angular.module('quayPages').config(['pages', function(pages) {
|
||||
pages.create('incomplete-setup', 'incomplete-setup.html', IncompleteSetupCtrl,
|
||||
{
|
||||
'newLayout': true,
|
||||
'title': 'Red Hat Quay Setup Incomplete'
|
||||
})
|
||||
}]);
|
||||
|
||||
function IncompleteSetupCtrl($scope, $location, $timeout, ApiService, Features, UserService, ContainerService, CoreDialog, Config) {
|
||||
if (Config['SETUP_COMPLETE']) {
|
||||
$location.path('/');
|
||||
return;
|
||||
}
|
||||
if (!Features.SUPER_USERS) {
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.States = {
|
||||
// Loading the state of the product.
|
||||
'LOADING': 'loading',
|
||||
|
||||
// The configuration directory is missing.
|
||||
'MISSING_CONFIG_DIR': 'missing-config-dir',
|
||||
|
||||
// The config.yaml exists but it is invalid.
|
||||
'INVALID_CONFIG': 'config-invalid',
|
||||
};
|
||||
|
||||
$scope.currentStep = $scope.States.LOADING;
|
||||
|
||||
$scope.$watch('currentStep', function(currentStep) {
|
||||
switch (currentStep) {
|
||||
case $scope.States.MISSING_CONFIG_DIR:
|
||||
$scope.showMissingConfigDialog();
|
||||
break;
|
||||
|
||||
case $scope.States.INVALID_CONFIG:
|
||||
$scope.showInvalidConfigDialog();
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
$scope.showInvalidConfigDialog = function() {
|
||||
var message = "The <code>config.yaml</code> file found in <code>conf/stack</code> could not be parsed."
|
||||
var title = "Invalid configuration file";
|
||||
CoreDialog.fatal(title, message);
|
||||
};
|
||||
|
||||
|
||||
$scope.showMissingConfigDialog = function() {
|
||||
var title = "Missing configuration volume";
|
||||
var message = "It looks like Quay was not mounted with a configuration volume. The volume should be " +
|
||||
"mounted into the container at <code>/conf/stack</code>. " +
|
||||
"<br>If you have a tarball, please ensure you untar it into a directory and re-run this container with: " +
|
||||
"<br><br><pre>docker run -v /path/to/config:/conf/stack</pre>" +
|
||||
"<br>If you haven't configured your Quay instance, please run the container with: " +
|
||||
"<br><br><pre>docker run <name-of-image> config </pre>" +
|
||||
"For more information, " +
|
||||
"<a href='https://coreos.com/docs/enterprise-registry/initial-setup/'>" +
|
||||
"Read the Setup Guide</a>";
|
||||
|
||||
if (window.__kubernetes_namespace) {
|
||||
title = "Configuration Secret Missing";
|
||||
message = `It looks like the Red Hat Quay secret is not present in the namespace <code>${window.__kubernetes_namespace}.</code>` +
|
||||
"<br>Please double-check that the secret exists, or " +
|
||||
"<a href='https://coreos.com/docs/enterprise-registry/initial-setup/'>" +
|
||||
"refer to the Setup Guide</a>";
|
||||
}
|
||||
|
||||
CoreDialog.fatal(title, message);
|
||||
};
|
||||
|
||||
$scope.checkStatus = function() {
|
||||
ContainerService.checkStatus(function(resp) {
|
||||
$scope.currentStep = resp['status'];
|
||||
}, $scope.currentConfig);
|
||||
};
|
||||
|
||||
// Load the initial status.
|
||||
$scope.checkStatus();
|
||||
};
|
||||
})();
|
90
static/js/pages/landing.js
Normal file
90
static/js/pages/landing.js
Normal file
|
@ -0,0 +1,90 @@
|
|||
(function() {
|
||||
/**
|
||||
* Landing page.
|
||||
*/
|
||||
angular.module('quayPages').config(['pages', function(pages) {
|
||||
pages.create('landing', 'landing.html', LandingCtrl, {
|
||||
'pageClass': function(Features) {
|
||||
return Features.BILLING ? 'landing-page' : '';
|
||||
}
|
||||
});
|
||||
}]);
|
||||
|
||||
function LandingCtrl($scope, $location, UserService, ApiService, Features, Config) {
|
||||
$scope.currentScreenshot = 'repo-view';
|
||||
$scope.userRegistered = false;
|
||||
|
||||
if (!Config['SETUP_COMPLETE'] && !Features.BILLING) {
|
||||
$location.path('/incomplete-setup');
|
||||
return;
|
||||
}
|
||||
|
||||
UserService.updateUserIn($scope, function(user) {
|
||||
if (!user.anonymous) {
|
||||
if (user.prompts && user.prompts.length) {
|
||||
$location.path('/updateuser/');
|
||||
} else {
|
||||
$location.path('/repository/');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$scope.handleUserRegistered = function() {
|
||||
$scope.userRegistered = true;
|
||||
};
|
||||
|
||||
$scope.changeScreenshot = function(screenshot) {
|
||||
$scope.currentScreenshot = screenshot;
|
||||
};
|
||||
|
||||
$scope.chromify = function() {
|
||||
browserchrome.update();
|
||||
|
||||
var jcarousel = $('.jcarousel');
|
||||
|
||||
jcarousel
|
||||
.on('jcarousel:reload jcarousel:create', function () {
|
||||
var width = jcarousel.innerWidth();
|
||||
jcarousel.jcarousel('items').css('width', width + 'px');
|
||||
})
|
||||
.jcarousel({
|
||||
wrap: 'circular'
|
||||
});
|
||||
|
||||
$('.jcarousel-control-prev')
|
||||
.on('jcarouselcontrol:active', function() {
|
||||
$(this).removeClass('inactive');
|
||||
})
|
||||
.on('jcarouselcontrol:inactive', function() {
|
||||
$(this).addClass('inactive');
|
||||
})
|
||||
.jcarouselControl({
|
||||
target: '-=1'
|
||||
});
|
||||
|
||||
$('.jcarousel-control-next')
|
||||
.on('jcarouselcontrol:active', function() {
|
||||
$(this).removeClass('inactive');
|
||||
})
|
||||
.on('jcarouselcontrol:inactive', function() {
|
||||
$(this).addClass('inactive');
|
||||
})
|
||||
.jcarouselControl({
|
||||
target: '+=1'
|
||||
});
|
||||
|
||||
$('.jcarousel-pagination')
|
||||
.on('jcarouselpagination:active', 'a', function() {
|
||||
$(this).addClass('active');
|
||||
})
|
||||
.on('jcarouselpagination:inactive', 'a', function() {
|
||||
$(this).removeClass('active');
|
||||
})
|
||||
.jcarouselPagination({
|
||||
'item': function(page, carouselItems) {
|
||||
return '<a href="javascript:void(0)" class="jcarousel-page"></a>';
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
})();
|
124
static/js/pages/manage-application.js
Normal file
124
static/js/pages/manage-application.js
Normal file
|
@ -0,0 +1,124 @@
|
|||
(function() {
|
||||
/**
|
||||
* Page for managing an organization-defined OAuth application.
|
||||
*/
|
||||
angular.module('quayPages').config(['pages', function(pages) {
|
||||
pages.create('manage-application', 'manage-application.html', ManageApplicationCtrl, {
|
||||
'newLayout': true,
|
||||
'title': 'Manage Application {{ application.name }}',
|
||||
'description': 'Manage an OAuth application'
|
||||
});
|
||||
}]);
|
||||
|
||||
function ManageApplicationCtrl($scope, $routeParams, $rootScope, $location, $timeout, OAuthService, ApiService, UserService, Config) {
|
||||
var orgname = $routeParams.orgname;
|
||||
var clientId = $routeParams.clientid;
|
||||
|
||||
$scope.Config = Config;
|
||||
$scope.OAuthService = OAuthService;
|
||||
$scope.updating = false;
|
||||
|
||||
$scope.genScopes = {};
|
||||
|
||||
UserService.updateUserIn($scope);
|
||||
|
||||
$scope.getScopes = function(scopes) {
|
||||
var checked = [];
|
||||
for (var scopeName in scopes) {
|
||||
if (scopes.hasOwnProperty(scopeName) && scopes[scopeName]) {
|
||||
checked.push(scopeName);
|
||||
}
|
||||
}
|
||||
return checked;
|
||||
};
|
||||
|
||||
$scope.askResetClientSecret = function() {
|
||||
$('#resetSecretModal').modal({});
|
||||
};
|
||||
|
||||
$scope.askDelete = function() {
|
||||
$('#deleteAppModal').modal({});
|
||||
};
|
||||
|
||||
$scope.deleteApplication = function() {
|
||||
var params = {
|
||||
'orgname': orgname,
|
||||
'client_id': clientId
|
||||
};
|
||||
|
||||
$('#deleteAppModal').modal('hide');
|
||||
|
||||
ApiService.deleteOrganizationApplication(null, params).then(function(resp) {
|
||||
$timeout(function() {
|
||||
$location.path('/organization/' + orgname + '/admin');
|
||||
}, 500);
|
||||
}, ApiService.errorDisplay('Could not delete application'));
|
||||
};
|
||||
|
||||
$scope.updateApplication = function() {
|
||||
$scope.updating = true;
|
||||
var params = {
|
||||
'orgname': orgname,
|
||||
'client_id': clientId
|
||||
};
|
||||
|
||||
if (!$scope.application['description']) {
|
||||
delete $scope.application['description'];
|
||||
}
|
||||
|
||||
if (!$scope.application['avatar_email']) {
|
||||
delete $scope.application['avatar_email'];
|
||||
}
|
||||
|
||||
var errorHandler = ApiService.errorDisplay('Could not update application', function(resp) {
|
||||
$scope.updating = false;
|
||||
});
|
||||
|
||||
ApiService.updateOrganizationApplication($scope.application, params).then(function(resp) {
|
||||
$scope.application = resp;
|
||||
}, errorHandler);
|
||||
};
|
||||
|
||||
$scope.resetClientSecret = function() {
|
||||
var params = {
|
||||
'orgname': orgname,
|
||||
'client_id': clientId
|
||||
};
|
||||
|
||||
$('#resetSecretModal').modal('hide');
|
||||
|
||||
ApiService.resetOrganizationApplicationClientSecret(null, params).then(function(resp) {
|
||||
$scope.application = resp;
|
||||
}, ApiService.errorDisplay('Could not reset client secret'));
|
||||
};
|
||||
|
||||
var loadOrganization = function() {
|
||||
$scope.orgResource = ApiService.getOrganizationAsResource({'orgname': orgname}).get(function(org) {
|
||||
$scope.organization = org;
|
||||
return org;
|
||||
});
|
||||
};
|
||||
|
||||
var loadApplicationInfo = function() {
|
||||
var params = {
|
||||
'orgname': orgname,
|
||||
'client_id': clientId
|
||||
};
|
||||
|
||||
$scope.appResource = ApiService.getOrganizationApplicationAsResource(params).get(function(resp) {
|
||||
$scope.application = resp;
|
||||
|
||||
$rootScope.title = 'Manage Application ' + $scope.application.name + ' (' + $scope.orgname + ')';
|
||||
$rootScope.description = 'Manage the details of application ' + $scope.application.name +
|
||||
' under organization ' + $scope.orgname;
|
||||
|
||||
return resp;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// Load the organization and application info.
|
||||
loadOrganization();
|
||||
loadApplicationInfo();
|
||||
}
|
||||
})();
|
86
static/js/pages/manifest-view.js
Normal file
86
static/js/pages/manifest-view.js
Normal file
|
@ -0,0 +1,86 @@
|
|||
(function() {
|
||||
/**
|
||||
* Page to view the details of a single manifest.
|
||||
*/
|
||||
angular.module('quayPages').config(['pages', function(pages) {
|
||||
pages.create('manifest-view', 'manifest-view.html', ManifestViewCtrl, {
|
||||
'newLayout': true,
|
||||
'title': '{{ manifest_digest }}',
|
||||
'description': 'Manifest {{ manifest_digest }}'
|
||||
})
|
||||
}]);
|
||||
|
||||
function ManifestViewCtrl($scope, $routeParams, $rootScope, $timeout, ApiService, ImageMetadataService, Features, CookieService) {
|
||||
var namespace = $routeParams.namespace;
|
||||
var name = $routeParams.name;
|
||||
var manifest_digest = $routeParams.manifest_digest;
|
||||
|
||||
$scope.manifestSecurityCounter = 0;
|
||||
$scope.manifestPackageCounter = 0;
|
||||
|
||||
$scope.options = {
|
||||
'vulnFilter': ''
|
||||
};
|
||||
|
||||
var loadManifest = function() {
|
||||
var params = {
|
||||
'repository': namespace + '/' + name,
|
||||
'manifestref': manifest_digest
|
||||
};
|
||||
|
||||
$scope.manifestResource = ApiService.getRepoManifestAsResource(params).get(function(manifest) {
|
||||
$scope.manifest = manifest;
|
||||
$scope.reversedLayers = manifest.layers ? manifest.layers.reverse() : null;
|
||||
});
|
||||
};
|
||||
|
||||
var loadRepository = function() {
|
||||
var params = {
|
||||
'repository': namespace + '/' + name,
|
||||
'includeTags': false
|
||||
};
|
||||
|
||||
$scope.repositoryResource = ApiService.getRepoAsResource(params).get(function(repo) {
|
||||
$scope.repository = repo;
|
||||
});
|
||||
};
|
||||
|
||||
loadManifest();
|
||||
loadRepository();
|
||||
|
||||
$scope.loadManifestSecurity = function() {
|
||||
if (!Features.SECURITY_SCANNER) { return; }
|
||||
$scope.manifestSecurityCounter++;
|
||||
};
|
||||
|
||||
$scope.loadManifestPackages = function() {
|
||||
if (!Features.SECURITY_SCANNER) { return; }
|
||||
$scope.manifestPackageCounter++;
|
||||
};
|
||||
|
||||
$scope.manifestsOf = function(manifest) {
|
||||
if (!manifest || !manifest.is_manifest_list) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!manifest._mapped_manifests) {
|
||||
// Calculate once and cache to avoid angular digest cycles.
|
||||
var parsed_manifest = JSON.parse(manifest.manifest_data);
|
||||
|
||||
manifest._mapped_manifests = parsed_manifest.manifests.map(function(manifest) {
|
||||
return {
|
||||
'repository': $scope.repository,
|
||||
'raw': manifest,
|
||||
'os': manifest.platform.os,
|
||||
'architecture': manifest.platform.architecture,
|
||||
'size': manifest.size,
|
||||
'digest': manifest.digest,
|
||||
'description': `${manifest.platform.os} on ${manifest.platform.architecture}`,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return manifest._mapped_manifests;
|
||||
};
|
||||
}
|
||||
})();
|
104
static/js/pages/new-organization.js
Normal file
104
static/js/pages/new-organization.js
Normal file
|
@ -0,0 +1,104 @@
|
|||
(function() {
|
||||
/**
|
||||
* Page for creating a new organization.
|
||||
*/
|
||||
angular.module('quayPages').config(['pages', function(pages) {
|
||||
pages.create('new-organization', 'new-organization.html', NewOrgCtrl, {
|
||||
'newLayout': true,
|
||||
'title': 'New Organization',
|
||||
'description': 'Create a new organization to manage teams and permissions'
|
||||
});
|
||||
}]);
|
||||
|
||||
function NewOrgCtrl($scope, $routeParams, $timeout, $location, UserService, PlanService, ApiService, CookieService, Features, Config) {
|
||||
$scope.Features = Features;
|
||||
$scope.Config = Config
|
||||
$scope.holder = {};
|
||||
$scope.org = {
|
||||
'name': $routeParams['namespace'] || ''
|
||||
};
|
||||
|
||||
UserService.updateUserIn($scope);
|
||||
|
||||
var requested = $routeParams['plan'];
|
||||
|
||||
if (Features.BILLING) {
|
||||
// Load the list of plans.
|
||||
PlanService.getPlans(function(plans) {
|
||||
$scope.plans = plans;
|
||||
$scope.holder.currentPlan = null;
|
||||
if (requested) {
|
||||
PlanService.getPlan(requested, function(plan) {
|
||||
$scope.holder.currentPlan = plan;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$scope.signedIn = function() {
|
||||
if (Features.BILLING) {
|
||||
PlanService.handleNotedPlan();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.signinStarted = function() {
|
||||
if (Features.BILLING) {
|
||||
PlanService.getMinimumPlan(1, true, function(plan) {
|
||||
if (!plan) { return; }
|
||||
PlanService.notePlan(plan.stripeId);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.setPlan = function(plan) {
|
||||
$scope.holder.currentPlan = plan;
|
||||
};
|
||||
|
||||
$scope.createNewOrg = function() {
|
||||
$scope.createError = null;
|
||||
$scope.creating = true;
|
||||
|
||||
var org = $scope.org;
|
||||
var data = {
|
||||
'name': org.name,
|
||||
'email': org.email,
|
||||
'recaptcha_response': org.recaptcha_response
|
||||
};
|
||||
|
||||
ApiService.createOrganization(data).then(function(created) {
|
||||
$scope.created = created;
|
||||
|
||||
// Reset the organizations list.
|
||||
UserService.load();
|
||||
|
||||
// Set the default namesapce to the organization.
|
||||
CookieService.putPermanent('quay.namespace', org.name);
|
||||
|
||||
var showOrg = function() {
|
||||
$scope.creating = false;
|
||||
$location.path('/organization/' + org.name + '/');
|
||||
};
|
||||
|
||||
// If the selected plan is free, simply move to the org page.
|
||||
if (!Features.BILLING || $scope.holder.currentPlan.price == 0) {
|
||||
showOrg();
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, show the subscribe for the plan.
|
||||
$scope.creating = true;
|
||||
var callbacks = {
|
||||
'opened': function() { $scope.creating = true; },
|
||||
'closed': showOrg,
|
||||
'success': showOrg,
|
||||
'failure': showOrg
|
||||
};
|
||||
|
||||
PlanService.changePlan($scope, org.name, $scope.holder.currentPlan.stripeId, callbacks);
|
||||
}, function(resp) {
|
||||
$scope.creating = false;
|
||||
$scope.createError = ApiService.getErrorMessage(resp);
|
||||
});
|
||||
};
|
||||
}
|
||||
})();
|
97
static/js/pages/new-repo.js
Normal file
97
static/js/pages/new-repo.js
Normal file
|
@ -0,0 +1,97 @@
|
|||
(function() {
|
||||
/**
|
||||
* Page to create a new repository.
|
||||
*/
|
||||
angular.module('quayPages').config(['pages', function(pages) {
|
||||
pages.create('new-repo', 'new-repo.html', NewRepoCtrl, {
|
||||
'newLayout': true,
|
||||
'title': 'New Repository',
|
||||
'description': 'Create a new Docker repository'
|
||||
})
|
||||
}]);
|
||||
|
||||
function NewRepoCtrl($scope, $location, $http, $timeout, $routeParams, UserService, ApiService, PlanService, TriggerService, Features) {
|
||||
UserService.updateUserIn($scope);
|
||||
|
||||
$scope.Features = Features;
|
||||
$scope.TriggerService = TriggerService;
|
||||
|
||||
$scope.repo = {
|
||||
'is_public': 0,
|
||||
'description': '',
|
||||
'initialize': '',
|
||||
'name': $routeParams['name'],
|
||||
'repo_kind': 'image'
|
||||
};
|
||||
|
||||
$scope.changeNamespace = function(namespace) {
|
||||
$scope.repo.namespace = namespace;
|
||||
};
|
||||
|
||||
$scope.$watch('repo.name', function() {
|
||||
$scope.createError = null;
|
||||
});
|
||||
|
||||
$scope.startBuild = function() {
|
||||
$scope.buildStarting = true;
|
||||
$scope.startBuildCallback(function(status, messageOrBuild) {
|
||||
if (status) {
|
||||
$location.url('/repository/' + $scope.created.namespace + '/' + $scope.created.name +
|
||||
'?tab=builds');
|
||||
} else {
|
||||
bootbox.alert(messageOrBuild || 'Could not start build');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.readyForBuild = function(startBuild) {
|
||||
$scope.startBuildCallback = startBuild;
|
||||
};
|
||||
|
||||
$scope.updateDescription = function(content) {
|
||||
$scope.repo.description = content;
|
||||
};
|
||||
|
||||
$scope.createNewRepo = function() {
|
||||
$scope.creating = true;
|
||||
var repo = $scope.repo;
|
||||
var data = {
|
||||
'namespace': repo.namespace,
|
||||
'repository': repo.name,
|
||||
'visibility': repo.is_public == '1' ? 'public' : 'private',
|
||||
'description': repo.description,
|
||||
'repo_kind': repo.repo_kind
|
||||
};
|
||||
|
||||
ApiService.createRepo(data).then(function(created) {
|
||||
$scope.creating = false;
|
||||
$scope.created = created;
|
||||
|
||||
if (repo.repo_kind == 'application') {
|
||||
$location.path('/application/' + created.namespace + '/' + created.name);
|
||||
return;
|
||||
}
|
||||
|
||||
// Start the build if applicable.
|
||||
if ($scope.repo.initialize == 'dockerfile' || $scope.repo.initialize == 'zipfile') {
|
||||
$scope.createdForBuild = created;
|
||||
$scope.startBuild();
|
||||
return;
|
||||
}
|
||||
|
||||
// Conduct the SCM redirect if applicable.
|
||||
var redirectUrl = TriggerService.getRedirectUrl($scope.repo.initialize, repo.namespace, repo.name);
|
||||
if (redirectUrl) {
|
||||
window.location = redirectUrl;
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, redirect to the repo page.
|
||||
$location.path('/repository/' + created.namespace + '/' + created.name);
|
||||
}, function(result) {
|
||||
$scope.creating = false;
|
||||
$scope.createError = ApiService.getErrorMessage(result);
|
||||
});
|
||||
};
|
||||
}
|
||||
})();
|
112
static/js/pages/org-view.js
Normal file
112
static/js/pages/org-view.js
Normal file
|
@ -0,0 +1,112 @@
|
|||
(function() {
|
||||
/**
|
||||
* Page that displays details about an organization, such as its teams.
|
||||
*/
|
||||
angular.module('quayPages').config(['pages', function(pages) {
|
||||
pages.create('org-view', 'org-view.html', OrgViewCtrl, {
|
||||
'newLayout': true,
|
||||
'title': 'Organization {{ organization.name }}',
|
||||
'description': 'Organization {{ organization.name }}'
|
||||
})
|
||||
}]);
|
||||
|
||||
function OrgViewCtrl($scope, $routeParams, $timeout, ApiService, UIService, AvatarService,
|
||||
Config, Features, StateService) {
|
||||
var orgname = $routeParams.orgname;
|
||||
|
||||
$scope.inReadOnlyMode = StateService.inReadOnlyMode();
|
||||
$scope.namespace = orgname;
|
||||
$scope.showLogsCounter = 0;
|
||||
$scope.showApplicationsCounter = 0;
|
||||
$scope.showBillingCounter = 0;
|
||||
$scope.showRobotsCounter = 0;
|
||||
$scope.showTeamsCounter = 0;
|
||||
$scope.changeEmailInfo = null;
|
||||
$scope.context = {};
|
||||
|
||||
$scope.Config = Config;
|
||||
$scope.Features = Features;
|
||||
|
||||
$scope.orgScope = {
|
||||
'changingOrganization': false,
|
||||
'organizationEmail': ''
|
||||
};
|
||||
|
||||
$scope.$watch('orgScope.organizationEmail', function(e) {
|
||||
UIService.hidePopover('#changeEmailForm input');
|
||||
});
|
||||
|
||||
var loadRepositories = function() {
|
||||
var options = {
|
||||
'namespace': orgname,
|
||||
'public': true,
|
||||
'last_modified': true,
|
||||
'popularity': true
|
||||
};
|
||||
|
||||
$scope.organization.repositories = ApiService.listReposAsResource().withOptions(options).get(function(resp) {
|
||||
return resp.repositories;
|
||||
});
|
||||
};
|
||||
|
||||
var loadOrganization = function() {
|
||||
$scope.orgResource = ApiService.getOrganizationAsResource({'orgname': orgname}).get(function(org) {
|
||||
$scope.organization = org;
|
||||
$scope.orgScope.organizationEmail = org.email;
|
||||
$scope.isAdmin = org.is_admin;
|
||||
$scope.isMember = org.is_member;
|
||||
|
||||
// Load the repositories.
|
||||
$timeout(function() {
|
||||
loadRepositories();
|
||||
}, 10);
|
||||
});
|
||||
};
|
||||
|
||||
// Load the organization.
|
||||
loadOrganization();
|
||||
|
||||
$scope.showRobots = function() {
|
||||
$scope.showRobotsCounter++;
|
||||
};
|
||||
|
||||
$scope.showTeams = function() {
|
||||
$scope.showTeamsCounter++;
|
||||
};
|
||||
|
||||
$scope.showBilling = function() {
|
||||
$scope.showBillingCounter = true;
|
||||
};
|
||||
|
||||
$scope.showApplications = function() {
|
||||
$scope.showApplicationsCounter++;
|
||||
};
|
||||
|
||||
$scope.showLogs = function() {
|
||||
$scope.showLogsCounter++;
|
||||
};
|
||||
|
||||
$scope.showChangeEmail = function() {
|
||||
$scope.changeEmailInfo = {
|
||||
'email': $scope.organization.email
|
||||
};
|
||||
};
|
||||
|
||||
$scope.changeEmail = function(info, callback) {
|
||||
var params = {
|
||||
'orgname': orgname
|
||||
};
|
||||
|
||||
var details = {
|
||||
'email': $scope.changeEmailInfo.email
|
||||
};
|
||||
|
||||
var errorDisplay = ApiService.errorDisplay('Could not change email address', callback);
|
||||
|
||||
ApiService.changeOrganizationDetails(details, params).then(function() {
|
||||
$scope.organization.email = $scope.changeEmailInfo.email;
|
||||
callback(true);
|
||||
}, errorDisplay);
|
||||
};
|
||||
}
|
||||
})();
|
16
static/js/pages/organizations.js
Normal file
16
static/js/pages/organizations.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
(function() {
|
||||
/**
|
||||
* DEPRECATED: Page which displays the list of organizations of which the user is a member.
|
||||
*/
|
||||
angular.module('quayPages').config(['pages', function(pages) {
|
||||
pages.create('organizations', 'organizations.html', OrgsCtrl, {
|
||||
'title': 'View Organizations',
|
||||
'description': 'View and manage your organizations'
|
||||
});
|
||||
}]);
|
||||
|
||||
function OrgsCtrl($scope, UserService) {
|
||||
UserService.updateUserIn($scope);
|
||||
browserchrome.update();
|
||||
}
|
||||
})();
|
13
static/js/pages/plans.js
Normal file
13
static/js/pages/plans.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
(function() {
|
||||
/**
|
||||
* The plans/pricing page.
|
||||
*/
|
||||
angular.module('quayPages').config(['pages', function(pages) {
|
||||
pages.create('plans', 'plans.html', PlansCtrl, {
|
||||
'title': 'Plans and Pricing',
|
||||
'newLayout': true
|
||||
});
|
||||
}]);
|
||||
|
||||
function PlansCtrl($scope) {}
|
||||
})();
|
10
static/js/pages/privacy.js
Normal file
10
static/js/pages/privacy.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
(function() {
|
||||
/**
|
||||
* Privacy page.
|
||||
*/
|
||||
angular.module('quayPages').config(['pages', function(pages) {
|
||||
pages.create('privacy', 'privacy.html', null, {
|
||||
'title': 'Privacy Policy'
|
||||
});
|
||||
}]);
|
||||
}());
|
117
static/js/pages/repo-list.js
Normal file
117
static/js/pages/repo-list.js
Normal file
|
@ -0,0 +1,117 @@
|
|||
(function() {
|
||||
/**
|
||||
* Repository listing page. Shows all repositories for all visibile namespaces.
|
||||
*/
|
||||
angular.module('quayPages').config(['pages', function(pages) {
|
||||
pages.create('repo-list', 'repo-list.html', RepoListCtrl, {
|
||||
'newLayout': true,
|
||||
'title': 'Repositories',
|
||||
'description': 'View and manage Docker repositories'
|
||||
})
|
||||
}]);
|
||||
|
||||
|
||||
function RepoListCtrl($scope, $sanitize, $q, Restangular, UserService, ApiService, Features,
|
||||
Config, StateService) {
|
||||
$scope.namespace = null;
|
||||
$scope.page = 1;
|
||||
$scope.publicPageCount = null;
|
||||
$scope.allRepositories = {};
|
||||
$scope.loading = true;
|
||||
$scope.resources = [];
|
||||
$scope.Features = Features;
|
||||
$scope.inReadOnlyMode = StateService.inReadOnlyMode();
|
||||
|
||||
// When loading the UserService, if the user is logged in, create a list of
|
||||
// relevant namespaces and collect the relevant repositories.
|
||||
UserService.updateUserIn($scope, function(user) {
|
||||
$scope.loading = false;
|
||||
if (!user.anonymous) {
|
||||
// Add our user to our list of namespaces.
|
||||
$scope.namespaces = [{
|
||||
'name': user.username,
|
||||
'avatar': user.avatar
|
||||
}];
|
||||
|
||||
// Add each org to our list of namespaces.
|
||||
user.organizations.map(function(org) {
|
||||
$scope.namespaces.push({
|
||||
'name': org.name,
|
||||
'avatar': org.avatar,
|
||||
'public': org.public
|
||||
});
|
||||
});
|
||||
|
||||
// Load the repos.
|
||||
loadStarredRepos();
|
||||
loadRepos();
|
||||
}
|
||||
});
|
||||
|
||||
$scope.isOrganization = function(namespace) {
|
||||
return !!UserService.getOrganization(namespace);
|
||||
};
|
||||
|
||||
$scope.starToggled = function(repo) {
|
||||
if (repo.is_starred) {
|
||||
$scope.starred_repositories.value.push(repo);
|
||||
} else {
|
||||
$scope.starred_repositories.value = $scope.starred_repositories.value.filter(function(repo) {
|
||||
return repo.is_starred;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Finds a duplicate repo if it exists. If it doesn't, inserts the repo.
|
||||
var findDuplicateRepo = function(repo) {
|
||||
var found = $scope.allRepositories[repo.namespace + '/' + repo.name];
|
||||
if (found) {
|
||||
return found;
|
||||
} else {
|
||||
$scope.allRepositories[repo.namespace + '/' + repo.name] = repo;
|
||||
return repo;
|
||||
}
|
||||
};
|
||||
|
||||
var loadStarredRepos = function() {
|
||||
if (!$scope.user || $scope.user.anonymous) {
|
||||
return;
|
||||
}
|
||||
|
||||
var options = {
|
||||
'starred': true,
|
||||
'last_modified': true,
|
||||
'popularity': true
|
||||
};
|
||||
|
||||
$scope.starred_repositories = ApiService.listReposAsResource().withOptions(options).get(function(resp) {
|
||||
return resp.repositories.map(function(repo) {
|
||||
repo = findDuplicateRepo(repo);
|
||||
repo.is_starred = true;
|
||||
return repo;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
var loadRepos = function() {
|
||||
if (!$scope.user || $scope.user.anonymous || $scope.namespaces.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.namespaces.map(function(namespace) {
|
||||
var options = {
|
||||
'namespace': namespace.name,
|
||||
'last_modified': true,
|
||||
'popularity': true,
|
||||
'public': namespace.public
|
||||
};
|
||||
|
||||
namespace.repositories = ApiService.listReposAsResource().withOptions(options).get(function(resp) {
|
||||
return resp.repositories.map(findDuplicateRepo);
|
||||
});
|
||||
|
||||
$scope.resources.push(namespace.repositories);
|
||||
});
|
||||
};
|
||||
}
|
||||
})();
|
201
static/js/pages/repo-view.js
Normal file
201
static/js/pages/repo-view.js
Normal file
|
@ -0,0 +1,201 @@
|
|||
(function() {
|
||||
/**
|
||||
* Repository view page.
|
||||
*/
|
||||
angular.module('quayPages').config(['pages', function(pages) {
|
||||
pages.create('repo-view', 'repo-view.html', RepoViewCtrl, {
|
||||
'newLayout': true,
|
||||
'title': '{{ namespace }}/{{ name }}',
|
||||
'description': 'Repository {{ namespace }}/{{ name }}'
|
||||
});
|
||||
}]);
|
||||
|
||||
function RepoViewCtrl($scope, $routeParams, $rootScope, $timeout, ApiService,
|
||||
UserService, AngularPollChannel, ImageLoaderService, UtilService) {
|
||||
$scope.namespace = $routeParams.namespace;
|
||||
$scope.name = $routeParams.name;
|
||||
|
||||
var imageLoader = ImageLoaderService.getLoader($scope.namespace, $scope.name);
|
||||
|
||||
// Tab-enabled counters.
|
||||
$scope.infoShown = 0;
|
||||
$scope.tagsShown = 0;
|
||||
$scope.logsShown = 0;
|
||||
$scope.buildsShown = 0;
|
||||
$scope.settingsShown = 0;
|
||||
$scope.historyShown = 0;
|
||||
$scope.mirrorShown = 0;
|
||||
|
||||
$scope.viewScope = {
|
||||
'selectedTags': [],
|
||||
'repository': null,
|
||||
'imageLoader': imageLoader,
|
||||
'builds': null,
|
||||
'historyFilter': '',
|
||||
'repositoryTags': null,
|
||||
'tagsLoading': true
|
||||
};
|
||||
|
||||
$scope.repositoryTags = {};
|
||||
|
||||
var buildPollChannel = null;
|
||||
|
||||
// Make sure we track the current user.
|
||||
UserService.updateUserIn($scope);
|
||||
|
||||
// Watch the repository to filter any tags removed.
|
||||
$scope.$watch('viewScope.repositoryTags', function(repository) {
|
||||
if (!repository) { return; }
|
||||
$scope.viewScope.selectedTags = filterTags($scope.viewScope.selectedTags);
|
||||
});
|
||||
|
||||
var filterTags = function(tags) {
|
||||
return (tags || []).filter(function(tag) {
|
||||
return !!$scope.viewScope.repositoryTags[tag];
|
||||
});
|
||||
};
|
||||
|
||||
var loadRepositoryTags = function() {
|
||||
loadPaginatedRepositoryTags(1);
|
||||
$scope.viewScope.repositoryTags = $scope.repositoryTags;
|
||||
};
|
||||
|
||||
var loadPaginatedRepositoryTags = function(page) {
|
||||
var params = {
|
||||
'repository': $scope.namespace + '/' + $scope.name,
|
||||
'limit': 100,
|
||||
'page': page,
|
||||
'onlyActiveTags': true
|
||||
};
|
||||
|
||||
ApiService.listRepoTags(null, params).then(function(resp) {
|
||||
var newTags = resp.tags.reduce(function(result, item, index, array) {
|
||||
var tag_name = item['name'];
|
||||
result[tag_name] = item;
|
||||
return result;
|
||||
}, {});
|
||||
|
||||
$.extend($scope.repositoryTags, newTags);
|
||||
|
||||
if (resp.has_additional) {
|
||||
loadPaginatedRepositoryTags(page + 1);
|
||||
} else {
|
||||
$scope.viewScope.tagsLoading = false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var loadRepository = function() {
|
||||
// Mark the images to be reloaded.
|
||||
$scope.viewScope.images = null;
|
||||
loadRepositoryTags();
|
||||
|
||||
var params = {
|
||||
'repository': $scope.namespace + '/' + $scope.name,
|
||||
'includeStats': true,
|
||||
'includeTags': false
|
||||
};
|
||||
|
||||
$scope.repositoryResource = ApiService.getRepoAsResource(params).get(function(repo) {
|
||||
if (repo != undefined) {
|
||||
$scope.repository = repo;
|
||||
$scope.viewScope.repository = repo;
|
||||
|
||||
// Update the page description for SEO
|
||||
$rootScope.description = UtilService.getFirstMarkdownLineAsString(repo.description);
|
||||
|
||||
// Load the remainder of the data async, so we don't block the initial view from showing
|
||||
$timeout(function() {
|
||||
$scope.setTags($routeParams.tag);
|
||||
|
||||
// Track builds.
|
||||
buildPollChannel = AngularPollChannel.create($scope, loadRepositoryBuilds, 30000 /* 30s */);
|
||||
buildPollChannel.start();
|
||||
}, 10);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var loadRepositoryBuilds = function(callback) {
|
||||
var params = {
|
||||
'repository': $scope.namespace + '/' + $scope.name,
|
||||
'limit': 3
|
||||
};
|
||||
|
||||
var errorHandler = function() {
|
||||
callback(false);
|
||||
};
|
||||
|
||||
$scope.repositoryBuildsResource = ApiService.getRepoBuildsAsResource(params, /* background */true).get(function(resp) {
|
||||
// Note: We could just set the builds here, but that causes a full digest cycle. Therefore,
|
||||
// to be more efficient, we do some work here to determine if anything has changed since
|
||||
// the last build load in the common case.
|
||||
if ($scope.viewScope.builds && resp.builds.length == $scope.viewScope.builds.length) {
|
||||
var hasNewInformation = false;
|
||||
for (var i = 0; i < resp.builds.length; ++i) {
|
||||
var current = $scope.viewScope.builds[i];
|
||||
var updated = resp.builds[i];
|
||||
if (current.phase != updated.phase || current.id != updated.id) {
|
||||
hasNewInformation = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasNewInformation) {
|
||||
callback(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$scope.viewScope.builds = resp.builds;
|
||||
callback(true);
|
||||
}, errorHandler);
|
||||
};
|
||||
|
||||
// Load the repository.
|
||||
loadRepository();
|
||||
|
||||
$scope.setTags = function(tagNames) {
|
||||
if (!tagNames) {
|
||||
$scope.viewScope.selectedTags = [];
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.viewScope.selectedTags = $.unique(tagNames.split(','));
|
||||
};
|
||||
|
||||
$scope.showInfo = function() {
|
||||
$scope.infoShown++;
|
||||
};
|
||||
|
||||
$scope.showBuilds = function() {
|
||||
$scope.buildsShown++;
|
||||
};
|
||||
|
||||
$scope.showHistory = function() {
|
||||
$scope.historyShown++;
|
||||
};
|
||||
|
||||
$scope.showSettings = function() {
|
||||
$scope.settingsShown++;
|
||||
};
|
||||
|
||||
$scope.showMirror = function() {
|
||||
$scope.mirrorShown++;
|
||||
}
|
||||
|
||||
$scope.showLogs = function() {
|
||||
$scope.logsShown++;
|
||||
};
|
||||
|
||||
$scope.showTags = function() {
|
||||
$timeout(function() {
|
||||
$scope.tagsShown = 1;
|
||||
}, 10);
|
||||
};
|
||||
|
||||
$scope.getImages = function(callback) {
|
||||
loadImages(callback);
|
||||
};
|
||||
}
|
||||
})();
|
55
static/js/pages/search.js
Normal file
55
static/js/pages/search.js
Normal file
|
@ -0,0 +1,55 @@
|
|||
(function() {
|
||||
/**
|
||||
* Search page.
|
||||
*/
|
||||
angular.module('quayPages').config(['pages', function(pages) {
|
||||
pages.create('search', 'search.html', SearchCtrl, {
|
||||
'title': 'Search'
|
||||
});
|
||||
}]);
|
||||
|
||||
function SearchCtrl($scope, ApiService, $routeParams, $location, Config) {
|
||||
var refreshResults = function() {
|
||||
$scope.currentPage = ($routeParams['page'] || '1') * 1;
|
||||
|
||||
var params = {
|
||||
'query': $routeParams['q'],
|
||||
'page': $scope.currentPage
|
||||
};
|
||||
|
||||
var MAX_PAGE_RESULTS = Config['SEARCH_MAX_RESULT_PAGE_COUNT'];
|
||||
var page = $routeParams['page'] || 1;
|
||||
|
||||
$scope.maxPopularity = 0;
|
||||
$scope.resultsResource = ApiService.conductRepoSearchAsResource(params).get(function(resp) {
|
||||
$scope.results = resp['results'];
|
||||
// Only show "Next Page" if we have more results, and we aren't on the max page
|
||||
$scope.showNextButton = page < MAX_PAGE_RESULTS && resp['has_additional'];
|
||||
// Show some help text if we're on the last page, making them specify the search more
|
||||
$scope.showMaxResultsHelpText = page >= MAX_PAGE_RESULTS;
|
||||
$scope.startIndex = resp['start_index'];
|
||||
resp['results'].forEach(function(result) {
|
||||
$scope.maxPopularity = Math.max($scope.maxPopularity, result['popularity']);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.previousPage = function() {
|
||||
$location.search('page', (($routeParams['page'] || 1) * 1) - 1);
|
||||
};
|
||||
|
||||
$scope.nextPage = function() {
|
||||
$location.search('page', (($routeParams['page'] || 1) * 1) + 1);
|
||||
};
|
||||
|
||||
$scope.currentQuery = $routeParams['q'];
|
||||
refreshResults();
|
||||
|
||||
$scope.$on('$routeUpdate', function(){
|
||||
$scope.currentQuery = $routeParams['q'];
|
||||
refreshResults();
|
||||
});
|
||||
}
|
||||
|
||||
SearchCtrl.$inject = ['$scope', 'ApiService', '$routeParams', '$location', 'Config'];
|
||||
})();
|
10
static/js/pages/security.js
Normal file
10
static/js/pages/security.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
(function() {
|
||||
/**
|
||||
* Security page.
|
||||
*/
|
||||
angular.module('quayPages').config(['pages', function(pages) {
|
||||
pages.create('security', 'security.html', null, {
|
||||
'title': 'Security'
|
||||
});
|
||||
}]);
|
||||
}());
|
20
static/js/pages/signin.js
Normal file
20
static/js/pages/signin.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
(function() {
|
||||
/**
|
||||
* Sign in page.
|
||||
*/
|
||||
angular.module('quayPages').config(['pages', function(pages) {
|
||||
pages.create('signin', 'signin.html', SignInCtrl, {
|
||||
'title': 'Sign In',
|
||||
});
|
||||
}]);
|
||||
|
||||
function SignInCtrl($scope, $location, ExternalLoginService, Features) {
|
||||
$scope.redirectUrl = '/';
|
||||
|
||||
ExternalLoginService.getSingleSigninUrl(function(singleUrl) {
|
||||
if (singleUrl) {
|
||||
document.location = singleUrl;
|
||||
}
|
||||
});
|
||||
}
|
||||
})();
|
190
static/js/pages/superuser.js
Normal file
190
static/js/pages/superuser.js
Normal file
|
@ -0,0 +1,190 @@
|
|||
(function() {
|
||||
/**
|
||||
* The superuser admin page provides a new management UI for Red Hat Quay.
|
||||
*/
|
||||
angular.module('quayPages').config(['pages', function(pages) {
|
||||
pages.create('superuser', 'super-user.html', SuperuserCtrl,
|
||||
{
|
||||
'newLayout': true,
|
||||
'title': 'Red Hat Quay Management'
|
||||
})
|
||||
}]);
|
||||
|
||||
function SuperuserCtrl($scope, $location, ApiService, Features, UserService, ContainerService,
|
||||
AngularPollChannel, CoreDialog, TableService, StateService) {
|
||||
if (!Features.SUPER_USERS) {
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.inReadOnlyMode = StateService.inReadOnlyMode();
|
||||
|
||||
// Monitor any user changes and place the current user into the scope.
|
||||
UserService.updateUserIn($scope);
|
||||
|
||||
$scope.configStatus = null;
|
||||
$scope.logsCounter = 0;
|
||||
$scope.changeLog = null;
|
||||
$scope.logsInstance = null;
|
||||
$scope.pollChannel = null;
|
||||
$scope.logsScrolled = false;
|
||||
$scope.csrf_token = encodeURIComponent(window.__token);
|
||||
$scope.currentConfig = null;
|
||||
$scope.serviceKeysActive = false;
|
||||
$scope.globalMessagesActive = false;
|
||||
$scope.superUserBuildLogsActive = false;
|
||||
$scope.manageUsersActive = false;
|
||||
$scope.orderedOrgs = [];
|
||||
$scope.orgsPerPage = 10;
|
||||
$scope.options = {
|
||||
'predicate': 'name',
|
||||
'reverse': false,
|
||||
'filter': null,
|
||||
'page': 0,
|
||||
}
|
||||
|
||||
$scope.loadMessageOfTheDay = function () {
|
||||
$scope.globalMessagesActive = true;
|
||||
};
|
||||
|
||||
$scope.loadSuperUserBuildLogs = function () {
|
||||
$scope.superUserBuildLogsActive = true;
|
||||
};
|
||||
|
||||
$scope.loadServiceKeys = function() {
|
||||
$scope.serviceKeysActive = true;
|
||||
};
|
||||
|
||||
$scope.getChangeLog = function() {
|
||||
if ($scope.changeLog) { return; }
|
||||
|
||||
ApiService.getChangeLog().then(function(resp) {
|
||||
$scope.changeLog = resp;
|
||||
}, ApiService.errorDisplay('Cannot load change log. Please contact support.'))
|
||||
};
|
||||
|
||||
$scope.loadUsageLogs = function() {
|
||||
$scope.logsCounter++;
|
||||
};
|
||||
|
||||
$scope.loadOrganizations = function() {
|
||||
if ($scope.organizations) {
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.loadOrganizationsInternal();
|
||||
};
|
||||
|
||||
var sortOrgs = function() {
|
||||
if (!$scope.organizations) {return;}
|
||||
$scope.orderedOrgs = TableService.buildOrderedItems($scope.organizations, $scope.options,
|
||||
['name', 'email'], []);
|
||||
};
|
||||
|
||||
$scope.loadOrganizationsInternal = function() {
|
||||
$scope.organizationsResource = ApiService.listAllOrganizationsAsResource().get(function(resp) {
|
||||
$scope.organizations = resp['organizations'];
|
||||
sortOrgs();
|
||||
return $scope.organizations;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.loadUsers = function() {
|
||||
$scope.manageUsersActive = true;
|
||||
};
|
||||
|
||||
$scope.tablePredicateClass = function(name, predicate, reverse) {
|
||||
if (name != predicate) {
|
||||
return '';
|
||||
}
|
||||
return 'current ' + (reverse ? 'reversed' : '');
|
||||
};
|
||||
|
||||
$scope.orderBy = function(predicate) {
|
||||
if (predicate == $scope.options.predicate) {
|
||||
$scope.options.reverse = !$scope.options.reverse;
|
||||
return;
|
||||
}
|
||||
$scope.options.reverse = false;
|
||||
$scope.options.predicate = predicate;
|
||||
};
|
||||
$scope.askDeleteOrganization = function(org) {
|
||||
bootbox.confirm('Are you sure you want to delete this organization? Its data will be deleted with it.',
|
||||
function(result) {
|
||||
if (!result) { return; }
|
||||
|
||||
var params = {
|
||||
'name': org.name
|
||||
};
|
||||
|
||||
ApiService.deleteOrganization(null, params).then(function(resp) {
|
||||
$scope.loadOrganizationsInternal();
|
||||
}, ApiService.errorDisplay('Could not delete organization'));
|
||||
});
|
||||
};
|
||||
|
||||
$scope.askRenameOrganization = function(org) {
|
||||
bootbox.prompt('Enter a new name for the organization:', function(newName) {
|
||||
if (!newName) { return; }
|
||||
|
||||
var params = {
|
||||
'name': org.name
|
||||
};
|
||||
|
||||
var data = {
|
||||
'name': newName
|
||||
};
|
||||
|
||||
ApiService.changeOrganization(data, params).then(function(resp) {
|
||||
$scope.loadOrganizationsInternal();
|
||||
org.name = newName;
|
||||
}, ApiService.errorDisplay('Could not rename organization'));
|
||||
});
|
||||
};
|
||||
|
||||
$scope.askTakeOwnership = function (entity) {
|
||||
$scope.takeOwnershipInfo = {
|
||||
'entity': entity
|
||||
};
|
||||
};
|
||||
|
||||
$scope.takeOwnership = function (info, callback) {
|
||||
var errorDisplay = ApiService.errorDisplay('Could not take ownership of namespace', callback);
|
||||
var params = {
|
||||
'namespace': info.entity.username || info.entity.name
|
||||
};
|
||||
|
||||
ApiService.takeOwnership(null, params).then(function () {
|
||||
callback(true);
|
||||
$location.path('/organization/' + params.namespace);
|
||||
}, errorDisplay)
|
||||
};
|
||||
|
||||
$scope.checkStatus = function() {
|
||||
ContainerService.checkStatus(function(resp) {
|
||||
$('#restartingContainerModal').modal('hide');
|
||||
$scope.configStatus = resp['status'];
|
||||
$scope.configProviderId = resp['provider_id'];
|
||||
|
||||
if ($scope.configStatus == 'ready') {
|
||||
$scope.currentConfig = null;
|
||||
$scope.loadUsers();
|
||||
} else {
|
||||
var message = "Installation of this product has not yet been completed." +
|
||||
"<br><br>Please read the " +
|
||||
"<a href='https://coreos.com/docs/enterprise-registry/initial-setup/'>" +
|
||||
"Setup Guide</a>";
|
||||
|
||||
var title = "Installation Incomplete";
|
||||
CoreDialog.fatal(title, message);
|
||||
}
|
||||
}, $scope.currentConfig);
|
||||
};
|
||||
|
||||
// Load the initial status.
|
||||
$scope.checkStatus();
|
||||
$scope.$watch('options.predicate', sortOrgs);
|
||||
$scope.$watch('options.reverse', sortOrgs);
|
||||
$scope.$watch('options.filter', sortOrgs);
|
||||
|
||||
}
|
||||
}());
|
291
static/js/pages/team-view.js
Normal file
291
static/js/pages/team-view.js
Normal file
|
@ -0,0 +1,291 @@
|
|||
(function() {
|
||||
/**
|
||||
* Page to view the members of a team and add/remove them.
|
||||
*/
|
||||
angular.module('quayPages').config(['pages', function(pages) {
|
||||
pages.create('team-view', 'team-view.html', TeamViewCtrl, {
|
||||
'newLayout': true,
|
||||
'title': 'Team {{ teamname }}',
|
||||
'description': 'Team {{ teamname }}'
|
||||
})
|
||||
}]);
|
||||
|
||||
function TeamViewCtrl($rootScope, $scope, $timeout, Features, Restangular, ApiService,
|
||||
$routeParams, StateService) {
|
||||
var teamname = $routeParams.teamname;
|
||||
var orgname = $routeParams.orgname;
|
||||
|
||||
$scope.inReadOnlyMode = StateService.inReadOnlyMode();
|
||||
$scope.context = {};
|
||||
$scope.orgname = orgname;
|
||||
$scope.teamname = teamname;
|
||||
$scope.addingMember = false;
|
||||
$scope.memberMap = null;
|
||||
$scope.allowEmail = Features.MAILING;
|
||||
$scope.feedback = null;
|
||||
$scope.allowedEntities = ['user', 'robot'];
|
||||
|
||||
$rootScope.title = 'Loading...';
|
||||
|
||||
$scope.filterFunction = function(invited, robots) {
|
||||
return function(item) {
|
||||
// Note: The !! is needed because is_robot will be undefined for invites.
|
||||
var robot_check = (!!item.is_robot == robots);
|
||||
return robot_check && item.invited == invited;
|
||||
};
|
||||
};
|
||||
|
||||
$scope.inviteEmail = function(email) {
|
||||
if (!email || $scope.memberMap[email]) { return; }
|
||||
|
||||
$scope.addingMember = true;
|
||||
|
||||
var params = {
|
||||
'orgname': orgname,
|
||||
'teamname': teamname,
|
||||
'email': email
|
||||
};
|
||||
|
||||
var errorHandler = ApiService.errorDisplay('Cannot invite team member', function() {
|
||||
$scope.addingMember = false;
|
||||
});
|
||||
|
||||
ApiService.inviteTeamMemberEmail(null, params).then(function(resp) {
|
||||
$scope.members.push(resp);
|
||||
$scope.memberMap[resp.email] = resp;
|
||||
$scope.addingMember = false;
|
||||
|
||||
$scope.feedback = {
|
||||
'kind': 'success',
|
||||
'message': 'E-mail address {email} was invited to join the team',
|
||||
'data': {
|
||||
'email': email
|
||||
}
|
||||
};
|
||||
}, errorHandler);
|
||||
};
|
||||
|
||||
$scope.addNewMember = function(member) {
|
||||
if (!member || $scope.memberMap[member.name]) { return; }
|
||||
|
||||
var params = {
|
||||
'orgname': orgname,
|
||||
'teamname': teamname,
|
||||
'membername': member.name
|
||||
};
|
||||
|
||||
var errorHandler = ApiService.errorDisplay('Cannot add team member', function() {
|
||||
$scope.addingMember = false;
|
||||
});
|
||||
|
||||
$scope.addingMember = true;
|
||||
ApiService.updateOrganizationTeamMember(null, params).then(function(resp) {
|
||||
$scope.members.push(resp);
|
||||
$scope.memberMap[resp.name] = resp;
|
||||
$scope.addingMember = false;
|
||||
|
||||
$scope.feedback = {
|
||||
'kind': 'success',
|
||||
'message': 'User {username} was added to the team',
|
||||
'data': {
|
||||
'username': member.name
|
||||
}
|
||||
};
|
||||
}, errorHandler);
|
||||
};
|
||||
|
||||
$scope.revokeInvite = function(inviteInfo) {
|
||||
if (inviteInfo.kind == 'invite') {
|
||||
// E-mail invite.
|
||||
$scope.revokeEmailInvite(inviteInfo.email);
|
||||
} else {
|
||||
// User invite.
|
||||
$scope.removeMember(inviteInfo.name);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.revokeEmailInvite = function(email) {
|
||||
var params = {
|
||||
'orgname': orgname,
|
||||
'teamname': teamname,
|
||||
'email': email
|
||||
};
|
||||
|
||||
ApiService.deleteTeamMemberEmailInvite(null, params).then(function(resp) {
|
||||
if (!$scope.memberMap[email]) { return; }
|
||||
var index = $.inArray($scope.memberMap[email], $scope.members);
|
||||
$scope.members.splice(index, 1);
|
||||
delete $scope.memberMap[email];
|
||||
|
||||
$scope.feedback = {
|
||||
'kind': 'success',
|
||||
'message': 'Invitation to e-amil address {email} was revoked',
|
||||
'data': {
|
||||
'email': email
|
||||
}
|
||||
};
|
||||
}, ApiService.errorDisplay('Cannot revoke team invite'));
|
||||
};
|
||||
|
||||
$scope.removeMember = function(username) {
|
||||
var params = {
|
||||
'orgname': orgname,
|
||||
'teamname': teamname,
|
||||
'membername': username
|
||||
};
|
||||
|
||||
ApiService.deleteOrganizationTeamMember(null, params).then(function(resp) {
|
||||
if (!$scope.memberMap[username]) { return; }
|
||||
var index = $.inArray($scope.memberMap[username], $scope.members);
|
||||
$scope.members.splice(index, 1);
|
||||
delete $scope.memberMap[username];
|
||||
|
||||
$scope.feedback = {
|
||||
'kind': 'success',
|
||||
'message': 'User {username} was removed from the team',
|
||||
'data': {
|
||||
'username': username
|
||||
}
|
||||
};
|
||||
}, ApiService.errorDisplay('Cannot remove team member'));
|
||||
};
|
||||
|
||||
$scope.getServiceName = function(service) {
|
||||
switch (service) {
|
||||
case 'ldap':
|
||||
return 'LDAP';
|
||||
|
||||
case 'keystone':
|
||||
return 'Keystone Auth';
|
||||
|
||||
case 'jwtauthn':
|
||||
return 'External JWT Auth';
|
||||
|
||||
default:
|
||||
return synced.service;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.getAddPlaceholder = function(email, synced) {
|
||||
var kinds = [];
|
||||
|
||||
if (!synced) {
|
||||
kinds.push('registered user');
|
||||
}
|
||||
|
||||
kinds.push('robot');
|
||||
|
||||
if (email && !synced) {
|
||||
kinds.push('email address');
|
||||
}
|
||||
|
||||
kind_string = kinds.join(', ')
|
||||
return 'Add a ' + kind_string + ' to the team';
|
||||
};
|
||||
|
||||
$scope.updateForDescription = function(content) {
|
||||
$scope.organization.teams[teamname].description = content;
|
||||
|
||||
var params = {
|
||||
'orgname': orgname,
|
||||
'teamname': teamname
|
||||
};
|
||||
|
||||
var teaminfo = $scope.organization.teams[teamname];
|
||||
ApiService.updateOrganizationTeam(teaminfo, params).then(function(resp) {
|
||||
$scope.feedback = {
|
||||
'kind': 'success',
|
||||
'message': 'Team description changed',
|
||||
'data': {}
|
||||
};
|
||||
}, function() {
|
||||
$('#cannotChangeTeamModal').modal({});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.showEnableSyncing = function() {
|
||||
$scope.enableSyncingInfo = {
|
||||
'service_info': $scope.canSync,
|
||||
'config': {}
|
||||
};
|
||||
};
|
||||
|
||||
$scope.showDisableSyncing = function() {
|
||||
msg = 'Are you sure you want to disable group syncing on this team? ' +
|
||||
'The team will once again become editable.';
|
||||
bootbox.confirm(msg, function(result) {
|
||||
if (result) {
|
||||
$scope.disableSyncing();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.disableSyncing = function() {
|
||||
var params = {
|
||||
'orgname': orgname,
|
||||
'teamname': teamname
|
||||
};
|
||||
|
||||
var errorHandler = ApiService.errorDisplay('Could not disable team syncing');
|
||||
ApiService.disableOrganizationTeamSync(null, params).then(function(resp) {
|
||||
loadMembers();
|
||||
}, errorHandler);
|
||||
};
|
||||
|
||||
$scope.enableSyncing = function(config, callback) {
|
||||
var params = {
|
||||
'orgname': orgname,
|
||||
'teamname': teamname
|
||||
};
|
||||
|
||||
var errorHandler = ApiService.errorDisplay('Cannot enable team syncing', callback);
|
||||
ApiService.enableOrganizationTeamSync(config, params).then(function(resp) {
|
||||
loadMembers();
|
||||
callback(true);
|
||||
}, errorHandler);
|
||||
};
|
||||
|
||||
var loadOrganization = function() {
|
||||
$scope.orgResource = ApiService.getOrganizationAsResource({'orgname': orgname}).get(function(org) {
|
||||
$scope.organization = org;
|
||||
$scope.team = $scope.organization.teams[teamname];
|
||||
$rootScope.title = teamname + ' (' + $scope.orgname + ')';
|
||||
$rootScope.description = 'Team management page for team ' + teamname + ' under organization ' + $scope.orgname;
|
||||
loadMembers();
|
||||
return org;
|
||||
});
|
||||
};
|
||||
|
||||
var loadMembers = function() {
|
||||
var params = {
|
||||
'orgname': orgname,
|
||||
'teamname': teamname,
|
||||
'includePending': true
|
||||
};
|
||||
|
||||
$scope.membersResource = ApiService.getOrganizationTeamMembersAsResource(params).get(function(resp) {
|
||||
$scope.members = resp.members;
|
||||
$scope.canEditMembers = resp.can_edit;
|
||||
$scope.canSync = resp.can_sync;
|
||||
$scope.syncInfo = resp.synced;
|
||||
$scope.allowedEntities = resp.synced ? ['robot'] : ['user', 'robot'];
|
||||
|
||||
$('.info-icon').popover({
|
||||
'trigger': 'hover',
|
||||
'html': true
|
||||
});
|
||||
|
||||
$scope.memberMap = {};
|
||||
for (var i = 0; i < $scope.members.length; ++i) {
|
||||
var current = $scope.members[i];
|
||||
$scope.memberMap[current.name || current.email] = current;
|
||||
}
|
||||
|
||||
return resp.members;
|
||||
});
|
||||
};
|
||||
|
||||
// Load the organization.
|
||||
loadOrganization();
|
||||
}
|
||||
})();
|
10
static/js/pages/tos.js
Normal file
10
static/js/pages/tos.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
(function() {
|
||||
/**
|
||||
* TOS page.
|
||||
*/
|
||||
angular.module('quayPages').config(['pages', function(pages) {
|
||||
pages.create('tos', 'tos.html', null, {
|
||||
'title': 'Terms of Service'
|
||||
});
|
||||
}]);
|
||||
}());
|
15
static/js/pages/tour.js
Normal file
15
static/js/pages/tour.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
(function() {
|
||||
/**
|
||||
* The site tour page.
|
||||
*/
|
||||
angular.module('quayPages').config(['pages', function(pages) {
|
||||
pages.create('tour', 'tour.html', TourCtrl, {
|
||||
'title': 'Feature Tour',
|
||||
'description': 'Take a tour of Quay\'s features'
|
||||
});
|
||||
}]);
|
||||
|
||||
function TourCtrl($scope, $location) {
|
||||
$scope.kind = $location.path().substring('/tour/'.length);
|
||||
}
|
||||
})();
|
90
static/js/pages/trigger-setup.js
Normal file
90
static/js/pages/trigger-setup.js
Normal file
|
@ -0,0 +1,90 @@
|
|||
(function() {
|
||||
/**
|
||||
* Trigger setup page.
|
||||
*/
|
||||
angular.module('quayPages').config(['pages', function(pages) {
|
||||
pages.create('trigger-setup', 'trigger-setup.html', TriggerSetupCtrl, {
|
||||
'title': 'Setup build trigger',
|
||||
'description': 'Setup build trigger',
|
||||
'newLayout': true
|
||||
});
|
||||
}]);
|
||||
|
||||
function TriggerSetupCtrl($scope, ApiService, $routeParams, $location, UserService, TriggerService) {
|
||||
var namespace = $routeParams.namespace;
|
||||
var name = $routeParams.name;
|
||||
var trigger_uuid = $routeParams.triggerid;
|
||||
|
||||
var loadRepository = function() {
|
||||
var params = {
|
||||
'repository': namespace + '/' + name,
|
||||
'includeTags': false
|
||||
};
|
||||
|
||||
$scope.repositoryResource = ApiService.getRepoAsResource(params).get(function(repo) {
|
||||
$scope.repository = repo;
|
||||
});
|
||||
};
|
||||
|
||||
var loadTrigger = function() {
|
||||
var params = {
|
||||
'repository': namespace + '/' + name,
|
||||
'trigger_uuid': trigger_uuid
|
||||
};
|
||||
|
||||
$scope.triggerResource = ApiService.getBuildTriggerAsResource(params).get(function(trigger) {
|
||||
$scope.trigger = trigger;
|
||||
});
|
||||
};
|
||||
|
||||
loadTrigger();
|
||||
loadRepository();
|
||||
|
||||
$scope.state = 'managing';
|
||||
|
||||
$scope.activateTrigger = function(event) {
|
||||
$scope.state = 'activating';
|
||||
var params = {
|
||||
'repository': namespace + '/' + name,
|
||||
'trigger_uuid': trigger_uuid
|
||||
};
|
||||
|
||||
var data = {
|
||||
'config': event.config
|
||||
};
|
||||
|
||||
if (event.pull_robot) {
|
||||
data['pull_robot'] = event.pull_robot['name'];
|
||||
}
|
||||
|
||||
var errorHandler = ApiService.errorDisplay('Cannot activate build trigger', function(resp) {
|
||||
$scope.state = 'managing';
|
||||
return ApiService.getErrorMessage(resp) +
|
||||
'\n\nNote: Errors can occur if you do not have admin access on the repository';
|
||||
});
|
||||
|
||||
ApiService.activateBuildTrigger(data, params).then(function(resp) {
|
||||
$scope.trigger['is_active'] = true;
|
||||
$scope.trigger['config'] = resp['config'];
|
||||
$scope.trigger['pull_robot'] = resp['pull_robot'];
|
||||
$scope.trigger['repository_url'] = resp['repository_url'];
|
||||
$scope.state = 'activated';
|
||||
|
||||
// If there are no credentials to display, redirect to the builds tab.
|
||||
if (!$scope.trigger['config'].credentials) {
|
||||
$location.url('/repository/' + namespace + '/' + name + '?tab=builds');
|
||||
}
|
||||
}, errorHandler);
|
||||
};
|
||||
|
||||
$scope.getTriggerIcon = function() {
|
||||
if (!$scope.trigger) { return ''; }
|
||||
return TriggerService.getIcon($scope.trigger.service);
|
||||
};
|
||||
|
||||
$scope.getTriggerId = function() {
|
||||
if (!trigger_uuid) { return ''; }
|
||||
return trigger_uuid.split('-')[0];
|
||||
};
|
||||
}
|
||||
}());
|
200
static/js/pages/tutorial.js
Normal file
200
static/js/pages/tutorial.js
Normal file
|
@ -0,0 +1,200 @@
|
|||
(function() {
|
||||
/**
|
||||
* Interactive tutorial page.
|
||||
*/
|
||||
angular.module('quayPages').config(['pages', function(pages) {
|
||||
pages.create('tutorial', 'tutorial.html', TutorialCtrl, {
|
||||
'newLayout': true,
|
||||
'title': 'Tutorial',
|
||||
'description': 'Basic tutorial on using Quay'
|
||||
})
|
||||
}]);
|
||||
|
||||
function TutorialCtrl($scope, AngularTour, AngularTourSignals, UserService, Config, Features) {
|
||||
// Default to showing sudo on all commands if on linux.
|
||||
var showSudo = navigator.appVersion.indexOf("Linux") != -1;
|
||||
|
||||
$scope.tour = {
|
||||
'title': Config.REGISTRY_TITLE_SHORT + ' Tutorial',
|
||||
'initialScope': {
|
||||
'showSudo': showSudo,
|
||||
'domainName': Config.getDomain()
|
||||
},
|
||||
'steps': [
|
||||
{
|
||||
'title': 'Welcome to the ' + Config.REGISTRY_TITLE_SHORT + ' tutorial!',
|
||||
'templateUrl': '/static/tutorial/welcome.html'
|
||||
},
|
||||
{
|
||||
'title': 'Sign in to get started',
|
||||
'templateUrl': '/static/tutorial/signup.html',
|
||||
'signal': function($tourScope) {
|
||||
var user = UserService.currentUser();
|
||||
$tourScope.username = user.username;
|
||||
$tourScope.email = user.email;
|
||||
$tourScope.inOrganization = user.organizations && user.organizations.length > 0;
|
||||
return !user.anonymous;
|
||||
}
|
||||
},
|
||||
{
|
||||
'title': 'Step 1: Login to ' + Config.REGISTRY_TITLE_SHORT,
|
||||
'templateUrl': '/static/tutorial/docker-login.html',
|
||||
'signal': AngularTourSignals.serverEvent('/realtime/user/subscribe?events=docker-cli',
|
||||
function(message) {
|
||||
return message['data']['action'] == 'login';
|
||||
}),
|
||||
'waitMessage': "Waiting for docker login",
|
||||
'skipTitle': "I'm already logged in",
|
||||
'mixpanelEvent': 'tutorial_start'
|
||||
},
|
||||
{
|
||||
'title': 'Step 2: Create a new container',
|
||||
'templateUrl': '/static/tutorial/create-container.html'
|
||||
},
|
||||
{
|
||||
'title': 'Step 3: Create a new image',
|
||||
'templateUrl': '/static/tutorial/create-image.html'
|
||||
},
|
||||
{
|
||||
'title': 'Step 4: Push the image to ' + Config.REGISTRY_TITLE_SHORT,
|
||||
'templateUrl': '/static/tutorial/push-image.html',
|
||||
'signal': AngularTourSignals.serverEvent('/realtime/user/subscribe?events=docker-cli',
|
||||
function(message, tourScope) {
|
||||
var pushing = message['data']['action'] == 'push_start';
|
||||
if (pushing) {
|
||||
tourScope.repoName = message['data']['repository'];
|
||||
}
|
||||
return pushing;
|
||||
}),
|
||||
'waitMessage': "Waiting for repository push to begin",
|
||||
'mixpanelEvent': 'tutorial_wait_for_push'
|
||||
},
|
||||
{
|
||||
'title': 'Push in progress',
|
||||
'templateUrl': '/static/tutorial/pushing.html',
|
||||
'signal': AngularTourSignals.serverEvent('/realtime/user/subscribe?events=docker-cli',
|
||||
function(message, tourScope) {
|
||||
return message['data']['action'] == 'push_repo';
|
||||
}),
|
||||
'waitMessage': "Waiting for repository push to complete"
|
||||
},
|
||||
{
|
||||
'title': 'Step 5: View the repository on ' + Config.REGISTRY_TITLE_SHORT,
|
||||
'templateUrl': '/static/tutorial/view-repo.html',
|
||||
'signal': AngularTourSignals.matchesLocation('/repository/'),
|
||||
'overlayable': true,
|
||||
'mixpanelEvent': 'tutorial_push_complete'
|
||||
},
|
||||
{
|
||||
'templateUrl': '/static/tutorial/view-repo.html',
|
||||
'signal': AngularTourSignals.matchesLocation('/repository/'),
|
||||
'overlayable': true
|
||||
},
|
||||
{
|
||||
'templateUrl': '/static/tutorial/waiting-repo-list.html',
|
||||
'signal': AngularTourSignals.elementAvaliable('*[data-repo="{{username}}/{{repoName}}"]'),
|
||||
'overlayable': true
|
||||
},
|
||||
{
|
||||
'templateUrl': '/static/tutorial/repo-list.html',
|
||||
'signal': AngularTourSignals.matchesLocation('/repository/{{username}}/{{repoName}}'),
|
||||
'element': '*[data-repo="{{username}}/{{repoName}}"]',
|
||||
'overlayable': true
|
||||
},
|
||||
{
|
||||
'title': 'Repository View',
|
||||
'content': 'This is the repository view page. It displays all the primary information about your repository',
|
||||
'overlayable': true,
|
||||
'mixpanelEvent': 'tutorial_view_repo'
|
||||
},
|
||||
{
|
||||
'title': 'Repository Tags',
|
||||
'content': 'Click on the tags tab to view all the tags in the repository',
|
||||
'overlayable': true,
|
||||
'element': '#tagsTab',
|
||||
'signal': AngularTourSignals.elementVisible('*[id="tagsTable"]')
|
||||
},
|
||||
{
|
||||
'title': 'Tag List',
|
||||
'content': 'The tag list displays shows the full list of active tags in the repository. ' +
|
||||
'You can click on an image to see its information or click on a tag to see its history.',
|
||||
'element': '#tagsTable',
|
||||
'overlayable': true
|
||||
},
|
||||
{
|
||||
'title': 'Tag Information',
|
||||
'content': 'Each row displays information about a specific tag',
|
||||
'element': '#tagsTable tr:first-child',
|
||||
'overlayable': true
|
||||
},
|
||||
{
|
||||
'title': 'Tag Actions',
|
||||
'content': 'You can modify a tag by clicking on the Tag Options icon',
|
||||
'element': '#tagsTable tr:first-child .fa-gear',
|
||||
'overlayable': true
|
||||
},
|
||||
{
|
||||
'title': 'Tag History',
|
||||
'content': 'You can view a tags history by clicking on the Tag History icon',
|
||||
'element': '#tagsTable tr:first-child .fa-history',
|
||||
'overlayable': true
|
||||
},
|
||||
{
|
||||
'title': 'Fetch Tag',
|
||||
'content': 'To see the various ways to fetch/pull a tag, click the Fetch Tag icon',
|
||||
'element': '#tagsTable tr:first-child .fa-download',
|
||||
'overlayable': true
|
||||
},
|
||||
{
|
||||
'content': 'To view the permissions for a repository, click on the Gear tab',
|
||||
'element': '#settingsTab',
|
||||
'overlayable': true,
|
||||
'signal': AngularTourSignals.elementVisible('*[id="repoPermissions"]')
|
||||
},
|
||||
{
|
||||
'title': 'Repository Settings',
|
||||
'content': "The repository settings tab allows for modification of a repository's permissions, notifications, visibility and other settings",
|
||||
'overlayable': true,
|
||||
'mixpanelEvent': 'tutorial_view_admin'
|
||||
},
|
||||
{
|
||||
'title': 'Permissions',
|
||||
'templateUrl': '/static/tutorial/permissions.html',
|
||||
'overlayable': true,
|
||||
'element': '#repoPermissions'
|
||||
},
|
||||
{
|
||||
'title': 'Adding a permission',
|
||||
'content': 'To add an <b>additional</b> permission, enter a username or robot account name into the autocomplete ' +
|
||||
'or hit the dropdown arrow to manage robot accounts',
|
||||
'overlayable': true,
|
||||
'element': '#add-entity-permission'
|
||||
},
|
||||
{
|
||||
'content': 'Repositories can be automatically populated in response to a Dockerfile build. To view the build settings for a repository, click on the builds tab',
|
||||
'element': '#buildsTab',
|
||||
'overlayable': true,
|
||||
'signal': AngularTourSignals.elementVisible('*[id="repoBuilds"]'),
|
||||
'skip': !Features.BUILD_SUPPORT
|
||||
},
|
||||
{
|
||||
'content': 'New build triggers can be created by clicking the "Create Build Trigger" button.',
|
||||
'element': '#addBuildTrigger',
|
||||
'overlayable': true,
|
||||
'skip': !Features.BUILD_SUPPORT
|
||||
},
|
||||
{
|
||||
'content': 'The full build history can always be referenced and filtered in the builds list.',
|
||||
'element': '#repoBuilds',
|
||||
'overlayable': true,
|
||||
'skip': !Features.BUILD_SUPPORT
|
||||
},
|
||||
{
|
||||
'templateUrl': '/static/tutorial/done.html',
|
||||
'overlayable': true,
|
||||
'mixpanelEvent': 'tutorial_complete'
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
})();
|
97
static/js/pages/update-user.js
Normal file
97
static/js/pages/update-user.js
Normal file
|
@ -0,0 +1,97 @@
|
|||
(function() {
|
||||
/**
|
||||
* Update user page.
|
||||
*/
|
||||
angular.module('quayPages').config(['pages', function(pages) {
|
||||
pages.create('update-user', 'update-user.html', UpdateUserCtrl, {
|
||||
'title': 'Confirm Username'
|
||||
});
|
||||
}]);
|
||||
|
||||
function UpdateUserCtrl($scope, UserService, $location, ApiService) {
|
||||
$scope.state = 'loading';
|
||||
$scope.metadata = {};
|
||||
|
||||
UserService.updateUserIn($scope, function(user) {
|
||||
if (!user.anonymous) {
|
||||
if (!user.prompts || !user.prompts.length) {
|
||||
$location.path('/');
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.state = 'editing';
|
||||
$scope.username = user.username;
|
||||
}
|
||||
});
|
||||
|
||||
var confirmUsername = function(username) {
|
||||
if (username == $scope.user.username) {
|
||||
$scope.state = 'confirmed';
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.state = 'confirming';
|
||||
var params = {
|
||||
'username': username
|
||||
};
|
||||
|
||||
var oparams = {
|
||||
'orgname': username
|
||||
};
|
||||
|
||||
ApiService.getUserInformation(null, params).then(function() {
|
||||
$scope.state = 'existing';
|
||||
}, function(resp) {
|
||||
ApiService.getOrganization(null, oparams).then(function() {
|
||||
$scope.state = 'existing';
|
||||
}, function() {
|
||||
if (resp.status == 404) {
|
||||
$scope.state = 'confirmed';
|
||||
} else {
|
||||
$scope.state = 'error';
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.updateUser = function(data) {
|
||||
$scope.state = 'updating';
|
||||
var errorHandler = ApiService.errorDisplay('Could not update user information', function() {
|
||||
$scope.state = 'editing';
|
||||
});
|
||||
|
||||
ApiService.changeUserDetails(data).then(function() {
|
||||
UserService.load(function(updated) {
|
||||
if (updated && updated.prompts && updated.prompts.length) {
|
||||
$scope.state = 'editing';
|
||||
} else {
|
||||
$location.url('/');
|
||||
}
|
||||
});
|
||||
}, errorHandler);
|
||||
};
|
||||
|
||||
$scope.hasPrompt = function(user, prompt_name) {
|
||||
if (!user || !user.prompts) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = 0; i < user.prompts.length; ++i) {
|
||||
if (user.prompts[i] == prompt_name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
$scope.$watch('username', function(username) {
|
||||
if (!username) {
|
||||
$scope.state = 'editing';
|
||||
return;
|
||||
}
|
||||
|
||||
confirmUsername(username);
|
||||
});
|
||||
}
|
||||
})();
|
253
static/js/pages/user-view.js
Normal file
253
static/js/pages/user-view.js
Normal file
|
@ -0,0 +1,253 @@
|
|||
(function() {
|
||||
/**
|
||||
* Page that displays details about an user.
|
||||
*/
|
||||
angular.module('quayPages').config(['pages', function(pages) {
|
||||
pages.create('user-view', 'user-view.html', UserViewCtrl, {
|
||||
'newLayout': true,
|
||||
'title': 'User {{ user.username }}',
|
||||
'description': 'User {{ user.username }}'
|
||||
})
|
||||
}]);
|
||||
|
||||
function UserViewCtrl($scope, $routeParams, $timeout, ApiService, UserService, UIService,
|
||||
AvatarService, Config, ExternalLoginService, CookieService, StateService) {
|
||||
var username = $routeParams.username;
|
||||
|
||||
$scope.inReadOnlyMode = StateService.inReadOnlyMode();
|
||||
$scope.Config = Config;
|
||||
|
||||
$scope.showAppsCounter = 0;
|
||||
$scope.showRobotsCounter = 0;
|
||||
$scope.showBillingCounter = 0;
|
||||
$scope.showLogsCounter = 0;
|
||||
|
||||
$scope.changeEmailInfo = null;
|
||||
$scope.changePasswordInfo = null;
|
||||
$scope.changeMetadataInfo = null;
|
||||
|
||||
$scope.hasSingleSignin = ExternalLoginService.hasSingleSignin();
|
||||
$scope.context = {};
|
||||
|
||||
$scope.oidcLoginProvider = null;
|
||||
|
||||
if (Config['INTERNAL_OIDC_SERVICE_ID']) {
|
||||
ExternalLoginService.EXTERNAL_LOGINS.forEach(function(provider) {
|
||||
if (provider.id == Config['INTERNAL_OIDC_SERVICE_ID']) {
|
||||
$scope.oidcLoginProvider = provider;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
UserService.updateUserIn($scope, function(user) {
|
||||
if (user && user.username) {
|
||||
if ($scope.oidcLoginProvider && $routeParams['idtoken']) {
|
||||
$scope.context.idTokenCredentials = {
|
||||
'username': UserService.getCLIUsername(),
|
||||
'password': $routeParams['idtoken'],
|
||||
'namespace': UserService.currentUser().username
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var loadRepositories = function() {
|
||||
var options = {
|
||||
'public': true,
|
||||
'namespace': username,
|
||||
'last_modified': true,
|
||||
'popularity': true
|
||||
};
|
||||
|
||||
$scope.context.viewuser.repositories = ApiService.listReposAsResource().withOptions(options).get(function(resp) {
|
||||
return resp.repositories;
|
||||
});
|
||||
};
|
||||
|
||||
var loadUser = function() {
|
||||
$scope.userResource = ApiService.getUserInformationAsResource({'username': username}).get(function(user) {
|
||||
$scope.context.viewuser = user;
|
||||
$scope.viewuser = user;
|
||||
|
||||
$timeout(function() {
|
||||
// Load the repositories.
|
||||
loadRepositories();
|
||||
|
||||
// Show the password change dialog if immediately after an account recovery.
|
||||
if ($routeParams.action == 'password' && UserService.isNamespaceAdmin(username)) {
|
||||
$scope.showChangePassword();
|
||||
}
|
||||
}, 10);
|
||||
});
|
||||
};
|
||||
|
||||
// Load the user.
|
||||
loadUser();
|
||||
|
||||
$scope.showRobots = function() {
|
||||
$scope.showRobotsCounter++;
|
||||
};
|
||||
|
||||
$scope.showLogs = function() {
|
||||
$scope.showLogsCounter++;
|
||||
};
|
||||
|
||||
$scope.showApplications = function() {
|
||||
$scope.showAppsCounter++;
|
||||
};
|
||||
|
||||
$scope.showChangePassword = function() {
|
||||
$scope.changePasswordInfo = {};
|
||||
};
|
||||
|
||||
$scope.changePassword = function(info, callback) {
|
||||
if (Config.AUTHENTICATION_TYPE != 'Database') { return; }
|
||||
|
||||
var data = {
|
||||
'password': $scope.changePasswordInfo.password
|
||||
};
|
||||
|
||||
var errorDisplay = ApiService.errorDisplay('Could not change password', callback);
|
||||
|
||||
ApiService.changeUserDetails(data).then(function(resp) {
|
||||
// Reload the user.
|
||||
UserService.load();
|
||||
callback(true);
|
||||
}, errorDisplay);
|
||||
};
|
||||
|
||||
$scope.generateClientToken = function() {
|
||||
var generateToken = function(password) {
|
||||
if (!password) {
|
||||
return;
|
||||
}
|
||||
|
||||
var data = {
|
||||
'password': password
|
||||
};
|
||||
|
||||
ApiService.generateUserClientKey(data).then(function(resp) {
|
||||
$scope.context.encryptedPasswordCredentials = {
|
||||
'username': UserService.getCLIUsername(),
|
||||
'password': resp['key'],
|
||||
'namespace': UserService.currentUser().username
|
||||
};
|
||||
}, ApiService.errorDisplay('Could not generate token'));
|
||||
};
|
||||
|
||||
UIService.showPasswordDialog('Enter your password to generate an encrypted version:', generateToken);
|
||||
};
|
||||
|
||||
$scope.showChangeMetadata = function(field_name, field_title) {
|
||||
$scope.changeMetadataInfo = {
|
||||
'value': $scope.context.viewuser[field_name],
|
||||
'field': field_name,
|
||||
'title': field_title
|
||||
};
|
||||
};
|
||||
|
||||
$scope.updateMetadataInfo = function(info, callback) {
|
||||
var details = {};
|
||||
details[info.field] = (info.value === '' ? null : info.value);
|
||||
|
||||
var errorDisplay = ApiService.errorDisplay('Could not update ' + info.title, callback);
|
||||
|
||||
ApiService.changeUserDetails(details).then(function() {
|
||||
$scope.context.viewuser[info.field] = info.value;
|
||||
callback(true);
|
||||
}, errorDisplay);
|
||||
};
|
||||
|
||||
$scope.showChangeEmail = function() {
|
||||
$scope.changeEmailInfo = {
|
||||
'email': $scope.context.viewuser.email
|
||||
};
|
||||
};
|
||||
|
||||
$scope.changeEmail = function(info, callback) {
|
||||
var details = {
|
||||
'email': $scope.changeEmailInfo.email
|
||||
};
|
||||
|
||||
var errorDisplay = ApiService.errorDisplay('Could not change email address', callback);
|
||||
|
||||
ApiService.changeUserDetails(details).then(function() {
|
||||
$scope.context.emailAwaitingChange = $scope.changeEmailInfo.email;
|
||||
callback(true);
|
||||
}, errorDisplay);
|
||||
};
|
||||
|
||||
$scope.showChangeAccount = function() {
|
||||
$scope.convertAccountInfo = {
|
||||
'user': $scope.context.viewuser
|
||||
};
|
||||
};
|
||||
|
||||
$scope.showBilling = function() {
|
||||
$scope.showBillingCounter++;
|
||||
};
|
||||
|
||||
|
||||
$scope.notificationsPermissionsEnabled = window['Notification']
|
||||
&& Notification.permission === 'granted'
|
||||
&& CookieService.get('quay.enabledDesktopNotifications') === 'on';
|
||||
|
||||
$scope.desktopNotificationsPermissionIsDisabled = () => window['Notification'] && Notification.permission === 'denied';
|
||||
|
||||
$scope.toggleDesktopNotifications = () => {
|
||||
if (!window['Notification']) { // unsupported in IE & some older browsers, we'll just tell the user it's not available
|
||||
bootbox.dialog({
|
||||
"message": 'Desktop Notifications unsupported in this browser',
|
||||
"title": 'Unsupported Option',
|
||||
"buttons": {
|
||||
"close": {
|
||||
"label": "Close",
|
||||
"className": "btn-primary"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (CookieService.get('quay.enabledDesktopNotifications') === 'on') {
|
||||
bootbox.confirm('Are you sure you want to turn off browser notifications?', confirmed => {
|
||||
if (confirmed) {
|
||||
CookieService.putPermanent('quay.enabledDesktopNotifications', 'off');
|
||||
CookieService.clear('quay.notifications.mostRecentTimestamp');
|
||||
|
||||
$scope.$apply(() => {
|
||||
$scope.notificationsPermissionsEnabled = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (Notification.permission === 'default') {
|
||||
Notification.requestPermission()
|
||||
.then((newPermission) => {
|
||||
if (newPermission === 'granted') {
|
||||
CookieService.putPermanent('quay.enabledDesktopNotifications', 'on');
|
||||
CookieService.putPermanent('quay.notifications.mostRecentTimestamp', new Date().getTime().toString());
|
||||
}
|
||||
|
||||
$scope.$apply(() => {
|
||||
$scope.notificationsPermissionsEnabled = (newPermission === 'granted');
|
||||
});
|
||||
});
|
||||
} else if (Notification.permission === 'granted') {
|
||||
bootbox.confirm('Are you sure you want to turn on browser notifications?', confirmed => {
|
||||
if (confirmed) {
|
||||
CookieService.putPermanent('quay.enabledDesktopNotifications', 'on');
|
||||
CookieService.putPermanent('quay.notifications.mostRecentTimestamp', new Date().getTime().toString());
|
||||
|
||||
$scope.$apply(() => {
|
||||
$scope.notificationsPermissionsEnabled = true;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
})();
|
Reference in a new issue