using decorators to write AngularJS in nearly identical syntax to Angular 2
This commit is contained in:
parent
8e863b8cf5
commit
c60ce4a696
19 changed files with 559 additions and 488 deletions
|
@ -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',
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -184,10 +184,10 @@
|
|||
<td>
|
||||
<input type="text" class="form-control" ng-model="local.triggerOptions.branchTagFilter" required>
|
||||
<div class="description">Examples: heads/master, tags/tagname, heads/.+</div>
|
||||
<div class="regex-match-view"
|
||||
items="local.repositoryFullRefs"
|
||||
regex="local.triggerOptions.branchTagFilter"
|
||||
ng-if="local.triggerOptions.branchTagFilter"></div>
|
||||
<regex-match-view
|
||||
items="local.repositoryFullRefs"
|
||||
regex="local.triggerOptions.branchTagFilter"
|
||||
ng-if="local.triggerOptions.branchTagFilter"></regex-match-view>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
<div class="regex-match-view-element">
|
||||
<div ng-if="filterMatches(regex, items, false) == null">
|
||||
<i class="fa fa-exclamation-triangle"></i>Invalid Regular Expression!
|
||||
</div>
|
||||
<div ng-if="filterMatches(regex, items, false) != null">
|
||||
<table class="match-table">
|
||||
<tr>
|
||||
<td>Matching:</td>
|
||||
<td>
|
||||
<ul class="matching match-list">
|
||||
<li ng-repeat="item in filterMatches(regex, items, true)">
|
||||
<i class="fa {{ item.icon }}"></i>{{ item.title }}
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Not Matching:</td>
|
||||
<td>
|
||||
<ul class="not-matching match-list">
|
||||
<li ng-repeat="item in filterMatches(regex, items, false)">
|
||||
<i class="fa {{ item.icon }}"></i>{{ item.title }}
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
|
@ -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]+)*)$',
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
|
@ -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;
|
||||
});
|
|
@ -0,0 +1,14 @@
|
|||
import { RegexMatchViewComponent } from './regex-match-view.component';
|
||||
|
||||
|
||||
describe("RegexMatchViewComponent", () => {
|
||||
var component: RegexMatchViewComponent;
|
||||
|
||||
beforeEach(() => {
|
||||
component = new RegexMatchViewComponent();
|
||||
});
|
||||
|
||||
describe("filterMatches", () => {
|
||||
|
||||
});
|
||||
});
|
|
@ -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: `
|
||||
<div class="regex-match-view-element">
|
||||
<div ng-if="$ctrl.filterMatches($ctrl.regex, $ctrl.items, false) == null">
|
||||
<i class="fa fa-exclamation-triangle"></i>Invalid Regular Expression!
|
||||
</div>
|
||||
<div ng-if="$ctrl.filterMatches($ctrl.regex, $ctrl.items, false) != null">
|
||||
<table class="match-table">
|
||||
<tr>
|
||||
<td>Matching:</td>
|
||||
<td>
|
||||
<ul class="matching match-list">
|
||||
<li ng-repeat="item in $ctrl.filterMatches($ctrl.regex, $ctrl.items, true)">
|
||||
<i class="fa {{ item.icon }}"></i>{{ item.title }}
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Not Matching:</td>
|
||||
<td>
|
||||
<ul class="not-matching match-list">
|
||||
<li ng-repeat="item in $ctrl.filterMatches($ctrl.regex, $ctrl.items, false)">
|
||||
<i class="fa {{ item.icon }}"></i>{{ item.title }}
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
146
static/js/quay-routes.module.ts
Normal file
146
static/js/quay-routes.module.ts
Normal file
|
@ -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');
|
||||
}
|
||||
}
|
|
@ -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}});
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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': (<any>window).__token || ''});
|
||||
|
||||
// Handle session expiration.
|
||||
Restangular.setErrorInterceptor(function(response) {
|
||||
if (response !== undefined && response.status == 503) {
|
||||
(<any>$('#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) {
|
||||
(<any>$('#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((<any>window).location.href) == 0) {
|
||||
(<any>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 <meta> 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;
|
||||
(<any>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);
|
||||
|
|
|
@ -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');
|
||||
}
|
|
@ -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': (<any>window).__token || ''});
|
||||
|
||||
// Handle session expiration.
|
||||
Restangular.setErrorInterceptor(function(response) {
|
||||
if (response !== undefined && response.status == 503) {
|
||||
(<any>$('#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) {
|
||||
(<any>$('#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((<any>window).location.href) == 0) {
|
||||
(<any>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 <meta> 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;
|
||||
(<any>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;
|
||||
}
|
22
static/js/services/page/page.service.impl.spec.ts
Normal file
22
static/js/services/page/page.service.impl.spec.ts
Normal file
|
@ -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", () => {
|
||||
|
||||
});
|
||||
});
|
45
static/js/services/page/page.service.impl.ts
Normal file
45
static/js/services/page/page.service.impl.ts
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
32
static/js/services/page/page.service.ts
Normal file
32
static/js/services/page/page.service.ts
Normal file
|
@ -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;
|
||||
}
|
|
@ -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[];
|
||||
|
|
Reference in a new issue