diff --git a/external_libraries.py b/external_libraries.py
index f2f9c3832..ef1a9d03f 100644
--- a/external_libraries.py
+++ b/external_libraries.py
@@ -7,7 +7,7 @@ LOCAL_DIRECTORY = '/static/ldn/'
EXTERNAL_JS = [
'code.jquery.com/jquery.js',
'netdna.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js',
- 'ajax.googleapis.com/ajax/libs/angularjs/1.5.3/angular.min.js',
+ 'ajax.googleapis.com/ajax/libs/angularjs/1.5.3/angular.js',
'ajax.googleapis.com/ajax/libs/angularjs/1.5.3/angular-route.min.js',
'ajax.googleapis.com/ajax/libs/angularjs/1.5.3/angular-sanitize.min.js',
'ajax.googleapis.com/ajax/libs/angularjs/1.5.3/angular-animate.min.js',
diff --git a/package.json b/package.json
index 7a471e930..e26efe744 100644
--- a/package.json
+++ b/package.json
@@ -41,6 +41,7 @@
"@types/react": "0.14.39",
"@types/react-dom": "0.14.17",
"angular-mocks": "^1.5.3",
+ "angular-ts-decorators": "0.0.19",
"css-loader": "0.25.0",
"jasmine-core": "^2.5.2",
"jasmine-ts": "0.0.3",
diff --git a/static/directives/manage-trigger-githost.html b/static/directives/manage-trigger-githost.html
index 4912b5c2f..4645a2f2f 100644
--- a/static/directives/manage-trigger-githost.html
+++ b/static/directives/manage-trigger-githost.html
@@ -184,10 +184,10 @@
Examples: heads/master, tags/tagname, heads/.+
-
+
|
diff --git a/static/directives/regex-match-view.html b/static/directives/regex-match-view.html
deleted file mode 100644
index da9316838..000000000
--- a/static/directives/regex-match-view.html
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
- Invalid Regular Expression!
-
-
-
-
- Matching: |
-
-
- |
-
-
- Not Matching: |
-
-
- |
-
-
-
-
\ No newline at end of file
diff --git a/static/js/constants/name-patterns.constant.ts b/static/js/constants/name-patterns.constant.ts
index 422887c3e..942028ee1 100644
--- a/static/js/constants/name-patterns.constant.ts
+++ b/static/js/constants/name-patterns.constant.ts
@@ -1,7 +1,7 @@
/**
* Regex patterns to for validating account names.
*/
-export default {
+export const NAME_PATTERNS: any = {
TEAM_PATTERN: '^[a-z][a-z0-9]+$',
ROBOT_PATTERN: '^[a-z][a-z0-9_]{1,254}$',
USERNAME_PATTERN: '^(?=.{2,255}$)([a-z0-9]+(?:[._-][a-z0-9]+)*)$',
diff --git a/static/js/constants/pages.constant.ts b/static/js/constants/pages.constant.ts
deleted file mode 100644
index 6d0452b8c..000000000
--- a/static/js/constants/pages.constant.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-/**
- * Manages the creation and retrieval of pages (route + controller)
- * TODO: Convert to class/Angular service
- */
-export default {
- _pages: {},
-
- /**
- * Create a page.
- * @param pageName The name of the page.
- * @param templateName The file name of the template.
- * @param controller Controller for the page.
- * @param flags Additional flags passed to route provider.
- * @param profiles Available profiles.
- */
- create: function(pageName: string, templateName: string, controller?: Object, flags = {}, profiles = ['old-layout', 'layout']) {
- for (var i = 0; i < profiles.length; ++i) {
- this._pages[profiles[i] + ':' + pageName] = {
- 'name': pageName,
- 'controller': controller,
- 'templateName': templateName,
- 'flags': flags
- };
- }
- },
-
- /**
- * Retrieve a registered page.
- * @param pageName The name of the page.
- * @param profiles Available profiles to search.
- */
- get: function(pageName: string, profiles: any[]) {
- for (var i = 0; i < profiles.length; ++i) {
- var current = profiles[i];
- var key = current.id + ':' + pageName;
- var page = this._pages[key];
- if (page) {
- return [current, page];
- }
- }
-
- return null;
- }
-};
diff --git a/static/js/directives/ui/regex-match-view.js b/static/js/directives/ui/regex-match-view.js
deleted file mode 100644
index 6bd2380a5..000000000
--- a/static/js/directives/ui/regex-match-view.js
+++ /dev/null
@@ -1,36 +0,0 @@
-/**
- * An element which displays the matches and non-matches for a regular expression against a set of
- * items.
- */
-angular.module('quay').directive('regexMatchView', function () {
- var directiveDefinitionObject = {
- priority: 0,
- templateUrl: '/static/directives/regex-match-view.html',
- replace: false,
- transclude: true,
- restrict: 'C',
- scope: {
- 'regex': '=regex',
- 'items': '=items'
- },
- controller: function($scope, $element) {
- $scope.filterMatches = function(regexstr, items, shouldMatch) {
- regexstr = regexstr || '.+';
-
- try {
- var regex = new RegExp(regexstr);
- } catch (ex) {
- return null;
- }
-
- return items.filter(function(item) {
- var value = item['value'];
- var m = value.match(regex);
- var matches = !!(m && m[0].length == value.length);
- return matches == shouldMatch;
- });
- };
- }
- };
- return directiveDefinitionObject;
-});
\ No newline at end of file
diff --git a/static/js/directives/ui/regex-match-view/regex-match-view.component.spec.ts b/static/js/directives/ui/regex-match-view/regex-match-view.component.spec.ts
new file mode 100644
index 000000000..86e18b99c
--- /dev/null
+++ b/static/js/directives/ui/regex-match-view/regex-match-view.component.spec.ts
@@ -0,0 +1,14 @@
+import { RegexMatchViewComponent } from './regex-match-view.component';
+
+
+describe("RegexMatchViewComponent", () => {
+ var component: RegexMatchViewComponent;
+
+ beforeEach(() => {
+ component = new RegexMatchViewComponent();
+ });
+
+ describe("filterMatches", () => {
+
+ });
+});
diff --git a/static/js/directives/ui/regex-match-view/regex-match-view.component.ts b/static/js/directives/ui/regex-match-view/regex-match-view.component.ts
new file mode 100644
index 000000000..2c987906c
--- /dev/null
+++ b/static/js/directives/ui/regex-match-view/regex-match-view.component.ts
@@ -0,0 +1,67 @@
+import { Input, Component } from 'angular-ts-decorators';
+
+
+/**
+ * An element which displays the matches and non-matches for a regular expression against a set of
+ * items.
+ */
+@Component({
+ selector: 'regexMatchView',
+ template: `
+
+
+ Invalid Regular Expression!
+
+
+
+
+ Matching: |
+
+
+ |
+
+
+ Not Matching: |
+
+
+ |
+
+
+
+
+ `
+})
+export class RegexMatchViewComponent implements ng.IComponentController {
+
+ @Input('=') private regex: string;
+ @Input('=') private items: any[];
+
+ constructor() {
+
+ }
+
+ public filterMatches(regexstr: string, items: any[], shouldMatch: boolean): any[] {
+ regexstr = regexstr || '.+';
+
+ try {
+ var regex = new RegExp(regexstr);
+ } catch (ex) {
+ return null;
+ }
+
+ return items.filter(function(item) {
+ var value = item['value'];
+ var m = value.match(regex);
+ var matches: boolean = !!(m && m[0].length == value.length);
+ return matches == shouldMatch;
+ });
+ }
+}
diff --git a/static/js/quay-pages.module.ts b/static/js/quay-pages.module.ts
index 871cb78f0..23ecc26a0 100644
--- a/static/js/quay-pages.module.ts
+++ b/static/js/quay-pages.module.ts
@@ -1,12 +1,28 @@
import * as angular from 'angular';
import { rpHeaderDirective, rpBodyDirective, rpSidebarDirective } from './directives/components/pages/repo-page/main';
-import pages from './constants/pages.constant';
+import { PageServiceImpl } from './services/page/page.service.impl';
+import { NgModule } from 'angular-ts-decorators';
-export default angular
- .module('quayPages', [])
- .constant('pages', pages)
+/**
+ * Module containing registered application page/view components.
+ */
+@NgModule({
+ imports: [],
+ declarations: [],
+ providers: [
+ PageServiceImpl,
+ ]
+})
+export class quayPages {
+
+}
+
+
+// TODO: Move component registration to @NgModule and remove this.
+angular
+ .module(quayPages.name)
+ .constant('pages', new PageServiceImpl())
.directive('rpHeader', rpHeaderDirective)
.directive('rpSidebar', rpSidebarDirective)
- .directive('rpBody', rpBodyDirective)
- .name;
+ .directive('rpBody', rpBodyDirective);
diff --git a/static/js/quay-routes.module.ts b/static/js/quay-routes.module.ts
new file mode 100644
index 000000000..13715a264
--- /dev/null
+++ b/static/js/quay-routes.module.ts
@@ -0,0 +1,146 @@
+import { RouteBuilderImpl } from './services/route-builder/route-builder.service.impl';
+import { RouteBuilder } from './services/route-builder/route-builder.service';
+import { PageService } from './services/page/page.service';
+import * as ng from '@types/angular';
+import { NgModule } from 'angular-ts-decorators';
+import { INJECTED_FEATURES } from './constants/injected-values.constant';
+import { quayPages } from './quay-pages.module';
+
+
+/**
+ * Module containing client-side routing configuration.
+ */
+@NgModule({
+ imports: [
+ quayPages,
+ 'ngRoute',
+ ],
+ declarations: [],
+ providers: [],
+})
+export class QuayRoutes {
+
+ public config($routeProvider: ng.route.IRouteProvider,
+ $locationProvider: ng.ILocationProvider,
+ pages: PageService): void {
+ $locationProvider.html5Mode(true);
+
+ // WARNING WARNING WARNING
+ // If you add a route here, you must add a corresponding route in thr endpoints/web.py
+ // index rule to make sure that deep links directly deep into the app continue to work.
+ // WARNING WARNING WARNING
+
+ var routeBuilder: RouteBuilder = new RouteBuilderImpl($routeProvider, pages.$get());
+
+ if (INJECTED_FEATURES.SUPER_USERS) {
+ // QE Management
+ routeBuilder.route('/superuser/', 'superuser')
+ // QE Setup
+ .route('/setup/', 'setup');
+ }
+
+ routeBuilder
+ // Repository View
+ .route('/repository/:namespace/:name', 'repo-view')
+ .route('/repository/:namespace/:name/tag/:tag', 'repo-view')
+
+ // Image View
+ .route('/repository/:namespace/:name/image/:image', 'image-view')
+
+ // Repo Build View
+ .route('/repository/:namespace/:name/build/:buildid', 'build-view')
+
+ // Repo Trigger View
+ .route('/repository/:namespace/:name/trigger/:triggerid', 'trigger-setup')
+
+ // Create repository notification
+ .route('/repository/:namespace/:name/create-notification', 'create-repository-notification')
+
+ // Repo List
+ .route('/repository/', 'repo-list')
+
+ // Organizations
+ .route('/organizations/', 'organizations')
+
+ // New Organization
+ .route('/organizations/new/', 'new-organization')
+
+ // View Organization
+ .route('/organization/:orgname', 'org-view')
+
+ // View Organization Team
+ .route('/organization/:orgname/teams/:teamname', 'team-view')
+
+ // Organization View Application
+ .route('/organization/:orgname/application/:clientid', 'manage-application')
+
+ // View Organization Billing
+ .route('/organization/:orgname/billing', 'billing')
+
+ // View Organization Billing Invoices
+ .route('/organization/:orgname/billing/invoices', 'invoices')
+
+ // View User
+ .route('/user/:username', 'user-view')
+
+ // View User Billing
+ .route('/user/:username/billing', 'billing')
+
+ // View User Billing Invoices
+ .route('/user/:username/billing/invoices', 'invoices')
+
+ // Sign In
+ .route('/signin/', 'signin')
+
+ // New Repository
+ .route('/new/', 'new-repo')
+
+ // Plans
+ .route('/plans/', 'plans')
+
+ // Tutorial
+ .route('/tutorial/', 'tutorial')
+
+ // Contact
+ .route('/contact/', 'contact')
+
+ // About
+ .route('/about/', 'about')
+
+ // Security
+ .route('/security/', 'security')
+
+ // TOS
+ .route('/tos', 'tos')
+
+ // Privacy
+ .route('/privacy', 'privacy')
+
+ // Change username
+ .route('/updateuser', 'update-user')
+
+ // Landing Page
+ .route('/', 'landing')
+
+ // Tour
+ .route('/tour/', 'tour')
+ .route('/tour/features', 'tour')
+ .route('/tour/organizations', 'tour')
+ .route('/tour/enterprise', 'tour')
+
+ // Confirm Invite
+ .route('/confirminvite', 'confirm-invite')
+
+ // Enterprise marketing page
+ .route('/enterprise', 'enterprise')
+
+ // Public Repo Experiments
+ .route('/__exp/publicRepo', 'public-repo-exp')
+
+ // 404/403
+ .route('/:catchall', 'error-view')
+ .route('/:catch/:all', 'error-view')
+ .route('/:catch/:all/:things', 'error-view')
+ .route('/:catch/:all/:things/:here', 'error-view');
+ }
+}
diff --git a/static/js/quay.config.ts b/static/js/quay.config.ts
deleted file mode 100644
index af2a36af9..000000000
--- a/static/js/quay.config.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-import * as Raven from 'raven-js';
-
-
-quayConfig.$inject = [
- '$provide',
- '$injector',
- 'INJECTED_CONFIG',
- 'cfpLoadingBarProvider',
- '$tooltipProvider',
- '$compileProvider',
- 'RestangularProvider',
-];
-
-export function quayConfig(
- $provide: ng.auto.IProvideService,
- $injector: ng.auto.IInjectorService,
- INJECTED_CONFIG: any,
- cfpLoadingBarProvider: any,
- $tooltipProvider: any,
- $compileProvider: ng.ICompileProvider,
- RestangularProvider: any) {
- cfpLoadingBarProvider.includeSpinner = false;
-
- // decorate the tooltip getter
- var tooltipFactory: any = $tooltipProvider.$get[$tooltipProvider.$get.length - 1];
- $tooltipProvider.$get[$tooltipProvider.$get.length - 1] = function($window: ng.IWindowService) {
- if ('ontouchstart' in $window) {
- var existing: any = tooltipFactory.apply(this, arguments);
- return function(element) {
- // Note: We only disable bs-tooltip's themselves. $tooltip is used for other things
- // (such as the datepicker), so we need to be specific when canceling it.
- if (element !== undefined && element.attr('bs-tooltip') == null) {
- return existing.apply(this, arguments);
- }
- };
- }
-
- return tooltipFactory.apply(this, arguments);
- };
-
- if (!INJECTED_CONFIG['DEBUG']) {
- $compileProvider.debugInfoEnabled(false);
- }
-
- // Configure compile provider to add additional URL prefixes to the sanitization list. We use
- // these on the Contact page.
- $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|tel|irc):/);
-
- // Configure the API provider.
- RestangularProvider.setBaseUrl('/api/v1/');
-
- // Configure analytics.
- if (INJECTED_CONFIG && INJECTED_CONFIG.MIXPANEL_KEY) {
- let $analyticsProvider: any = $injector.get('$analyticsProvider');
- $analyticsProvider.virtualPageviews(true);
- }
-
- // Configure sentry.
- if (INJECTED_CONFIG && INJECTED_CONFIG.SENTRY_PUBLIC_DSN) {
- $provide.decorator("$exceptionHandler", function($delegate) {
- return function(ex, cause) {
- $delegate(ex, cause);
- Raven.captureException(ex, {extra: {cause: cause}});
- };
- });
- }
-}
diff --git a/static/js/quay.module.ts b/static/js/quay.module.ts
index c149f1ba3..6e37c162f 100644
--- a/static/js/quay.module.ts
+++ b/static/js/quay.module.ts
@@ -1,16 +1,15 @@
-import * as angular from 'angular';
-import { quayConfig } from './quay.config';
-import quayPages from './quay-pages.module';
-import quayRun from './quay.run';
-import { ViewArrayImpl } from './services/view-array/view-array.impl';
-import NAME_PATTERNS from './constants/name-patterns.constant';
-import { routeConfig } from './quay.routes';
-import { INJECTED_CONFIG, INJECTED_FEATURES, INJECTED_ENDPOINTS } from './constants/injected-values.constant';
+import * as angular from "angular";
+import * as Raven from "raven-js";
+import { ViewArrayImpl } from "./services/view-array/view-array.impl";
+import { NAME_PATTERNS } from "./constants/name-patterns.constant";
+import { INJECTED_CONFIG, INJECTED_FEATURES, INJECTED_ENDPOINTS } from "./constants/injected-values.constant";
+import { RegexMatchViewComponent } from "./directives/ui/regex-match-view/regex-match-view.component";
+import { NgModule } from "angular-ts-decorators";
+import { QuayRoutes } from "./quay-routes.module";
-var quayDependencies: string[] = [
- quayPages,
- 'ngRoute',
+var quayDependencies: any[] = [
+ QuayRoutes,
'chieffancypants.loadingBar',
'cfp.hotkeys',
'angular-tour',
@@ -46,14 +45,198 @@ if (INJECTED_CONFIG && INJECTED_CONFIG.RECAPTCHA_SITE_KEY) {
quayDependencies.push('vcRecaptcha');
}
-export default angular
- .module('quay', quayDependencies)
- .config(quayConfig)
- .config(routeConfig)
+
+/**
+ * Main application module.
+ */
+@NgModule({
+ imports: quayDependencies,
+ declarations: [
+ RegexMatchViewComponent,
+ ],
+ providers: [
+ ViewArrayImpl,
+ ],
+})
+export class quay {
+
+ public config($provide: ng.auto.IProvideService,
+ $injector: ng.auto.IInjectorService,
+ INJECTED_CONFIG: any,
+ cfpLoadingBarProvider: any,
+ $tooltipProvider: any,
+ $compileProvider: ng.ICompileProvider,
+ RestangularProvider: any): void {
+ cfpLoadingBarProvider.includeSpinner = false;
+
+ // decorate the tooltip getter
+ var tooltipFactory: any = $tooltipProvider.$get[$tooltipProvider.$get.length - 1];
+ $tooltipProvider.$get[$tooltipProvider.$get.length - 1] = function($window: ng.IWindowService) {
+ if ('ontouchstart' in $window) {
+ var existing: any = tooltipFactory.apply(this, arguments);
+ return function(element) {
+ // Note: We only disable bs-tooltip's themselves. $tooltip is used for other things
+ // (such as the datepicker), so we need to be specific when canceling it.
+ if (element !== undefined && element.attr('bs-tooltip') == null) {
+ return existing.apply(this, arguments);
+ }
+ };
+ }
+
+ return tooltipFactory.apply(this, arguments);
+ };
+
+ if (!INJECTED_CONFIG['DEBUG']) {
+ $compileProvider.debugInfoEnabled(false);
+ }
+
+ // Configure compile provider to add additional URL prefixes to the sanitization list. We use
+ // these on the Contact page.
+ $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|tel|irc):/);
+
+ // Configure the API provider.
+ RestangularProvider.setBaseUrl('/api/v1/');
+
+ // Configure analytics.
+ if (INJECTED_CONFIG && INJECTED_CONFIG.MIXPANEL_KEY) {
+ let $analyticsProvider: any = $injector.get('$analyticsProvider');
+ $analyticsProvider.virtualPageviews(true);
+ }
+
+ // Configure sentry.
+ if (INJECTED_CONFIG && INJECTED_CONFIG.SENTRY_PUBLIC_DSN) {
+ $provide.decorator("$exceptionHandler", function($delegate) {
+ return function(ex, cause) {
+ $delegate(ex, cause);
+ Raven.captureException(ex, {extra: {cause: cause}});
+ };
+ });
+ }
+ }
+
+ public run($rootScope: QuayRunScope,
+ Restangular: any,
+ PlanService: any,
+ $http: ng.IHttpService,
+ CookieService: any,
+ Features: any,
+ $anchorScroll: ng.IAnchorScrollService,
+ MetaService: any,
+ INJECTED_CONFIG: any): void {
+ var defaultTitle = INJECTED_CONFIG['REGISTRY_TITLE'] || 'Quay Container Registry';
+
+ // Handle session security.
+ Restangular.setDefaultRequestParams(['post', 'put', 'remove', 'delete'],
+ {'_csrf_token': (window).__token || ''});
+
+ // Handle session expiration.
+ Restangular.setErrorInterceptor(function(response) {
+ if (response !== undefined && response.status == 503) {
+ ($('#cannotContactService')).modal({});
+ return false;
+ }
+
+ if (response !== undefined && response.status == 500) {
+ window.location.href = '/500';
+ return false;
+ }
+
+ if (response !== undefined && !response.data) {
+ return true;
+ }
+
+ var invalid_token = response.data['title'] == 'invalid_token' || response.data['error_type'] == 'invalid_token';
+ if (response !== undefined && response.status == 401 &&
+ invalid_token &&
+ response.data['session_required'] !== false) {
+ ($('#sessionexpiredModal')).modal({});
+ return false;
+ }
+
+ return true;
+ });
+
+ // Check if we need to redirect based on a previously chosen plan.
+ var result = PlanService.handleNotedPlan();
+
+ // Check to see if we need to show a redirection page.
+ var redirectUrl = CookieService.get('quay.redirectAfterLoad');
+ CookieService.clear('quay.redirectAfterLoad');
+
+ if (!result && redirectUrl && redirectUrl.indexOf((window).location.href) == 0) {
+ (window).location = redirectUrl;
+ return;
+ }
+
+ $rootScope.$watch('description', function(description: string) {
+ if (!description) {
+ description = `Hosted private docker repositories. Includes full user management and history.
+ Free for public repositories.`;
+ }
+
+ // Note: We set the content of the description tag manually here rather than using Angular binding
+ // because we need the tag to have a default description that is not of the form "{{ description }}",
+ // we read by tools that do not properly invoke the Angular code.
+ $('#descriptionTag').attr('content', description);
+ });
+
+ // Listen for scope changes and update the title and description accordingly.
+ $rootScope.$watch(function() {
+ var title = MetaService.getTitle($rootScope.currentPage) || defaultTitle;
+ $rootScope.title = title;
+
+ var description = MetaService.getDescription($rootScope.currentPage) || '';
+ if ($rootScope.description != description) {
+ $rootScope.description = description;
+ }
+ });
+
+ $rootScope.$on('$routeChangeSuccess', function (event, current, previous) {
+ $rootScope.current = current.$$route;
+ $rootScope.currentPage = current;
+ $rootScope.pageClass = '';
+
+ if (!current.$$route) { return; }
+
+ var pageClass = current.$$route.pageClass || '';
+ if (typeof pageClass != 'string') {
+ pageClass = pageClass(Features);
+ }
+
+ $rootScope.pageClass = pageClass;
+ $rootScope.newLayout = !!current.$$route.newLayout;
+ $rootScope.fixFooter = !!current.$$route.fixFooter;
+
+ $anchorScroll();
+ });
+
+ var initallyChecked: boolean = false;
+ (window).__isLoading = function() {
+ if (!initallyChecked) {
+ initallyChecked = true;
+ return true;
+ }
+ return $http.pendingRequests.length > 0;
+ };
+ }
+}
+
+
+interface QuayRunScope extends ng.IRootScopeService {
+ currentPage: any;
+ current: any;
+ title: any;
+ description: string,
+ pageClass: any;
+ newLayout: any;
+ fixFooter: any;
+}
+
+
+// TODO: Move component registration to @NgModule and remove this.
+angular
+ .module(quay.name)
.constant('NAME_PATTERNS', NAME_PATTERNS)
.constant('INJECTED_CONFIG', INJECTED_CONFIG)
.constant('INJECTED_FEATURES', INJECTED_FEATURES)
- .constant('INJECTED_ENDPOINTS', INJECTED_ENDPOINTS)
- .service('ViewArray', ViewArrayImpl)
- .run(quayRun)
- .name;
+ .constant('INJECTED_ENDPOINTS', INJECTED_ENDPOINTS);
diff --git a/static/js/quay.routes.ts b/static/js/quay.routes.ts
deleted file mode 100644
index 1714c51ba..000000000
--- a/static/js/quay.routes.ts
+++ /dev/null
@@ -1,138 +0,0 @@
-import { RouteBuilderImpl } from './services/route-builder/route-builder.service.impl';
-import { RouteBuilder } from './services/route-builder/route-builder.service';
-import pages from './constants/pages.constant';
-import * as ng from '@types/angular';
-
-
-routeConfig.$inject = [
- 'pages',
- '$routeProvider',
- '$locationProvider',
- 'INJECTED_FEATURES',
-];
-
-export function routeConfig(
- pages: any,
- $routeProvider: ng.route.IRouteProvider,
- $locationProvider: ng.ILocationProvider,
- INJECTED_FEATURES) {
- $locationProvider.html5Mode(true);
-
- // WARNING WARNING WARNING
- // If you add a route here, you must add a corresponding route in thr endpoints/web.py
- // index rule to make sure that deep links directly deep into the app continue to work.
- // WARNING WARNING WARNING
-
- var routeBuilder: RouteBuilder = new RouteBuilderImpl($routeProvider, pages);
-
- if (INJECTED_FEATURES.SUPER_USERS) {
- // QE Management
- routeBuilder.route('/superuser/', 'superuser')
- // QE Setup
- .route('/setup/', 'setup');
- }
-
- routeBuilder
- // Repository View
- .route('/repository/:namespace/:name', 'repo-view')
- .route('/repository/:namespace/:name/tag/:tag', 'repo-view')
-
- // Image View
- .route('/repository/:namespace/:name/image/:image', 'image-view')
-
- // Repo Build View
- .route('/repository/:namespace/:name/build/:buildid', 'build-view')
-
- // Repo Trigger View
- .route('/repository/:namespace/:name/trigger/:triggerid', 'trigger-setup')
-
- // Create repository notification
- .route('/repository/:namespace/:name/create-notification', 'create-repository-notification')
-
- // Repo List
- .route('/repository/', 'repo-list')
-
- // Organizations
- .route('/organizations/', 'organizations')
-
- // New Organization
- .route('/organizations/new/', 'new-organization')
-
- // View Organization
- .route('/organization/:orgname', 'org-view')
-
- // View Organization Team
- .route('/organization/:orgname/teams/:teamname', 'team-view')
-
- // Organization View Application
- .route('/organization/:orgname/application/:clientid', 'manage-application')
-
- // View Organization Billing
- .route('/organization/:orgname/billing', 'billing')
-
- // View Organization Billing Invoices
- .route('/organization/:orgname/billing/invoices', 'invoices')
-
- // View User
- .route('/user/:username', 'user-view')
-
- // View User Billing
- .route('/user/:username/billing', 'billing')
-
- // View User Billing Invoices
- .route('/user/:username/billing/invoices', 'invoices')
-
- // Sign In
- .route('/signin/', 'signin')
-
- // New Repository
- .route('/new/', 'new-repo')
-
- // Plans
- .route('/plans/', 'plans')
-
- // Tutorial
- .route('/tutorial/', 'tutorial')
-
- // Contact
- .route('/contact/', 'contact')
-
- // About
- .route('/about/', 'about')
-
- // Security
- .route('/security/', 'security')
-
- // TOS
- .route('/tos', 'tos')
-
- // Privacy
- .route('/privacy', 'privacy')
-
- // Change username
- .route('/updateuser', 'update-user')
-
- // Landing Page
- .route('/', 'landing')
-
- // Tour
- .route('/tour/', 'tour')
- .route('/tour/features', 'tour')
- .route('/tour/organizations', 'tour')
- .route('/tour/enterprise', 'tour')
-
- // Confirm Invite
- .route('/confirminvite', 'confirm-invite')
-
- // Enterprise marketing page
- .route('/enterprise', 'enterprise')
-
- // Public Repo Experiments
- .route('/__exp/publicRepo', 'public-repo-exp')
-
- // 404/403
- .route('/:catchall', 'error-view')
- .route('/:catch/:all', 'error-view')
- .route('/:catch/:all/:things', 'error-view')
- .route('/:catch/:all/:things/:here', 'error-view');
-}
diff --git a/static/js/quay.run.ts b/static/js/quay.run.ts
deleted file mode 100644
index 9ebcfd262..000000000
--- a/static/js/quay.run.ts
+++ /dev/null
@@ -1,143 +0,0 @@
-import * as $ from 'jquery';
-import * as ng from '@types/angular';
-
-
-quayRun.$inject = [
- '$location',
- '$rootScope',
- 'Restangular',
- 'UserService',
- 'PlanService',
- '$http',
- '$timeout',
- 'CookieService',
- 'Features',
- '$anchorScroll',
- 'UtilService',
- 'MetaService',
- 'INJECTED_CONFIG',
-];
-
-export default function quayRun(
- $location: ng.ILocationService,
- $rootScope: QuayRunScope,
- Restangular: any,
- UserService: any,
- PlanService: any,
- $http: ng.IHttpService,
- $timeout: ng.ITimeoutService,
- CookieService: any,
- Features: any,
- $anchorScroll: ng.IAnchorScrollService,
- UtilService: any,
- MetaService: any,
- INJECTED_CONFIG: any) {
- var defaultTitle = INJECTED_CONFIG['REGISTRY_TITLE'] || 'Quay Container Registry';
-
- // Handle session security.
- Restangular.setDefaultRequestParams(['post', 'put', 'remove', 'delete'],
- {'_csrf_token': (window).__token || ''});
-
- // Handle session expiration.
- Restangular.setErrorInterceptor(function(response) {
- if (response !== undefined && response.status == 503) {
- ($('#cannotContactService')).modal({});
- return false;
- }
-
- if (response !== undefined && response.status == 500) {
- window.location.href = '/500';
- return false;
- }
-
- if (response !== undefined && !response.data) {
- return true;
- }
-
- var invalid_token = response.data['title'] == 'invalid_token' || response.data['error_type'] == 'invalid_token';
- if (response !== undefined && response.status == 401 &&
- invalid_token &&
- response.data['session_required'] !== false) {
- ($('#sessionexpiredModal')).modal({});
- return false;
- }
-
- return true;
- });
-
- // Check if we need to redirect based on a previously chosen plan.
- var result = PlanService.handleNotedPlan();
-
- // Check to see if we need to show a redirection page.
- var redirectUrl = CookieService.get('quay.redirectAfterLoad');
- CookieService.clear('quay.redirectAfterLoad');
-
- if (!result && redirectUrl && redirectUrl.indexOf((window).location.href) == 0) {
- (window).location = redirectUrl;
- return;
- }
-
- $rootScope.$watch('description', function(description: string) {
- if (!description) {
- description = `Hosted private docker repositories. Includes full user management and history.
- Free for public repositories.`;
- }
-
- // Note: We set the content of the description tag manually here rather than using Angular binding
- // because we need the tag to have a default description that is not of the form "{{ description }}",
- // we read by tools that do not properly invoke the Angular code.
- $('#descriptionTag').attr('content', description);
- });
-
- // Listen for scope changes and update the title and description accordingly.
- $rootScope.$watch(function() {
- var title = MetaService.getTitle($rootScope.currentPage) || defaultTitle;
- $rootScope.title = title;
-
- var description = MetaService.getDescription($rootScope.currentPage) || '';
- if ($rootScope.description != description) {
- $rootScope.description = description;
- }
- });
-
- $rootScope.$on('$routeChangeSuccess', function (event, current, previous) {
- $rootScope.current = current.$$route;
- $rootScope.currentPage = current;
-
- $rootScope.pageClass = '';
-
- if (!current.$$route) { return; }
-
- var pageClass = current.$$route.pageClass || '';
- if (typeof pageClass != 'string') {
- pageClass = pageClass(Features);
- }
-
-
- $rootScope.pageClass = pageClass;
- $rootScope.newLayout = !!current.$$route.newLayout;
- $rootScope.fixFooter = !!current.$$route.fixFooter;
-
- $anchorScroll();
- });
-
- var initallyChecked = false;
- (window).__isLoading = function() {
- if (!initallyChecked) {
- initallyChecked = true;
- return true;
- }
- return $http.pendingRequests.length > 0;
- };
-}
-
-
-interface QuayRunScope extends ng.IRootScopeService {
- currentPage: any;
- current: any;
- title: any;
- description: string,
- pageClass: any;
- newLayout: any;
- fixFooter: any;
-}
\ No newline at end of file
diff --git a/static/js/services/page/page.service.impl.spec.ts b/static/js/services/page/page.service.impl.spec.ts
new file mode 100644
index 000000000..61f9e51f1
--- /dev/null
+++ b/static/js/services/page/page.service.impl.spec.ts
@@ -0,0 +1,22 @@
+import { PageServiceImpl } from './page.service.impl';
+
+
+describe("Service: PageServiceImpl", () => {
+ var pageServiceImpl: PageServiceImpl;
+
+ beforeEach(() => {
+ pageServiceImpl = new PageServiceImpl();
+ });
+
+ describe("create", () => {
+
+ });
+
+ describe("get", () => {
+
+ });
+
+ describe("$get", () => {
+
+ });
+});
\ No newline at end of file
diff --git a/static/js/services/page/page.service.impl.ts b/static/js/services/page/page.service.impl.ts
new file mode 100644
index 000000000..d52818899
--- /dev/null
+++ b/static/js/services/page/page.service.impl.ts
@@ -0,0 +1,45 @@
+import { Injectable } from 'angular-ts-decorators';
+import { PageService } from './page.service';
+
+
+@Injectable(PageService.name)
+export class PageServiceImpl implements ng.IServiceProvider {
+
+ private pages: any = {};
+
+ constructor() {
+
+ }
+
+ public create(pageName: string,
+ templateName: string,
+ controller?: any,
+ flags: any = {},
+ profiles: string[] = ['old-layout', 'layout']): void {
+ for (var i = 0; i < profiles.length; ++i) {
+ this.pages[profiles[i] + ':' + pageName] = {
+ 'name': pageName,
+ 'controller': controller,
+ 'templateName': templateName,
+ 'flags': flags
+ };
+ }
+ }
+
+ public get(pageName: string, profiles: any[]): any[] | null {
+ for (var i = 0; i < profiles.length; ++i) {
+ var current = profiles[i];
+ var key = current.id + ':' + pageName;
+ var page = this.pages[key];
+ if (page) {
+ return [current, page];
+ }
+ }
+
+ return null;
+ }
+
+ public $get(): PageService {
+ return this;
+ }
+}
diff --git a/static/js/services/page/page.service.ts b/static/js/services/page/page.service.ts
new file mode 100644
index 000000000..829959d2a
--- /dev/null
+++ b/static/js/services/page/page.service.ts
@@ -0,0 +1,32 @@
+/**
+ * Manages the creation and retrieval of pages (route + controller)
+ */
+export abstract class PageService implements ng.IServiceProvider {
+
+ /**
+ * Create a page.
+ * @param pageName The name of the page.
+ * @param templateName The file name of the template.
+ * @param controller Controller for the page.
+ * @param flags Additional flags passed to route provider.
+ * @param profiles Available profiles.
+ */
+ public abstract create(pageName: string,
+ templateName: string,
+ controller?: any,
+ flags?: any,
+ profiles?: string[]): void;
+
+ /**
+ * Retrieve a registered page.
+ * @param pageName The name of the page.
+ * @param profiles Available profiles to search.
+ */
+ public abstract get(pageName: string, profiles: any[]): any[] | null;
+
+ /**
+ * Provide the service instance.
+ * @return pageService The singleton service instance.
+ */
+ public abstract $get(): PageService;
+}
diff --git a/static/js/services/view-array/view-array.impl.ts b/static/js/services/view-array/view-array.impl.ts
index 8cc3f75c4..939d50e5c 100644
--- a/static/js/services/view-array/view-array.impl.ts
+++ b/static/js/services/view-array/view-array.impl.ts
@@ -1,7 +1,9 @@
import { ViewArray } from './view-array';
import { Inject } from '../../decorators/inject/inject.decorator';
+import { Injectable } from 'angular-ts-decorators';
+@Injectable(ViewArray.name)
export class ViewArrayImpl implements ViewArray {
public entries: any[];