diff --git a/endpoints/api.py b/endpoints/api.py index b5c4a49ec..8d484ee96 100644 --- a/endpoints/api.py +++ b/endpoints/api.py @@ -26,7 +26,7 @@ from auth.permissions import (ReadRepositoryPermission, OrganizationMemberPermission, ViewTeamPermission) from endpoints import registry -from endpoints.web import common_login +from endpoints.common import common_login from util.cache import cache_control from datetime import datetime, timedelta @@ -35,6 +35,38 @@ store = app.config['STORAGE'] user_files = app.config['USERFILES'] logger = logging.getLogger(__name__) +route_data = None + +def get_route_data(): + global route_data + if route_data: + return route_data + + routes = [] + for rule in app.url_map.iter_rules(): + if rule.rule.startswith('/api/'): + endpoint_method = globals()[rule.endpoint] + is_internal = '__internal_call' in dir(endpoint_method) + is_org_api = '__user_call' in dir(endpoint_method) + methods = list(rule.methods.difference(['HEAD', 'OPTIONS'])) + + route = { + 'name': rule.endpoint, + 'methods': methods, + 'path': rule.rule, + 'parameters': list(rule.arguments) + } + + if is_org_api: + route['user_method'] = endpoint_method.__user_call + + routes.append(route) + + route_data = { + 'endpoints': routes + } + return route_data + def log_action(kind, user_or_orgname, metadata={}, repo=None): performer = current_user.db_user() @@ -60,6 +92,26 @@ def api_login_required(f): return decorated_view +def internal_api_call(f): + @wraps(f) + def decorated_view(*args, **kwargs): + return f(*args, **kwargs) + + decorated_view.__internal_call = True + return decorated_view + + +def org_api_call(user_call_name): + def internal_decorator(f): + @wraps(f) + def decorated_view(*args, **kwargs): + return f(*args, **kwargs) + + decorated_view.__user_call = user_call_name + return decorated_view + + return internal_decorator + @app.errorhandler(model.DataModelException) def handle_dme(ex): return make_response(ex.message, 400) @@ -70,13 +122,19 @@ def handle_dme_key_error(ex): return make_response(ex.message, 400) +@app.route('/api/discovery') +def discovery(): + return jsonify(get_route_data()) + + @app.route('/api/') +@internal_api_call def welcome(): return make_response('welcome', 200) @app.route('/api/plans/') -def plans_list(): +def list_plans(): return jsonify({ 'plans': PLANS, }) @@ -108,6 +166,7 @@ def user_view(user): @app.route('/api/user/', methods=['GET']) +@internal_api_call def get_logged_in_user(): if current_user.is_anonymous(): return jsonify({'anonymous': True}) @@ -121,6 +180,7 @@ def get_logged_in_user(): @app.route('/api/user/private', methods=['GET']) @api_login_required +@internal_api_call def get_user_private_count(): user = current_user.db_user() private_repos = model.get_private_repo_count(user.username) @@ -141,6 +201,7 @@ def get_user_private_count(): @app.route('/api/user/convert', methods=['POST']) @api_login_required +@internal_api_call def convert_user_to_organization(): user = current_user.db_user() convert_data = request.get_json() @@ -177,6 +238,7 @@ def convert_user_to_organization(): @app.route('/api/user/', methods=['PUT']) @api_login_required +@internal_api_call def change_user_details(): user = current_user.db_user() @@ -203,7 +265,8 @@ def change_user_details(): @app.route('/api/user/', methods=['POST']) -def create_user_api(): +@internal_api_call +def create_new_user(): user_data = request.get_json() existing_user = model.get_user(user_data['username']) @@ -229,7 +292,8 @@ def create_user_api(): @app.route('/api/signin', methods=['POST']) -def signin_api(): +@internal_api_call +def signin_user(): signin_data = request.get_json() username = signin_data['username'] @@ -263,6 +327,7 @@ def conduct_signin(username, password): @app.route("/api/signout", methods=['POST']) @api_login_required +@internal_api_call def logout(): logout_user() identity_changed.send(app, identity=AnonymousIdentity()) @@ -270,7 +335,8 @@ def logout(): @app.route("/api/recovery", methods=['POST']) -def send_recovery(): +@internal_api_call +def request_recovery_email(): email = request.get_json()['email'] code = model.create_reset_password_email_code(email) send_recovery_email(email, code.code) @@ -355,7 +421,8 @@ def team_view(orgname, team): @app.route('/api/organization/', methods=['POST']) @api_login_required -def create_organization_api(): +@internal_api_call +def create_organization(): org_data = request.get_json() existing = None @@ -419,6 +486,7 @@ def get_organization(orgname): @app.route('/api/organization/', methods=['PUT']) @api_login_required +@org_api_call('change_user_details') def change_organization_details(orgname): permission = AdministerOrganizationPermission(orgname) if permission.can(): @@ -496,6 +564,7 @@ def get_organization_member(orgname, membername): @app.route('/api/organization//private', methods=['GET']) @api_login_required +@internal_api_call def get_organization_private_allowed(orgname): permission = CreateRepositoryPermission(orgname) if permission.can(): @@ -657,7 +726,7 @@ def delete_organization_team_member(orgname, teamname, membername): @app.route('/api/repository', methods=['POST']) @api_login_required -def create_repo_api(): +def create_repo(): owner = current_user.db_user() req = request.get_json() namespace_name = req['namespace'] if 'namespace' in req else owner.username @@ -690,7 +759,7 @@ def create_repo_api(): @app.route('/api/find/repository', methods=['GET']) -def match_repos_api(): +def find_repos(): prefix = request.args.get('query', '') def repo_view(repo): @@ -713,7 +782,7 @@ def match_repos_api(): @app.route('/api/repository/', methods=['GET']) -def list_repos_api(): +def list_repos(): def repo_view(repo_obj): return { 'namespace': repo_obj.namespace, @@ -756,7 +825,7 @@ def list_repos_api(): @app.route('/api/repository/', methods=['PUT']) @api_login_required @parse_repository_name -def update_repo_api(namespace, repository): +def update_repo(namespace, repository): permission = ModifyRepositoryPermission(namespace, repository) if permission.can(): repo = model.get_repository(namespace, repository) @@ -779,7 +848,7 @@ def update_repo_api(namespace, repository): methods=['POST']) @api_login_required @parse_repository_name -def change_repo_visibility_api(namespace, repository): +def change_repo_visibility(namespace, repository): permission = AdministerRepositoryPermission(namespace, repository) if permission.can(): repo = model.get_repository(namespace, repository) @@ -823,7 +892,7 @@ def image_view(image): @app.route('/api/repository/', methods=['GET']) @parse_repository_name -def get_repo_api(namespace, repository): +def get_repo(namespace, repository): logger.debug('Get repo: %s/%s' % (namespace, repository)) def tag_view(tag): @@ -1006,6 +1075,7 @@ def delete_webhook(namespace, repository, public_id): @app.route('/api/filedrop/', methods=['POST']) @api_login_required +@internal_api_call def get_filedrop_url(): mime_type = request.get_json()['mimeType'] (url, file_id) = user_files.prepare_for_drop(mime_type) @@ -1436,14 +1506,17 @@ def subscription_view(stripe_subscription, used_repos): @app.route('/api/user/card', methods=['GET']) @api_login_required -def get_user_card_api(): +@internal_api_call +def get_user_card(): user = current_user.db_user() return get_card(user) @app.route('/api/organization//card', methods=['GET']) @api_login_required -def get_org_card_api(orgname): +@internal_api_call +@org_api_call('get_user_card') +def get_org_card(orgname): permission = AdministerOrganizationPermission(orgname) if permission.can(): organization = model.get_organization(orgname) @@ -1454,7 +1527,8 @@ def get_org_card_api(orgname): @app.route('/api/user/card', methods=['POST']) @api_login_required -def set_user_card_api(): +@internal_api_call +def set_user_card(): user = current_user.db_user() token = request.get_json()['token'] response = set_card(user, token) @@ -1464,7 +1538,8 @@ def set_user_card_api(): @app.route('/api/organization//card', methods=['POST']) @api_login_required -def set_org_card_api(orgname): +@org_api_call('set_user_card') +def set_org_card(orgname): permission = AdministerOrganizationPermission(orgname) if permission.can(): organization = model.get_organization(orgname) @@ -1515,7 +1590,8 @@ def get_card(user): @app.route('/api/user/plan', methods=['PUT']) @api_login_required -def subscribe_api(): +@internal_api_call +def update_user_subscription(): request_data = request.get_json() plan = request_data['plan'] token = request_data['token'] if 'token' in request_data else None @@ -1607,7 +1683,7 @@ def subscribe(user, plan, token, require_business_plan): @app.route('/api/user/invoices', methods=['GET']) @api_login_required -def user_invoices_api(): +def list_user_invoices(): user = current_user.db_user() if not user.stripe_id: abort(404) @@ -1617,7 +1693,8 @@ def user_invoices_api(): @app.route('/api/organization//invoices', methods=['GET']) @api_login_required -def org_invoices_api(orgname): +@org_api_call('list_user_invoices') +def list_org_invoices(orgname): permission = AdministerOrganizationPermission(orgname) if permission.can(): organization = model.get_organization(orgname) @@ -1653,7 +1730,9 @@ def get_invoices(customer_id): @app.route('/api/organization//plan', methods=['PUT']) @api_login_required -def subscribe_org_api(orgname): +@internal_api_call +@org_api_call('update_user_subscription') +def update_org_subscription(orgname): permission = AdministerOrganizationPermission(orgname) if permission.can(): request_data = request.get_json() @@ -1667,7 +1746,8 @@ def subscribe_org_api(orgname): @app.route('/api/user/plan', methods=['GET']) @api_login_required -def get_subscription(): +@internal_api_call +def get_user_subscription(): user = current_user.db_user() private_repos = model.get_private_repo_count(user.username) @@ -1685,6 +1765,8 @@ def get_subscription(): @app.route('/api/organization//plan', methods=['GET']) @api_login_required +@internal_api_call +@org_api_call('get_user_subscription') def get_org_subscription(orgname): permission = AdministerOrganizationPermission(orgname) if permission.can(): @@ -1723,6 +1805,7 @@ def get_user_robots(): @app.route('/api/organization//robots', methods=['GET']) @api_login_required +@org_api_call('get_user_robots') def get_org_robots(orgname): permission = OrganizationMemberPermission(orgname) if permission.can(): @@ -1736,7 +1819,7 @@ def get_org_robots(orgname): @app.route('/api/user/robots/', methods=['PUT']) @api_login_required -def create_robot(robot_shortname): +def create_user_robot(robot_shortname): parent = current_user.db_user() robot, password = model.create_robot(robot_shortname, parent) resp = jsonify(robot_view(robot.username, password)) @@ -1748,6 +1831,7 @@ def create_robot(robot_shortname): @app.route('/api/organization//robots/', methods=['PUT']) @api_login_required +@org_api_call('create_user_robot') def create_org_robot(orgname, robot_shortname): permission = AdministerOrganizationPermission(orgname) if permission.can(): @@ -1763,7 +1847,7 @@ def create_org_robot(orgname, robot_shortname): @app.route('/api/user/robots/', methods=['DELETE']) @api_login_required -def delete_robot(robot_shortname): +def delete_user_robot(robot_shortname): parent = current_user.db_user() model.delete_robot(format_robot_username(parent.username, robot_shortname)) log_action('delete_robot', parent.username, {'robot': robot_shortname}) @@ -1773,6 +1857,7 @@ def delete_robot(robot_shortname): @app.route('/api/organization//robots/', methods=['DELETE']) @api_login_required +@org_api_call('delete_user_robot') def delete_org_robot(orgname, robot_shortname): permission = AdministerOrganizationPermission(orgname) if permission.can(): @@ -1804,7 +1889,7 @@ def log_view(log): @app.route('/api/repository//logs', methods=['GET']) @api_login_required @parse_repository_name -def repo_logs_api(namespace, repository): +def list_repo_logs(namespace, repository): permission = AdministerRepositoryPermission(namespace, repository) if permission.can(): repo = model.get_repository(namespace, repository) @@ -1820,7 +1905,8 @@ def repo_logs_api(namespace, repository): @app.route('/api/organization//logs', methods=['GET']) @api_login_required -def org_logs_api(orgname): +@org_api_call('list_user_logs') +def list_org_logs(orgname): permission = AdministerOrganizationPermission(orgname) if permission.can(): performer_name = request.args.get('performer', None) @@ -1835,7 +1921,7 @@ def org_logs_api(orgname): @app.route('/api/user/logs', methods=['GET']) @api_login_required -def user_logs_api(): +def list_user_logs(): performer_name = request.args.get('performer', None) start_time = request.args.get('starttime', None) end_time = request.args.get('endtime', None) diff --git a/endpoints/common.py b/endpoints/common.py new file mode 100644 index 000000000..d6bd0125a --- /dev/null +++ b/endpoints/common.py @@ -0,0 +1,48 @@ +import logging + +from flask.ext.login import login_user, UserMixin +from flask.ext.principal import identity_changed + +from data import model +from app import app, login_manager +from auth.permissions import QuayDeferredPermissionUser + + +logger = logging.getLogger(__name__) + + +@login_manager.user_loader +def load_user(username): + logger.debug('Loading user: %s' % username) + return _LoginWrappedDBUser(username) + +class _LoginWrappedDBUser(UserMixin): + def __init__(self, db_username, db_user=None): + + self._db_username = db_username + self._db_user = db_user + + def db_user(self): + if not self._db_user: + self._db_user = model.get_user(self._db_username) + return self._db_user + + def is_authenticated(self): + return self.db_user() is not None + + def is_active(self): + return self.db_user().verified + + def get_id(self): + return unicode(self._db_username) + + +def common_login(db_user): + if login_user(_LoginWrappedDBUser(db_user.username, db_user)): + logger.debug('Successfully signed in as: %s' % db_user.username) + new_identity = QuayDeferredPermissionUser(db_user.username, 'username') + identity_changed.send(app, identity=new_identity) + return True + else: + logger.debug('User could not be logged in, inactive?.') + return False diff --git a/endpoints/web.py b/endpoints/web.py index c11c9ac3e..82ee2bc50 100644 --- a/endpoints/web.py +++ b/endpoints/web.py @@ -4,53 +4,30 @@ import stripe from flask import (abort, redirect, request, url_for, render_template, make_response, Response) -from flask.ext.login import login_user, UserMixin, current_user -from flask.ext.principal import identity_changed +from flask.ext.login import current_user from urlparse import urlparse from data import model -from app import app, login_manager, mixpanel -from auth.permissions import (QuayDeferredPermissionUser, - AdministerOrganizationPermission) +from app import app, mixpanel +from auth.permissions import AdministerOrganizationPermission from util.invoice import renderInvoiceToPdf from util.seo import render_snapshot +from endpoints.api import get_route_data +from endpoints.common import common_login logger = logging.getLogger(__name__) -class _LoginWrappedDBUser(UserMixin): - def __init__(self, db_username, db_user=None): - - self._db_username = db_username - self._db_user = db_user - - def db_user(self): - if not self._db_user: - self._db_user = model.get_user(self._db_username) - return self._db_user - - def is_authenticated(self): - return self.db_user() is not None - - def is_active(self): - return self.db_user().verified - - def get_id(self): - return unicode(self._db_username) - - -@login_manager.user_loader -def load_user(username): - logger.debug('Loading user: %s' % username) - return _LoginWrappedDBUser(username) +def render_page_template(name): + return render_template(name, route_data = get_route_data()) @app.route('/', methods=['GET'], defaults={'path': ''}) @app.route('/repository/', methods=['GET']) @app.route('/organization/', methods=['GET']) def index(path): - return render_template('index.html') + return render_page_template('index.html') @app.route('/snapshot', methods=['GET']) @@ -81,6 +58,7 @@ def guide(): def organizations(): return index('') + @app.route('/user/') def user(): return index('') @@ -119,17 +97,17 @@ def status(): @app.route('/tos', methods=['GET']) def tos(): - return render_template('tos.html') + return render_page_template('tos.html') @app.route('/disclaimer', methods=['GET']) def disclaimer(): - return render_template('disclaimer.html') + return render_page_template('disclaimer.html') @app.route('/privacy', methods=['GET']) def privacy(): - return render_template('privacy.html') + return render_page_template('privacy.html') @app.route('/receipt', methods=['GET']) @@ -162,17 +140,6 @@ def receipt(): abort(404) -def common_login(db_user): - if login_user(_LoginWrappedDBUser(db_user.username, db_user)): - logger.debug('Successfully signed in as: %s' % db_user.username) - new_identity = QuayDeferredPermissionUser(db_user.username, 'username') - identity_changed.send(app, identity=new_identity) - return True - else: - logger.debug('User could not be logged in, inactive?.') - return False - - @app.route('/oauth2/github/callback', methods=['GET']) def github_oauth_callback(): code = request.args.get('code') @@ -228,12 +195,12 @@ def github_oauth_callback(): mixpanel.alias(to_login.username, state) except model.DataModelException, ex: - return render_template('githuberror.html', error_message=ex.message) + return render_page_template('githuberror.html', error_message=ex.message) if common_login(to_login): return redirect(url_for('index')) - return render_template('githuberror.html') + return render_page_template('githuberror.html') @app.route('/confirm', methods=['GET']) diff --git a/static/js/app.js b/static/js/app.js index a03f5a989..414c05357 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -1,6 +1,18 @@ var TEAM_PATTERN = '^[a-zA-Z][a-zA-Z0-9]+$'; var ROBOT_PATTERN = '^[a-zA-Z][a-zA-Z0-9]+$'; +function getRestUrl(args) { + var url = ''; + for (var i = 0; i < arguments.length; ++i) { + if (i > 0) { + url += '/'; + } + url += encodeURI(arguments[i]) + } + return url; +} + + function getFirstTextLine(commentString) { if (!commentString) { return ''; } @@ -34,11 +46,8 @@ function getFirstTextLine(commentString) { return ''; } -function createRobotAccount(Restangular, is_org, orgname, name, callback) { - var url = is_org ? getRestUrl('organization', orgname, 'robots', name) : - getRestUrl('user/robots', name); - var createRobot = Restangular.one(url); - createRobot.customPUT().then(callback, function(resp) { +function createRobotAccount(ApiService, is_org, orgname, name, callback) { + ApiService.createRobot(is_org ? orgname : null, null, {'robot_shortname': name}).then(callback, function(resp) { bootbox.dialog({ "message": resp.data ? resp.data : 'The robot account could not be created', "title": "Cannot create robot account", @@ -52,14 +61,18 @@ function createRobotAccount(Restangular, is_org, orgname, name, callback) { }); } -function createOrganizationTeam(Restangular, orgname, teamname, callback) { - var createTeam = Restangular.one(getRestUrl('organization', orgname, 'team', teamname)); +function createOrganizationTeam(ApiService, orgname, teamname, callback) { var data = { 'name': teamname, 'role': 'member' }; + + var params = { + 'orgname': orgname, + 'teamname': teamname + }; - createTeam.customPOST(data).then(callback, function() { + ApiService.updateOrganizationTeam(data, params).then(callback, function() { bootbox.dialog({ "message": resp.data ? resp.data : 'The team could not be created', "title": "Cannot create team", @@ -73,17 +86,6 @@ function createOrganizationTeam(Restangular, orgname, teamname, callback) { }); } -function getRestUrl(args) { - var url = ''; - for (var i = 0; i < arguments.length; ++i) { - if (i > 0) { - url += '/'; - } - url += encodeURI(arguments[i]) - } - return url; -} - function getMarkedDown(string) { return Markdown.getSanitizingConverter().makeHtml(string || ''); } @@ -91,6 +93,137 @@ function getMarkedDown(string) { // Start the application code itself. quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'restangular', 'angularMoment', 'angulartics', /*'angulartics.google.analytics',*/ 'angulartics.mixpanel', '$strap.directives', 'ngCookies'], function($provide, cfpLoadingBarProvider) { cfpLoadingBarProvider.includeSpinner = false; + + $provide.factory('ApiService', ['Restangular', function(Restangular) { + var apiService = {}; + + var getResource = function(path) { + var resource = {}; + resource.url = path; + resource.withOptions = function(options) { + this.options = options; + return this; + }; + + resource.get = function(processor, opt_errorHandler) { + var options = this.options; + var performer = Restangular.one(this.url); + + var result = { + 'loading': true, + 'value': null, + 'hasError': false + }; + + performer.get(options).then(function(resp) { + result.value = processor(resp); + result.loading = false; + }, function(resp) { + result.hasError = true; + result.loading = false; + if (opt_errorHandler) { + opt_errorHandler(resp); + } + }); + + return result; + }; + + return resource; + }; + + var formatMethodName = function(endpointName) { + var formatted = ''; + for (var i = 0; i < endpointName.length; ++i) { + var c = endpointName[i]; + if (c == '_') { + c = endpointName[i + 1].toUpperCase(); + i++; + } + + formatted += c; + } + + return formatted; + }; + + var buildUrl = function(path, parameters) { + // We already have /api/ on the URLs, so remove them from the paths. + path = path.substr('/api/'.length, path.length); + + var url = ''; + for (var i = 0; i < path.length; ++i) { + var c = path[i]; + if (c == '<') { + var end = path.indexOf('>', i); + var varName = path.substr(i + 1, end - i - 1); + var colon = varName.indexOf(':'); + var isPathVar = false; + if (colon > 0) { + isPathVar = true; + varName = varName.substr(colon + 1); + } + + if (!parameters[varName]) { + throw new Error('Missing parameter: ' + varName); + } + + url += isPathVar ? parameters[varName] : encodeURI(parameters[varName]); + i = end; + continue; + } + + url += c; + } + + return url; + }; + + var getGenericMethodName = function(userMethodName) { + return formatMethodName(userMethodName.replace('_user', '')); + }; + + var buildMethodsForEndpoint = function(endpoint) { + var method = endpoint.methods[0].toLowerCase(); + var methodName = formatMethodName(endpoint['name']); + apiService[methodName] = function(opt_options, opt_parameters) { + return Restangular.one(buildUrl(endpoint['path'], opt_parameters))['custom' + method.toUpperCase()](opt_options); + }; + + if (method == 'get') { + apiService[methodName + 'AsResource'] = function(opt_parameters) { + return getResource(buildUrl(endpoint['path'], opt_parameters)); + }; + } + + if (endpoint['user_method']) { + apiService[getGenericMethodName(endpoint['user_method'])] = function(orgname, opt_options, opt_parameters) { + if (orgname) { + if (orgname.name) { + orgname = orgname.name; + } + + var params = jQuery.extend({'orgname' : orgname}, opt_parameters || {}); + return apiService[methodName](opt_options, params); + } else { + return apiService[formatMethodName(endpoint['user_method'])](opt_options, opt_parameters); + } + }; + } + }; + + // Construct the methods for each API endpoint. + if (!window.__endpoints) { + return apiService; + } + + for (var i = 0; i < window.__endpoints.length; ++i) { + var endpoint = window.__endpoints[i]; + buildMethodsForEndpoint(endpoint); + } + + return apiService; + }]); $provide.factory('CookieService', ['$cookies', '$cookieStore', function($cookies, $cookieStore) { var cookieService = {}; @@ -113,7 +246,7 @@ quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'rest return cookieService; }]); - $provide.factory('UserService', ['Restangular', 'CookieService', function(Restangular, CookieService) { + $provide.factory('UserService', ['ApiService', 'CookieService', function(ApiService, CookieService) { var userResponse = { verified: false, anonymous: true, @@ -139,8 +272,7 @@ quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'rest }; userService.load = function(opt_callback) { - var userFetch = Restangular.one('user/'); - userFetch.get().then(function(loadedUser) { + ApiService.getLoggedInUser().then(function(loadedUser) { userResponse = loadedUser; if (!userResponse.anonymous) { @@ -198,48 +330,6 @@ quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'rest return userService; }]); - - $provide.factory('ApiService', ['Restangular', function(Restangular) { - var apiService = {} - apiService.at = function(locationPieces) { - var location = getRestUrl.apply(this, arguments); - var info = { - 'url': location, - 'caller': Restangular.one(location), - 'withOptions': function(options) { - info.options = options; - return info; - }, - 'get': function(processor, opt_errorHandler) { - var options = info.options; - var caller = info.caller; - var result = { - 'loading': true, - 'value': null, - 'hasError': false - }; - - caller.get(options).then(function(resp) { - result.value = processor(resp); - result.loading = false; - }, function(resp) { - result.hasError = true; - result.loading = false; - if (opt_errorHandler) { - opt_errorHandler(resp); - } - }); - - return result; - } - }; - - return info; - }; - - return apiService; - }]); - $provide.factory('KeyService', ['$location', function($location) { var keyService = {} @@ -254,8 +344,8 @@ quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'rest return keyService; }]); - $provide.factory('PlanService', ['Restangular', 'KeyService', 'UserService', 'CookieService', - function(Restangular, KeyService, UserService, CookieService) { + $provide.factory('PlanService', ['KeyService', 'UserService', 'CookieService', 'ApiService', + function(KeyService, UserService, CookieService, ApiService) { var plans = null; var planDict = {}; var planService = {}; @@ -353,8 +443,7 @@ quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'rest return; } - var getPlans = Restangular.one('plans'); - getPlans.get().then(function(data) { + ApiService.listPlans().then(function(data) { var i = 0; for(i = 0; i < data.plans.length; i++) { planDict[data.plans[i].stripeId] = data.plans[i]; @@ -412,13 +501,7 @@ quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'rest }; planService.getSubscription = function(orgname, success, failure) { - var url = planService.getSubscriptionUrl(orgname); - var getSubscription = Restangular.one(url); - getSubscription.get().then(success, failure); - }; - - planService.getSubscriptionUrl = function(orgname) { - return orgname ? getRestUrl('organization', orgname, 'plan') : 'user/plan'; + ApiService.getSubscription(orgname).then(success, failure); }; planService.setSubscription = function(orgname, planId, success, failure, opt_token) { @@ -430,9 +513,7 @@ quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'rest subscriptionDetails['token'] = opt_token.id; } - var url = planService.getSubscriptionUrl(orgname); - var createSubscriptionRequest = Restangular.one(url); - createSubscriptionRequest.customPUT(subscriptionDetails).then(function(resp) { + ApiService.updateSubscription(orgname, subscriptionDetails).then(function(resp) { success(resp); planService.getPlan(planId, function(plan) { for (var i = 0; i < listeners.length; ++i) { @@ -443,9 +524,7 @@ quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'rest }; planService.getCardInfo = function(orgname, callback) { - var url = orgname ? getRestUrl('organization', orgname, 'card') : 'user/card'; - var getCard = Restangular.one(url); - getCard.customGET().then(function(resp) { + ApiService.getCard(orgname).then(function(resp) { callback(resp.card); }, function() { callback({'is_valid': false}); @@ -492,12 +571,10 @@ quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'rest 'token': token.id }; - var url = orgname ? getRestUrl('organization', orgname, 'card') : 'user/card'; - var changeCardRequest = Restangular.one(url); - changeCardRequest.customPOST(cardInfo).then(callbacks['success'], function(resp) { - planService.handleCardError(resp); - callbacks['failure'](resp); - }); + ApiService.setCard(orgname, cardInfo).then(callbacks['success'], function(resp) { + planService.handleCardError(resp); + callbacks['failure'](resp); + }); }); }; @@ -739,10 +816,9 @@ quayApp.directive('userSetup', function () { 'signInStarted': '&signInStarted', 'signedIn': '&signedIn' }, - controller: function($scope, $location, $timeout, Restangular, KeyService, UserService) { + controller: function($scope, $location, $timeout, ApiService, KeyService, UserService) { $scope.sendRecovery = function() { - var signinPost = Restangular.one('recovery'); - signinPost.customPOST($scope.recovery).then(function() { + ApiService.requestRecoveryEmail($scope.recovery).then(function() { $scope.invalidEmail = false; $scope.sent = true; }, function(result) { @@ -772,7 +848,7 @@ quayApp.directive('signinForm', function () { 'signInStarted': '&signInStarted', 'signedIn': '&signedIn' }, - controller: function($scope, $location, $timeout, Restangular, KeyService, UserService) { + controller: function($scope, $location, $timeout, ApiService, KeyService, UserService) { $scope.showGithub = function() { $scope.markStarted(); @@ -799,8 +875,7 @@ quayApp.directive('signinForm', function () { $scope.signin = function() { $scope.markStarted(); - var signinPost = Restangular.one('signin'); - signinPost.customPOST($scope.user).then(function() { + ApiService.signinUser($scope.user).then(function() { $scope.needsEmailVerification = false; $scope.invalidCredentials = false; @@ -840,7 +915,7 @@ quayApp.directive('signupForm', function () { scope: { }, - controller: function($scope, $location, $timeout, Restangular, KeyService, UserService) { + controller: function($scope, $location, $timeout, ApiService, KeyService, UserService) { $('.form-signup').popover(); angulartics.waitForVendorApi(mixpanel, 500, function(loadedMixpanel) { @@ -857,8 +932,7 @@ quayApp.directive('signupForm', function () { $('.form-signup').popover('hide'); $scope.registering = true; - var newUserPost = Restangular.one('user/'); - newUserPost.customPOST($scope.newUser).then(function() { + ApiService.createNewUser($scope.newUser).then(function() { $scope.awaitingConfirmation = true; $scope.registering = false; @@ -911,7 +985,7 @@ quayApp.directive('dockerAuthDialog', function () { 'shown': '=shown', 'counter': '=counter' }, - controller: function($scope, $element, Restangular) { + controller: function($scope, $element) { $scope.isDownloadSupported = function() { try { return !!new Blob(); } catch(e){} return false; @@ -981,7 +1055,7 @@ quayApp.directive('billingInvoices', function () { 'user': '=user', 'visible': '=visible' }, - controller: function($scope, $element, $sce, Restangular) { + controller: function($scope, $element, $sce, ApiService) { $scope.loading = false; $scope.invoiceExpanded = {}; @@ -1000,13 +1074,7 @@ quayApp.directive('billingInvoices', function () { $scope.loading = true; - var url = getRestUrl('user/invoices'); - if ($scope.organization) { - url = getRestUrl('organization', $scope.organization.name, 'invoices'); - } - - var getInvoices = Restangular.one(url); - getInvoices.get().then(function(resp) { + ApiService.listInvoices($scope.organization).then(function(resp) { $scope.invoices = resp.invoices; $scope.loading = false; }); @@ -1036,7 +1104,7 @@ quayApp.directive('logsView', function () { 'repository': '=repository', 'performer': '=performer' }, - controller: function($scope, $element, $sce, Restangular) { + controller: function($scope, $element, $sce, Restangular, ApiService) { $scope.loading = true; $scope.logs = null; $scope.kindsAllowed = null; @@ -1152,6 +1220,8 @@ quayApp.directive('logsView', function () { $scope.loading = true; + // Note: We construct the URLs here manually because we also use it for the download + // path. var url = getRestUrl('user/logs'); if ($scope.organization) { url = getRestUrl('organization', $scope.organization.name, 'logs'); @@ -1255,7 +1325,7 @@ quayApp.directive('robotsManager', function () { 'organization': '=organization', 'user': '=user' }, - controller: function($scope, $element, Restangular) { + controller: function($scope, $element, ApiService) { $scope.ROBOT_PATTERN = ROBOT_PATTERN; $scope.robots = null; $scope.loading = false; @@ -1280,7 +1350,7 @@ quayApp.directive('robotsManager', function () { $scope.createRobot = function(name) { if (!name) { return; } - createRobotAccount(Restangular, !!$scope.organization, $scope.organization ? $scope.organization.name : '', name, + createRobotAccount(ApiService, !!$scope.organization, $scope.organization ? $scope.organization.name : '', name, function(created) { $scope.robots.push(created); }); @@ -1288,11 +1358,7 @@ quayApp.directive('robotsManager', function () { $scope.deleteRobot = function(info) { var shortName = $scope.getShortenedName(info.name); - var url = $scope.organization ? getRestUrl('organization', $scope.organization.name, 'robots', shortName) : - getRestUrl('user/robots', shortName); - - var deleteRobot = Restangular.one(url); - deleteRobot.customDELETE().then(function(resp) { + ApiService.deleteRobot($scope.organization, null, {'robot_shortname': shortName}).then(function(resp) { for (var i = 0; i < $scope.robots.length; ++i) { if ($scope.robots[i].name == info.name) { $scope.robots.splice(i, 1); @@ -1318,9 +1384,7 @@ quayApp.directive('robotsManager', function () { if ($scope.loading) { return; } $scope.loading = true; - var url = $scope.organization ? getRestUrl('organization', $scope.organization.name, 'robots') : 'user/robots'; - var getRobots = Restangular.one(url); - getRobots.customGET($scope.obj).then(function(resp) { + ApiService.getRobots($scope.organization).then(function(resp) { $scope.robots = resp.robots; $scope.loading = false; }); @@ -1553,7 +1617,7 @@ quayApp.directive('headerBar', function () { restrict: 'C', scope: { }, - controller: function($scope, $element, $location, UserService, PlanService, Restangular) { + controller: function($scope, $element, $location, UserService, PlanService, ApiService) { $scope.overPlan = false; var checkOverPlan = function() { @@ -1562,8 +1626,7 @@ quayApp.directive('headerBar', function () { return; } - var checkPrivate = Restangular.one('user/private'); - checkPrivate.customGET().then(function(resp) { + ApiService.getUserPrivateCount().then(function(resp) { $scope.overPlan = resp.privateCount > resp.reposAllowed; }); }; @@ -1575,10 +1638,9 @@ quayApp.directive('headerBar', function () { PlanService.registerListener(this, checkOverPlan); $scope.signout = function() { - var signoutPost = Restangular.one('signout'); - signoutPost.customPOST().then(function() { - UserService.load(); - $location.path('/'); + ApiService.logout().then(function() { + UserService.load(); + $location.path('/'); }); }; @@ -1609,7 +1671,7 @@ quayApp.directive('entitySearch', function () { 'includeTeams': '=includeTeams', 'isOrganization': '=isOrganization' }, - controller: function($scope, $element, Restangular, UserService) { + controller: function($scope, $element, Restangular, UserService, ApiService) { $scope.lazyLoading = true; $scope.isAdmin = false; @@ -1619,16 +1681,12 @@ quayApp.directive('entitySearch', function () { $scope.isAdmin = UserService.isNamespaceAdmin($scope.namespace); if ($scope.isOrganization && $scope.includeTeams) { - var url = getRestUrl('organization', $scope.namespace); - var getOrganization = Restangular.one(url); - getOrganization.customGET().then(function(resp) { + ApiService.getOrganization(null, {'orgname': $scope.namespace}).then(function(resp) { $scope.teams = resp.teams; }); } - var url = $scope.isOrganization ? getRestUrl('organization', $scope.namespace, 'robots') : 'user/robots'; - var getRobots = Restangular.one(url); - getRobots.customGET().then(function(resp) { + ApiService.getRobots($scope.isOrganization ? $scope.namespace : null).then(function(resp) { $scope.robots = resp.robots; $scope.lazyLoading = false; }, function() { @@ -1648,7 +1706,7 @@ quayApp.directive('entitySearch', function () { return; } - createOrganizationTeam(Restangular, $scope.namespace, teamname, function(created) { + createOrganizationTeam(ApiService, $scope.namespace, teamname, function(created) { $scope.setEntity(created.name, 'team', false); $scope.teams[teamname] = created; }); @@ -1667,7 +1725,7 @@ quayApp.directive('entitySearch', function () { return; } - createRobotAccount(Restangular, $scope.isOrganization, $scope.namespace, robotname, function(created) { + createRobotAccount(ApiService, $scope.isOrganization, $scope.namespace, robotname, function(created) { $scope.setEntity(created.name, 'user', true); $scope.robots.push(created); }); @@ -1793,7 +1851,7 @@ quayApp.directive('billingOptions', function () { 'user': '=user', 'organization': '=organization' }, - controller: function($scope, $element, PlanService, Restangular) { + controller: function($scope, $element, PlanService, ApiService) { $scope.invoice_email = false; $scope.currentCard = null; @@ -1863,9 +1921,7 @@ quayApp.directive('billingOptions', function () { var save = function() { $scope.working = true; - var url = $scope.organization ? getRestUrl('organization', $scope.organization.name) : 'user/'; - var conductSave = Restangular.one(url); - conductSave.customPUT($scope.obj).then(function(resp) { + ApiService.changeDetails($scope.organization, $scope.obj).then(function(resp) { $scope.working = false; }); }; @@ -1900,7 +1956,7 @@ quayApp.directive('planManager', function () { 'readyForPlan': '&readyForPlan', 'planChanged': '&planChanged' }, - controller: function($scope, $element, PlanService, Restangular) { + controller: function($scope, $element, PlanService, ApiService) { var hasSubscription = false; $scope.isPlanVisible = function(plan, subscribedPlan) { diff --git a/static/js/bootstrap.js b/static/js/bootstrap.js new file mode 100644 index 000000000..4293c6651 --- /dev/null +++ b/static/js/bootstrap.js @@ -0,0 +1,13 @@ + $.ajax({ + type: 'GET', + async: false, + url: '/api/discovery', + success: function(data) { + window.__endpoints = data.endpoints; + }, + error: function() { + setTimeout(function() { + $('#couldnotloadModal').modal({}); + }, 250); + } + }); \ No newline at end of file diff --git a/static/js/controllers.js b/static/js/controllers.js index 2054f3e84..f66f052dc 100644 --- a/static/js/controllers.js +++ b/static/js/controllers.js @@ -67,14 +67,15 @@ function RepoListCtrl($scope, Restangular, UserService, ApiService) { } var options = {'public': false, 'sort': true, 'namespace': namespace}; - $scope.user_repositories = ApiService.at('repository').withOptions(options).get(function(resp) { + + $scope.user_repositories = ApiService.listReposAsResource().withOptions(options).get(function(resp) { return resp.repositories; }); }; var loadPublicRepos = function() { var options = {'public': true, 'private': false, 'sort': true, 'limit': 10}; - $scope.public_repositories = ApiService.at('repository').withOptions(options).get(function(resp) { + $scope.public_repositories = ApiService.listReposAsResource().withOptions(options).get(function(resp) { return resp.repositories; }); }; @@ -118,7 +119,7 @@ function LandingCtrl($scope, UserService, ApiService) { } var options = {'limit': 4, 'public': false, 'sort': true, 'namespace': namespace }; - $scope.my_repositories = ApiService.at('repository').withOptions(options).get(function(resp) { + $scope.my_repositories = ApiService.listReposAsResource().withOptions(options).get(function(resp) { return resp.repositories; }); }; @@ -169,7 +170,8 @@ function RepoCtrl($scope, Restangular, ApiService, $routeParams, $rootScope, $lo }; $scope.loadImageChanges = function(image) { - $scope.currentImageChangeResource = ApiService.at('repository', namespace, name, 'image', image.id, 'changes').get(function(ci) { + var params = {'repository': namespace + '/' + name, 'image_id': image.id}; + $scope.currentImageChangeResource = ApiService.getImageChangesAsResource(params).get(function(ci) { $scope.currentImageChanges = ci; }); }; @@ -240,8 +242,9 @@ function RepoCtrl($scope, Restangular, ApiService, $routeParams, $rootScope, $lo }; var fetchRepository = function() { + var params = {'repository': namespace + '/' + name}; $rootScope.title = 'Loading Repository...'; - $scope.repository = ApiService.at('repository', namespace, name).get(function(repo) { + $scope.repository = ApiService.getRepoAsResource(params).get(function(repo) { // Set the repository object. $scope.repo = repo; @@ -283,6 +286,7 @@ function RepoCtrl($scope, Restangular, ApiService, $routeParams, $rootScope, $lo }; var getBuildInfo = function(repo) { + // Note: We use restangular manually here because we need to turn off the loading bar. var buildInfo = Restangular.one('repository/' + repo.namespace + '/' + repo.name + '/build/'); buildInfo.withHttpConfig({ 'ignoreLoadingBar': true @@ -312,7 +316,8 @@ function RepoCtrl($scope, Restangular, ApiService, $routeParams, $rootScope, $lo }; var listImages = function() { - $scope.imageHistory = ApiService.at('repository', namespace, name, 'image').get(function(resp) { + var params = {'repository': namespace + '/' + name}; + $scope.imageHistory = ApiService.listRepositoryImagesAsResource(params).get(function(resp) { // Dispose of any existing tree. if ($scope.tree) { $scope.tree.dispose(); @@ -352,11 +357,6 @@ function RepoCtrl($scope, Restangular, ApiService, $routeParams, $rootScope, $lo } function RepoAdminCtrl($scope, Restangular, ApiService, $routeParams, $rootScope) { - $('.info-icon').popover({ - 'trigger': 'hover', - 'html': true - }); - var namespace = $routeParams.namespace; var name = $routeParams.name; @@ -443,8 +443,8 @@ function RepoAdminCtrl($scope, Restangular, ApiService, $routeParams, $rootScope 'friendlyName': $scope.newToken.friendlyName }; - var permissionPost = Restangular.one('repository/' + namespace + '/' + name + '/tokens/'); - permissionPost.customPOST(friendlyName).then(function(newToken) { + var params = {'repository': namespace + '/' + name}; + ApiService.createToken(friendlyName, params).then(function(newToken) { $scope.newToken.friendlyName = ''; $scope.createTokenForm.$setPristine(); $scope.tokens[newToken.code] = newToken; @@ -452,8 +452,12 @@ function RepoAdminCtrl($scope, Restangular, ApiService, $routeParams, $rootScope }; $scope.deleteToken = function(tokenCode) { - var deleteAction = Restangular.one('repository/' + namespace + '/' + name + '/tokens/' + tokenCode); - deleteAction.customDELETE().then(function() { + var params = { + 'repository': namespace + '/' + name, + 'code': tokenCode + }; + + ApiService.deleteToken(null, params).then(function() { delete $scope.tokens[tokenCode]; }); }; @@ -463,8 +467,12 @@ function RepoAdminCtrl($scope, Restangular, ApiService, $routeParams, $rootScope 'role': newAccess }; - var deleteAction = Restangular.one('repository/' + namespace + '/' + name + '/tokens/' + tokenCode); - deleteAction.customPUT(role).then(function(updated) { + var params = { + 'repository': namespace + '/' + name, + 'code': tokenCode + }; + + ApiService.changeToken(role, params).then(function(updated) { $scope.tokens[updated.code] = updated; }); }; @@ -486,8 +494,12 @@ function RepoAdminCtrl($scope, Restangular, ApiService, $routeParams, $rootScope var visibility = { 'visibility': newAccess }; - var visibilityPost = Restangular.one('repository/' + namespace + '/' + name + '/changevisibility'); - visibilityPost.customPOST(visibility).then(function() { + + var params = { + 'repository': namespace + '/' + name + }; + + ApiService.changeRepoVisibility(visibility, params).then(function() { $scope.repo.is_public = newAccess == 'public'; }, function() { $('#cannotchangeModal').modal({}); @@ -501,8 +513,11 @@ function RepoAdminCtrl($scope, Restangular, ApiService, $routeParams, $rootScope $scope.deleteRepo = function() { $('#confirmdeleteModal').modal('hide'); - var deleteAction = Restangular.one('repository/' + namespace + '/' + name); - deleteAction.customDELETE().then(function() { + var params = { + 'repository': namespace + '/' + name + }; + + ApiService.deleteRepository(null, params).then(function() { $scope.repo = null; setTimeout(function() { @@ -514,8 +529,12 @@ function RepoAdminCtrl($scope, Restangular, ApiService, $routeParams, $rootScope }; $scope.loadWebhooks = function() { + var params = { + 'repository': namespace + '/' + name + }; + $scope.newWebhook = {}; - $scope.webhooksResource = ApiService.at('repository', namespace, name, 'webhook').get(function(resp) { + $scope.webhooksResource = ApiService.listWebhooksAsResource(params).get(function(resp) { $scope.webhooks = resp.webhooks; return $scope.webhooks; }); @@ -526,8 +545,11 @@ function RepoAdminCtrl($scope, Restangular, ApiService, $routeParams, $rootScope return; } - var newWebhook = Restangular.one('repository/' + namespace + '/' + name + '/webhook/'); - newWebhook.customPOST($scope.newWebhook).then(function(resp) { + var params = { + 'repository': namespace + '/' + name + }; + + ApiService.createWebhook($scope.newWebhook, params).then(function(resp) { $scope.webhooks.push(resp); $scope.newWebhook.url = ''; $scope.createWebhookForm.$setPristine(); @@ -535,15 +557,22 @@ function RepoAdminCtrl($scope, Restangular, ApiService, $routeParams, $rootScope }; $scope.deleteWebhook = function(webhook) { - var deleteWebhookReq = Restangular.one('repository/' + namespace + '/' + name + '/webhook/' + webhook.public_id); - deleteWebhookReq.customDELETE().then(function(resp) { + var params = { + 'repository': namespace + '/' + name, + 'public_id': webhook.public_id + }; + + ApiService.deleteWebhook(null, params).then(function(resp) { $scope.webhooks.splice($scope.webhooks.indexOf(webhook), 1); }); }; var fetchTokens = function() { - var tokensFetch = Restangular.one('repository/' + namespace + '/' + name + '/tokens/'); - tokensFetch.get().then(function(resp) { + var params = { + 'repository': namespace + '/' + name + }; + + ApiService.listRepoTokens(null, params).then(function(resp) { $scope.tokens = resp.tokens; }, function() { $scope.tokens = null; @@ -560,7 +589,11 @@ function RepoAdminCtrl($scope, Restangular, ApiService, $routeParams, $rootScope }; var fetchRepository = function() { - $scope.repository = ApiService.at('repository', namespace, name).get(function(repo) { + var params = { + 'repository': namespace + '/' + name + }; + + $scope.repository = ApiService.getRepoAsResource(params).get(function(repo) { $scope.repo = repo; $rootScope.title = 'Settings - ' + namespace + '/' + name; @@ -572,6 +605,11 @@ function RepoAdminCtrl($scope, Restangular, ApiService, $routeParams, $rootScope fetchPermissions('team'); fetchTokens(); + $('.info-icon').popover({ + 'trigger': 'hover', + 'html': true + }); + return $scope.repo; }); }; @@ -580,13 +618,14 @@ function RepoAdminCtrl($scope, Restangular, ApiService, $routeParams, $rootScope fetchRepository(); } -function UserAdminCtrl($scope, $timeout, $location, Restangular, PlanService, UserService, KeyService, $routeParams) { +function UserAdminCtrl($scope, $timeout, $location, ApiService, PlanService, UserService, KeyService, $routeParams) { if ($routeParams['migrate']) { $('#migrateTab').tab('show') } UserService.updateUserIn($scope, function(user) { $scope.askForPassword = user.askForPassword; + $scope.cuser = jQuery.extend({}, user); }); $scope.readyForPlan = function() { @@ -645,8 +684,7 @@ function UserAdminCtrl($scope, $timeout, $location, Restangular, PlanService, Us 'plan': $scope.org.plan.stripeId }; - var convertAccount = Restangular.one('user/convert'); - convertAccount.customPOST(data).then(function(resp) { + ApiService.convertUserToOrganization(data).then(function(resp) { UserService.load(); $location.path('/'); }, function(resp) { @@ -663,14 +701,14 @@ function UserAdminCtrl($scope, $timeout, $location, Restangular, PlanService, Us $('.form-change-pw').popover('hide'); $scope.updatingUser = true; $scope.changePasswordSuccess = false; - var changePasswordPost = Restangular.one('user/'); - changePasswordPost.customPUT($scope.user).then(function() { + + ApiService.changeUserDetails($scope.cuser).then(function() { $scope.updatingUser = false; $scope.changePasswordSuccess = true; // Reset the form - $scope.user.password = ''; - $scope.user.repeatPassword = ''; + $scope.cuser.password = ''; + $scope.cuser.repeatPassword = ''; $scope.changePasswordForm.$setPristine(); // Reload the user. @@ -686,7 +724,7 @@ function UserAdminCtrl($scope, $timeout, $location, Restangular, PlanService, Us }; } -function ImageViewCtrl($scope, $routeParams, $rootScope, $timeout, ApiService, Restangular) { +function ImageViewCtrl($scope, $routeParams, $rootScope, $timeout, ApiService) { var namespace = $routeParams.namespace; var name = $routeParams.name; var imageid = $routeParams.image; @@ -742,7 +780,12 @@ function ImageViewCtrl($scope, $routeParams, $rootScope, $timeout, ApiService, R }; var fetchImage = function() { - $scope.image = ApiService.at('repository', namespace, name, 'image', imageid).get(function(image) { + var params = { + 'repository': namespace + '/' + name, + 'image_id': imageid + }; + + $scope.image = ApiService.getImageAsResource(params).get(function(image) { $scope.repo = { 'name': name, 'namespace': namespace @@ -762,8 +805,12 @@ function ImageViewCtrl($scope, $routeParams, $rootScope, $timeout, ApiService, R }; var fetchChanges = function() { - var changesFetch = Restangular.one('repository/' + namespace + '/' + name + '/image/' + imageid + '/changes'); - changesFetch.get().then(function(changes) { + var params = { + 'repository': namespace + '/' + name, + 'image_id': imageid + }; + + ApiService.getImageChanges(null, params).then(function(changes) { var combinedChanges = []; var addCombinedChanges = function(c, kind) { for (var i = 0; i < c.length; ++i) { @@ -791,7 +838,7 @@ function V1Ctrl($scope, $location, UserService) { UserService.updateUserIn($scope); } -function NewRepoCtrl($scope, $location, $http, $timeout, UserService, Restangular, PlanService) { +function NewRepoCtrl($scope, $location, $http, $timeout, UserService, ApiService, PlanService) { UserService.updateUserIn($scope); $scope.repo = { @@ -822,8 +869,7 @@ function NewRepoCtrl($scope, $location, $http, $timeout, UserService, Restangula if (isUserNamespace) { // Load the user's subscription information in case they want to create a private // repository. - var checkPrivateAllowed = Restangular.one('user/private'); - checkPrivateAllowed.get().then(function(resp) { + ApiService.getUserPrivateCount().then(function(resp) { if (resp.privateCount + 1 > resp.reposAllowed) { PlanService.getMinimumPlan(resp.privateCount + 1, false, function(minimum) { $scope.planRequired = minimum; @@ -836,8 +882,7 @@ function NewRepoCtrl($scope, $location, $http, $timeout, UserService, Restangula $scope.checkingPlan = false; }); } else { - var checkPrivateAllowed = Restangular.one('organization/' + namespace + '/private'); - checkPrivateAllowed.get().then(function(resp) { + ApiService.getOrganizationPrivateAllowed(null, {'orgname': namespace}).then(function(resp) { $scope.planRequired = resp.privateAllowed ? null : {}; $scope.checkingPlan = false; }, function() { @@ -868,8 +913,7 @@ function NewRepoCtrl($scope, $location, $http, $timeout, UserService, Restangula 'description': repo.description }; - var createPost = Restangular.one('repository'); - createPost.customPOST(data).then(function(created) { + ApiService.createRepo(data).then(function(created) { $scope.creating = false; $scope.created = created; @@ -912,9 +956,12 @@ function NewRepoCtrl($scope, $location, $http, $timeout, UserService, Restangula 'file_id': fileId }; - var startBuildCall = Restangular.one('repository/' + repo.namespace + '/' + repo.name + '/build/'); - startBuildCall.customPOST(data).then(function(resp) { - $location.path('/repository/' + repo.namespace + '/' + repo.name); + var params = { + 'repository': repo.namespace + '/' + repo.name + }; + + ApiService.requestRepoBuild(data, params).then(function(resp) { + $location.path('/repository/' + params.repository); }, function() { $('#couldnotbuildModal').modal(); }); @@ -963,8 +1010,7 @@ function NewRepoCtrl($scope, $location, $http, $timeout, UserService, Restangula 'mimeType': mimeType }; - var getUploadUrl = Restangular.one('filedrop/'); - getUploadUrl.customPOST(data).then(function(resp) { + var getUploadUrl = ApiService.getFiledropUrl(data).then(function(resp) { conductUpload(repo, file, resp.url, resp.file_id, mimeType); }, function() { $('#couldnotbuildModal').modal(); @@ -992,7 +1038,7 @@ function NewRepoCtrl($scope, $location, $http, $timeout, UserService, Restangula }; } -function OrgViewCtrl($rootScope, $scope, Restangular, ApiService, $routeParams) { +function OrgViewCtrl($rootScope, $scope, ApiService, $routeParams) { var orgname = $routeParams.orgname; $scope.TEAM_PATTERN = TEAM_PATTERN; @@ -1008,10 +1054,14 @@ function OrgViewCtrl($rootScope, $scope, Restangular, ApiService, $routeParams) var previousRole = $scope.organization.teams[teamname].role; $scope.organization.teams[teamname].role = role; - var updateTeam = Restangular.one(getRestUrl('organization', orgname, 'team', teamname)); + var params = { + 'orgname': orgname, + 'teamname': teamname + }; + var data = $scope.organization.teams[teamname]; - updateTeam.customPUT(data).then(function(resp) { + ApiService.updateOrganizationTeam(data, params).then(function(resp) { }, function(resp) { $scope.organization.teams[teamname].role = previousRole; $scope.roleError = resp.data || ''; @@ -1032,7 +1082,7 @@ function OrgViewCtrl($rootScope, $scope, Restangular, ApiService, $routeParams) return; } - createOrganizationTeam(Restangular, orgname, teamname, function(created) { + createOrganizationTeam(ApiService, orgname, teamname, function(created) { $scope.organization.teams[teamname] = created; }); }; @@ -1047,8 +1097,12 @@ function OrgViewCtrl($rootScope, $scope, Restangular, ApiService, $routeParams) if (!$scope.currentDeleteTeam) { return; } var teamname = $scope.currentDeleteTeam; - var deleteAction = Restangular.one(getRestUrl('organization', orgname, 'team', teamname)); - deleteAction.customDELETE().then(function() { + var params = { + 'orgname': orgname, + 'teamname': teamname + }; + + ApiService.deleteOrganizationTeam(null, params).then(function() { delete $scope.organization.teams[teamname]; $scope.currentDeleteTeam = null; }, function() { @@ -1058,7 +1112,7 @@ function OrgViewCtrl($rootScope, $scope, Restangular, ApiService, $routeParams) }; var loadOrganization = function() { - $scope.orgResource = ApiService.at('organization', orgname).get(function(org) { + $scope.orgResource = ApiService.getOrganizationAsResource({'orgname': orgname}).get(function(org) { $scope.organization = org; $rootScope.title = orgname; $rootScope.description = 'Viewing organization ' + orgname; @@ -1109,9 +1163,12 @@ function OrgAdminCtrl($rootScope, $scope, Restangular, $routeParams, UserService $scope.loadMembers = function() { if ($scope.membersFound) { return; } $scope.membersLoading = true; - - var getMembers = Restangular.one(getRestUrl('organization', orgname, 'members')); - getMembers.get().then(function(resp) { + + var params = { + 'orgname': orgname + }; + + ApiService.getOrganizationMembers(null, params).then(function(resp) { var membersArray = []; for (var key in resp.members) { if (resp.members.hasOwnProperty(key)) { @@ -1125,7 +1182,7 @@ function OrgAdminCtrl($rootScope, $scope, Restangular, $routeParams, UserService }; var loadOrganization = function() { - $scope.orgResource = ApiService.at('organization', orgname).get(function(org) { + $scope.orgResource = ApiService.getOrganizationAsResource({'orgname': orgname}).get(function(org) { if (org && org.is_admin) { $scope.organization = org; $rootScope.title = orgname + ' (Admin)'; @@ -1139,11 +1196,6 @@ function OrgAdminCtrl($rootScope, $scope, Restangular, $routeParams, UserService } function TeamViewCtrl($rootScope, $scope, Restangular, ApiService, $routeParams) { - $('.info-icon').popover({ - 'trigger': 'hover', - 'html': true - }); - var teamname = $routeParams.teamname; var orgname = $routeParams.orgname; @@ -1155,8 +1207,13 @@ function TeamViewCtrl($rootScope, $scope, Restangular, ApiService, $routeParams) $scope.addNewMember = function(member) { if ($scope.members[member.name]) { return; } - var addMember = Restangular.one(getRestUrl('organization', $scope.orgname, 'team', teamname, 'members', member.name)); - addMember.customPOST().then(function(resp) { + var params = { + 'orgname': orgname, + 'teamname': teamname, + 'membername': member.name + }; + + ApiService.updateOrganizationTeamMember(null, params).then(function(resp) { $scope.members[member.name] = resp; }, function() { $('#cannotChangeMembersModal').modal({}); @@ -1164,8 +1221,13 @@ function TeamViewCtrl($rootScope, $scope, Restangular, ApiService, $routeParams) }; $scope.removeMember = function(username) { - var removeMember = Restangular.one(getRestUrl('organization', $scope.orgname, 'team', teamname, 'members', username)); - removeMember.customDELETE().then(function(resp) { + var params = { + 'orgname': orgname, + 'teamname': teamname, + 'membername': username + }; + + ApiService.deleteOrganizationTeamMember(null, params).then(function(resp) { delete $scope.members[username]; }, function() { $('#cannotChangeMembersModal').modal({}); @@ -1175,16 +1237,20 @@ function TeamViewCtrl($rootScope, $scope, Restangular, ApiService, $routeParams) $scope.updateForDescription = function(content) { $scope.organization.teams[teamname].description = content; - var updateTeam = Restangular.one(getRestUrl('organization', $scope.orgname, 'team', teamname)); - var data = $scope.organization.teams[teamname]; - updateTeam.customPUT(data).then(function(resp) { + var params = { + 'orgname': orgname, + 'teamname': teamname + }; + + var teaminfo = $scope.organization.teams[teamname]; + ApiService.updateOrganizationTeam(teaminfo, params).then(function(resp) { }, function() { $('#cannotChangeTeamModal').modal({}); }); }; var loadOrganization = function() { - $scope.orgResource = ApiService.at('organization', orgname).get(function(org) { + $scope.orgResource = ApiService.getOrganizationAsResource({'orgname': orgname}).get(function(org) { $scope.organization = org; $scope.team = $scope.organization.teams[teamname]; $rootScope.title = teamname + ' (' + $scope.orgname + ')'; @@ -1195,11 +1261,22 @@ function TeamViewCtrl($rootScope, $scope, Restangular, ApiService, $routeParams) }; var loadMembers = function() { - $scope.membersResource = ApiService.at('organization', $scope.orgname, 'team', teamname, 'members').get(function(resp) { + var params = { + 'orgname': orgname, + 'teamname': teamname + }; + + $scope.membersResource = ApiService.getOrganizationTeamMembersAsResource(params).get(function(resp) { $scope.members = resp.members; $scope.canEditMembers = resp.can_edit; + + $('.info-icon').popover({ + 'trigger': 'hover', + 'html': true + }); + return resp.members; - }); + }); }; // Load the organization. @@ -1208,11 +1285,10 @@ function TeamViewCtrl($rootScope, $scope, Restangular, ApiService, $routeParams) function OrgsCtrl($scope, UserService) { UserService.updateUserIn($scope); - browserchrome.update(); } -function NewOrgCtrl($scope, $routeParams, $timeout, $location, UserService, PlanService, Restangular) { +function NewOrgCtrl($scope, $routeParams, $timeout, $location, UserService, PlanService, ApiService) { UserService.updateUserIn($scope); var requested = $routeParams['plan']; @@ -1252,8 +1328,7 @@ function NewOrgCtrl($scope, $routeParams, $timeout, $location, UserService, Plan 'email': org.email }; - var createPost = Restangular.one('organization/'); - createPost.customPOST(data).then(function(created) { + ApiService.createOrganization(data).then(function(created) { $scope.created = created; // Reset the organizations list. @@ -1300,14 +1375,19 @@ function OrgMemberLogsCtrl($scope, $routeParams, $rootScope, $timeout, Restangul $scope.ready = false; var loadOrganization = function() { - $scope.orgResource = ApiService.at('organization', orgname).get(function(org) { + $scope.orgResource = ApiService.getOrganizationAsResource({'orgname': orgname}).get(function(org) { $scope.organization = org; return org; }); }; var loadMemberInfo = function() { - $scope.memberResource = ApiService.at('organization', $scope.orgname, 'members', membername).get(function(resp) { + var params = { + 'orgname': orgname, + 'membername': membername + }; + + $scope.memberResource = ApiService.getOrganizationMemberAsResource(params).get(function(resp) { $scope.memberInfo = resp.member; $rootScope.title = 'Logs for ' + $scope.memberInfo.username + ' (' + $scope.orgname + ')'; diff --git a/static/partials/user-admin.html b/static/partials/user-admin.html index 1b05ac382..1e5f0ab82 100644 --- a/static/partials/user-admin.html +++ b/static/partials/user-admin.html @@ -60,9 +60,9 @@
- - + +
diff --git a/templates/base.html b/templates/base.html index 8940e9d33..af71c9bde 100644 --- a/templates/base.html +++ b/templates/base.html @@ -65,6 +65,10 @@ {% endblock %} + + @@ -122,6 +126,22 @@ var isProd = document.location.hostname === 'quay.io';
+ + + {% if request.host == 'quay.io' %}