├── .babelrc ├── .bowerrc ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── app ├── app.less ├── app.ts ├── assets │ └── img │ │ ├── favicon.ico │ │ └── favicon.png ├── components │ ├── navigation │ │ ├── navigation.component.ts │ │ ├── navigation.controller.ts │ │ └── navigation.html │ └── oauth │ │ ├── oauth.component.ts │ │ ├── oauth.controller.ts │ │ └── oauth.html ├── config.js ├── index.html ├── mockServices │ ├── mockAPI.service.ts │ ├── mockAlertMessage.service.ts │ ├── mockApplications.service.ts │ ├── mockAuth.service.ts │ ├── mockAuthCfg.constant.ts │ ├── mockAuthorization.service.ts │ ├── mockBinding.service.ts │ ├── mockConstants.service.ts │ ├── mockData.service.ts │ ├── mockData │ │ ├── deploymentConfig.ts │ │ ├── deployments.ts │ │ ├── instances.ts │ │ ├── openshift-images.ts │ │ ├── plans.ts │ │ ├── projects.ts │ │ ├── replicaSets.ts │ │ ├── replicationControllers.ts │ │ ├── services.ts │ │ └── statefulSets.ts │ ├── mockKeywords.service.ts │ ├── mockLogger.service.ts │ ├── mockProjects.service.ts │ ├── mockRecentlyViewedProjects.service.ts │ ├── mockServices.module.ts │ └── mockVersions.service.ts ├── pages │ ├── error │ │ ├── error-page.html │ │ ├── errorPage.ts │ │ └── errorPageController.ts │ ├── home │ │ ├── home-page.html │ │ ├── homePage.ts │ │ └── homePageController.ts │ └── logout │ │ ├── logout-page.html │ │ ├── logoutPage.ts │ │ └── logoutPageController.ts ├── routes.ts └── styles │ ├── scrollbars.less │ └── variables.less ├── bower.json ├── config ├── helpers.js ├── webpack.common.js ├── webpack.dev.js ├── webpack.prod.js └── webpack.test.js ├── dist ├── less │ ├── animations.less │ ├── catalog.less │ ├── catalogParameters.less │ ├── landing-page.less │ ├── main.less │ ├── mixins.less │ ├── order-service.less │ ├── overlay-panel.less │ ├── projects-summary.less │ ├── saas-list.less │ ├── services-view.less │ └── variables.less ├── origin-web-catalogs.css ├── origin-web-catalogs.css.map ├── origin-web-catalogs.js └── vendor-bundle.js ├── hack ├── clean-deps.sh ├── install-deps.sh ├── test-headless.sh └── verify-dist.sh ├── karma.conf.js ├── package-lock.json ├── package.json ├── src ├── components │ ├── catalog-filter │ │ ├── catalog-filter.component.ts │ │ ├── catalog-filter.controller.ts │ │ └── catalog-filter.html │ ├── catalog-parameters │ │ ├── catalog-parameters.component.ts │ │ ├── catalog-parameters.controller.ts │ │ └── catalog-parameters.html │ ├── catalog-search │ │ ├── catalog-search-result.html │ │ ├── catalog-search.component.ts │ │ ├── catalog-search.controller.ts │ │ └── catalog-search.html │ ├── create-from-builder │ │ ├── create-from-builder-bind.html │ │ ├── create-from-builder-configure.html │ │ ├── create-from-builder-info.html │ │ ├── create-from-builder-results.html │ │ ├── create-from-builder.component.ts │ │ ├── create-from-builder.controller.ts │ │ └── create-from-builder.html │ ├── landing-page │ │ ├── landing-page.component.ts │ │ ├── landing-page.controller.ts │ │ └── landing-page.html │ ├── order-service │ │ ├── order-service-bind-parameters.html │ │ ├── order-service-bind.html │ │ ├── order-service-configure.html │ │ ├── order-service-info.html │ │ ├── order-service-plans.html │ │ ├── order-service-results.html │ │ ├── order-service.component.ts │ │ ├── order-service.controller.ts │ │ └── order-service.html │ ├── overlay-panel │ │ ├── overlay-panel.component.ts │ │ ├── overlay-panel.controller.ts │ │ └── overlay-panel.html │ ├── projects-summary │ │ ├── projects-summary.component.ts │ │ ├── projects-summary.controller.ts │ │ └── projects-summary.html │ ├── saas-list │ │ ├── saas-list.component.ts │ │ ├── saas-list.controller.ts │ │ └── saas-list.html │ ├── select-plan │ │ ├── select-plan.component.ts │ │ ├── select-plan.controller.ts │ │ └── selectPlan.html │ ├── select-project │ │ ├── select-project.component.ts │ │ ├── select-project.controller.ts │ │ └── selectProject.html │ ├── services-view │ │ ├── services-view.component.ts │ │ ├── services-view.controller.ts │ │ └── services-view.html │ └── update-service │ │ ├── update-service-configure.html │ │ ├── update-service-plans.html │ │ ├── update-service-results.html │ │ ├── update-service.component.ts │ │ ├── update-service.controller.ts │ │ └── update-service.html ├── constants.ts ├── decorators │ └── bootstrap │ │ ├── array.html │ │ ├── checkbox.html │ │ ├── checkboxes.html │ │ ├── default.html │ │ └── select.html ├── filters │ ├── escapeRegExp.ts │ ├── projectUrl.ts │ └── secretUrl.ts ├── index.ts ├── services │ ├── builder-app.service.ts │ ├── catalog.service.ts │ └── recently-viewed-service-items.service.ts └── styles │ ├── animations.less │ ├── catalog.less │ ├── catalogParameters.less │ ├── landing-page.less │ ├── main.less │ ├── mixins.less │ ├── order-service.less │ ├── overlay-panel.less │ ├── projects-summary.less │ ├── saas-list.less │ ├── services-view.less │ └── variables.less ├── test ├── landing-page.spec.ts ├── overlay-panel.spec.ts ├── projects-summary.spec.ts ├── saas-list.spec.ts ├── select-project.spec.ts ├── services-view.spec.ts ├── test-bundle.ts └── utils │ ├── ComponentTest.ts │ └── testHelpers.ts ├── tsconfig.json ├── tslint.json └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } 4 | -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | 11 | # Matches multiple files with brace expansion notation 12 | # Set default charset 13 | [*.{js}] 14 | charset = utf-8 15 | 16 | # Indentation override for all JS/TS under src directory 17 | [src/**.js, src/**.ts] 18 | indent_style = space 19 | indent_size = 2 20 | 21 | # Matches the exact files either package.json or .travis.yml 22 | [{package.json,.travis.yml}] 23 | indent_style = space 24 | indent_size = 2 25 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 6, 4 | "ecmaFeatures": { 5 | "experimentalObjectRestSpread": true, 6 | "jsx": true 7 | }, 8 | "sourceType": "module" 9 | }, 10 | 11 | "env": { 12 | "es6": true, 13 | "node": true 14 | }, 15 | 16 | "plugins": [ 17 | "standard", 18 | "promise" 19 | ], 20 | 21 | "globals": { 22 | "document": false, 23 | "navigator": false, 24 | "window": false 25 | }, 26 | 27 | "rules": { 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | bower_components 4 | /app/config.local.js 5 | coverage 6 | npm-debug.log 7 | dist/catalogs-app* 8 | dist/*.wof* 9 | dist/*.eot 10 | dist/*.ttf 11 | dist/*.svg 12 | dist/*.gif 13 | dist/*.jpg 14 | dist/*.png 15 | .DS_Store 16 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .babelrc 3 | .bowerrc 4 | .editorconfig 5 | .eslintignore 6 | .elsintrc 7 | .gitignore 8 | .travis.yml 9 | 10 | node_modules 11 | bower_components 12 | 13 | karma.conf.js 14 | Makefile 15 | tsconfig.json 16 | tslint.json 17 | webpack.config.js 18 | 19 | 20 | app 21 | config 22 | coverage 23 | hack 24 | src 25 | test 26 | 27 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | before_install: 5 | - if [[ `npm -v` != 5* ]]; then npm i -g npm@5; npm -v; fi 6 | before_script: 7 | - make build 8 | script: 9 | - hack/test-headless.sh test 10 | - hack/verify-dist.sh 11 | addons: 12 | firefox: "49.0" 13 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .DEFAULT_GOAL := build 2 | 3 | # Pull in dependencies 4 | # 5 | # Examples: 6 | # make install 7 | install: 8 | hack/install-deps.sh 9 | .PHONY: install 10 | 11 | # Clean up all dependencies 12 | # 13 | # 14 | # Example: 15 | # make clean 16 | # make test 17 | clean: 18 | hack/clean-deps.sh 19 | .PHONY: clean 20 | 21 | # Run `npm run build` 22 | # 23 | # Examples: 24 | # make build 25 | build: install 26 | npm run build 27 | .PHONY: build 28 | 29 | # Run all tests 30 | # 31 | # Examples: 32 | # make test 33 | test: build 34 | hack/verify-dist.sh 35 | hack/test-headless.sh test 36 | .PHONY: test 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | OpenShift Catalog Console Library 2 | ========================= 3 | A component library created for the cataloging components for [The OpenShift Web Console](https://github.com/openshift/origin-web-console). 4 | 5 | [![Build Status](https://travis-ci.org/openshift/origin-web-catalog.svg?branch=master)](https://travis-ci.org/openshift/origin-web-catalog) 6 | 7 | ## Quick start 8 | 9 | ``` 10 | # install the dependencies with npm and bower 11 | $ npm install 12 | $ bower install 13 | 14 | # build the library 15 | $ npm run build 16 | ``` 17 | 18 | ## Showcase Application 19 | 20 | There is a showcase application used to help develop and view the existing components in the library. 21 | 22 | ``` 23 | # run the server 24 | $ npm run start 25 | ``` 26 | 27 | Go to [https://localhost:9001](https://localhost:9001) in your browser. (though it should auto-launch) 28 | This will watch for asset changes. 29 | 30 | You will need to patch the web console oauth client to allow for port 9001: 31 | ``` 32 | $ oc login -u system:admin 33 | $ oc patch oauthclient/openshift-web-console -p '{"redirectURIs":["https://localhost:9001/"]}' 34 | ``` 35 | 36 | Contributing 37 | ------------ 38 | 39 | #### Getting started 40 | 1. Install [Nodejs](http://nodejs.org/) and [npm](https://www.npmjs.org/) 41 | 2. Build the library with 'npm run build' 42 | 3. Run the test server with 'npm run start' 43 | 4. Launch a browser at https://localhost:9001/ this will watch for asset changes. 44 | 45 | ## Testing 46 | 47 | ### 1. Unit Tests 48 | 49 | * single run: `npm test` 50 | * live mode (TDD style): `npm run test:watch` 51 | -------------------------------------------------------------------------------- /app/app.less: -------------------------------------------------------------------------------- 1 | @import (css) "~hopscotch/dist/css/hopscotch.css"; 2 | 3 | @import (css) "~patternfly/dist/css/patternfly.css"; 4 | @import (css) "~patternfly/dist/css/patternfly-additions.css"; 5 | @import (css) "~angular-patternfly/dist/styles/angular-patternfly.css"; 6 | @import (css) "~ui-select/dist/select.css"; 7 | @import (css) "~origin-web-common/dist/origin-web-common.css"; 8 | 9 | @import '~openshift-logos-icon/less/openshift-logos-icon.less'; 10 | 11 | @import '~bootstrap/less/variables.less'; 12 | @import '~patternfly/dist/less/color-variables.less'; 13 | @import '~patternfly/dist/less/variables.less'; 14 | @import '~origin-web-common/dist/less/_variables.less'; 15 | @import '../src/styles/variables.less'; 16 | 17 | @import "./styles/variables.less"; 18 | @import "./styles/scrollbars.less"; 19 | 20 | 21 | @media(min-width: @screen-sm-min) { 22 | body, html, main { 23 | height: 100%; 24 | overflow: hidden; 25 | } 26 | body { 27 | padding-top: @navbar-height; 28 | } 29 | } 30 | 31 | .showcase-app { 32 | .navbar-brand { 33 | cursor: default; 34 | &:hover, &:focus { 35 | color: @navbar-pf-active-color; 36 | } 37 | } 38 | .navbar-header { 39 | height: @navbar-height - 3px; 40 | padding-top: 16px 41 | } 42 | .navbar-utility { 43 | padding-top: 17px; 44 | } 45 | 46 | } 47 | 48 | .main-container { 49 | padding: 0 (@grid-gutter-width / 2); 50 | } 51 | 52 | .navbar-fixed-top { 53 | @media(max-width: @screen-xs-max) { 54 | position: static; 55 | } 56 | } 57 | 58 | .navbar-pf .navbar-brand { 59 | font-size: 11px; 60 | padding-bottom: 7px; 61 | padding-top: 7px; 62 | @media(min-width: @screen-sm-min) { 63 | padding-bottom: 2px; 64 | padding-top: 3px; 65 | } 66 | } 67 | 68 | .surface-shaded { 69 | background-color: @panel-shaded; 70 | } 71 | -------------------------------------------------------------------------------- /app/app.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import * as angular from 'angular'; 4 | 5 | require('bootstrap/dist/js/bootstrap'); 6 | require('patternfly/dist/js/patternfly'); 7 | require('angular-patternfly/dist/angular-patternfly'); 8 | require('angular-ui-bootstrap/ui-bootstrap'); 9 | require('angular-ui-bootstrap/ui-bootstrap-tpls'); 10 | require('ui-select/dist/select'); 11 | require('angular-sanitize/angular-sanitize'); 12 | require('angular-moment/angular-moment'); 13 | 14 | require('angular-drag-and-drop-lists/angular-drag-and-drop-lists.min.js'); 15 | require('angular-animate/angular-animate.min.js'); 16 | 17 | require('jquery/dist/jquery.min.js'); 18 | require('lodash/index.js'); 19 | 20 | require('imports-loader?define=>false!js-logger/src/logger'); 21 | require('urijs'); 22 | require('urijs/src/URITemplate.js'); 23 | require('angular-utf8-base64'); 24 | require('hopscotch/dist/js/hopscotch.js'); 25 | require('angular-schema-form'); 26 | require('angular-schema-form-bootstrap'); 27 | 28 | try { 29 | require('./config.local.js'); 30 | } catch (e) { 31 | require('./config.js'); 32 | } 33 | 34 | import {oauth} from './components/oauth/oauth.component'; 35 | import {homePage} from './pages/home/homePage'; 36 | import {errorPage} from './pages/error/errorPage'; 37 | import {logoutPage} from './pages/logout/logoutPage'; 38 | import {navigation} from './components/navigation/navigation.component'; 39 | import {MockServicesModule} from './mockServices/mockServices.module'; 40 | 41 | import '@uirouter/angularjs'; 42 | import routesConfig from './routes'; 43 | 44 | import './app.less'; 45 | import '../src/index'; 46 | 47 | export const catalogApp: string = 'catalogApp'; 48 | 49 | let pluginLoader, commonServices = ''; 50 | let mockServicesModule = new MockServicesModule(window); 51 | 52 | if (mockServicesModule.useMockServices() !== true) { 53 | pluginLoader = require('origin-web-common/dist/origin-web-common.js'); 54 | commonServices = 'openshiftCommonServices'; 55 | } else { 56 | pluginLoader = require('origin-web-common/dist/origin-web-common-ui.js'); 57 | commonServices = mockServicesModule.moduleName; 58 | } 59 | 60 | angular 61 | .module(catalogApp, ['webCatalog', 'openshiftCommonUI', commonServices, 'ui.router', 'patternfly', 'angularMoment', 'schemaForm']) 62 | .config(routesConfig) 63 | .component('oauth', oauth) 64 | .component('homepage', homePage) 65 | .component('errorpage', errorPage) 66 | .component('logoutpage', logoutPage) 67 | .component('navigation', navigation) 68 | .constant('amTimeAgoConfig', {titleFormat: 'LLL'}) 69 | .run(function(IS_IOS: boolean) { 70 | if (IS_IOS) { 71 | // Add a class for iOS devices. This lets us disable some hover effects 72 | // since iOS will treat the first tap as a hover if it changes the DOM 73 | // content (e.g. using :before pseudo-elements). 74 | $('body').addClass('ios'); 75 | } 76 | }); 77 | 78 | pluginLoader.addModule(catalogApp); 79 | -------------------------------------------------------------------------------- /app/assets/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openshift/origin-web-catalog/afd6bc5e2f444f8bcc3cbf651598c97fb9fce2cb/app/assets/img/favicon.ico -------------------------------------------------------------------------------- /app/assets/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openshift/origin-web-catalog/afd6bc5e2f444f8bcc3cbf651598c97fb9fce2cb/app/assets/img/favicon.png -------------------------------------------------------------------------------- /app/components/navigation/navigation.component.ts: -------------------------------------------------------------------------------- 1 | import {NavigationController} from './navigation.controller'; 2 | 3 | export const navigation: angular.IComponentOptions = { 4 | controller: NavigationController, 5 | template: require('./navigation.html') 6 | }; 7 | -------------------------------------------------------------------------------- /app/components/navigation/navigation.controller.ts: -------------------------------------------------------------------------------- 1 | import * as angular from 'angular'; 2 | 3 | export class NavigationController implements angular.IController { 4 | static $inject = ['$transitions', '$rootScope', '$state', '$timeout', 'Constants', 'GuidedTourService']; 5 | 6 | public ctrl: any = this; 7 | public applicationName: string; 8 | public navCollapsed: boolean = true; 9 | private $transitions: any; 10 | private $rootScope: any; 11 | private $state: any; 12 | private $timeout: any; 13 | private Constants: any; 14 | private GuidedTourService: any; 15 | private tourConfig: any; 16 | 17 | constructor($transitions: any, $rootScope: any, $state: any, $timeout: any, Constants: any, GuidedTourService: any) { 18 | this.$transitions = $transitions; 19 | this.$rootScope = $rootScope; 20 | this.$state = $state; 21 | this.$timeout = $timeout; 22 | this.Constants = Constants; 23 | this.GuidedTourService = GuidedTourService; 24 | }; 25 | 26 | public $onInit() { 27 | this.ctrl.applicationName = 'OPENSHIFT WEB CATALOGS'; 28 | 29 | var _ctrl: any = this.ctrl; 30 | this.$transitions.onSuccess({to: true}, function(state: any) { 31 | var toState: any = state.$to(); 32 | var stateName: string = toState.name; 33 | 34 | angular.forEach(_ctrl.navigationItems, function(navItem: any) { 35 | navItem.isActive = stateName.indexOf(navItem.uiSref) === 0; 36 | }); 37 | }); 38 | 39 | this.ctrl.user = this.$rootScope.user; 40 | 41 | this.$rootScope.$watch('user', function(newValue: any) { 42 | _ctrl.user = newValue; 43 | }); 44 | 45 | this.tourConfig = this.Constants.GUIDED_TOURS ? this.Constants.GUIDED_TOURS.landing_page_tour : undefined; 46 | }; 47 | 48 | public doLogout() { 49 | this.$state.go('logout'); 50 | } 51 | 52 | public startTour() { 53 | this.$state.go('home'); 54 | this.$timeout(() => { 55 | if (this.tourConfig && this.tourConfig.enabled && this.tourConfig.steps) { 56 | this.GuidedTourService.startTour(this.tourConfig.steps); 57 | } 58 | }, 500); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/components/navigation/navigation.html: -------------------------------------------------------------------------------- 1 | 48 | -------------------------------------------------------------------------------- /app/components/oauth/oauth.component.ts: -------------------------------------------------------------------------------- 1 | import {OAuthController} from './oauth.controller'; 2 | 3 | export const oauth: angular.IComponentOptions = { 4 | controller: OAuthController, 5 | template: require('./oauth.html') 6 | }; 7 | -------------------------------------------------------------------------------- /app/components/oauth/oauth.controller.ts: -------------------------------------------------------------------------------- 1 | 2 | import * as angular from 'angular'; 3 | 4 | let URI = require('urijs'); 5 | 6 | export class OAuthController implements angular.IController { 7 | static $inject = ['$location', '$q', 'RedirectLoginService', 'DataService', 'AuthService', 'Logger', '$window']; 8 | 9 | public ctrl: any = this; 10 | 11 | private $location: any; 12 | private $q: any; 13 | private RedirectLoginService: any; 14 | private DataService: any; 15 | private AuthService: any; 16 | private Logger: any; 17 | 18 | private doCompleteLogin: any; 19 | private authLogger: any; 20 | 21 | constructor($location: any, $q: any, RedirectLoginService: any, DataService: any, AuthService: any, Logger: any, $window: any) { 22 | this.$location = $location; 23 | this.$q = $q; 24 | this.RedirectLoginService = RedirectLoginService; 25 | this.DataService = DataService; 26 | this.AuthService = AuthService; 27 | this.Logger = Logger; 28 | 29 | // initialize to a no-op function. 30 | this.doCompleteLogin = function() { 31 | return; 32 | }; 33 | } 34 | 35 | public $onInit() { 36 | var _this: any = this; 37 | this.authLogger = this.Logger.get('auth'); 38 | 39 | this.RedirectLoginService.finish() 40 | .then(function(data: any) { 41 | var token = data.token; 42 | var then = data.then; 43 | var verified = data.verified; 44 | var ttl = data.ttl; 45 | 46 | // try to fetch the user 47 | var opts = {errorNotification: false, http: {auth: {token: token, triggerLogin: false}}}; 48 | _this.authLogger.log('OAuthController, got token, fetching user', opts); 49 | 50 | _this.DataService.get('users', '~', {}, opts) 51 | .then(function(user: any) { 52 | // set the new user and token in the auth service 53 | _this.authLogger.log('OAuthController, got user', user); 54 | 55 | _this.doCompleteLogin = function() { 56 | // persist the user 57 | _this.AuthService.setUser(user, token, ttl); 58 | 59 | // redirect to original destination (or default to './') 60 | var destination = then || './'; 61 | if (URI(destination).is('absolute')) { 62 | _this.authLogger.log('OAuthController, invalid absolute redirect', destination); 63 | destination = './'; 64 | } 65 | _this.authLogger.log('OAuthController, redirecting', destination); 66 | _this.$location.replace(); 67 | _this.$location.url(destination); 68 | }; 69 | 70 | if (verified) { 71 | // automatically complete 72 | _this.completeLogin(); 73 | } else { 74 | // require the UI to prompt 75 | _this.confirmUser = user; 76 | 77 | // additionally, give the UI info about the user being overridden 78 | var currentUser = _this.AuthService.UserStore().getUser(); 79 | if (currentUser && currentUser.metadata.name !== user.metadata.name) { 80 | _this.overriddenUser = currentUser; 81 | } 82 | } 83 | }) 84 | .catch(function(rejection: any) { 85 | // handle an API error response fetching the user 86 | var redirect = URI('error').query({error: 'user_fetch_failed'}).toString(); 87 | _this.authLogger.error('OAuthController, error fetching user', rejection, 'redirecting', redirect); 88 | _this.$location.replace(); 89 | _this.$location.url(redirect); 90 | }); 91 | 92 | }) 93 | .catch(function(rejection: any) { 94 | var redirect = URI('error').query({ 95 | error: rejection.error || '', 96 | error_description: rejection.error_description || '', 97 | error_uri: rejection.error_uri || '' 98 | }).toString(); 99 | _this.authLogger.error('OAuthController, error', rejection, 'redirecting', redirect); 100 | _this.$location.replace(); 101 | _this.$location.url(redirect); 102 | }); 103 | } 104 | 105 | public completeLogin() { 106 | this.doCompleteLogin(); 107 | } 108 | 109 | public cancelLogin() { 110 | this.$location.replace(); 111 | this.$location.url('./'); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /app/components/oauth/oauth.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 |
6 |

Logging in…

7 |

Please wait while you are logged in…

8 |
9 | 10 |
11 |

Confirm Login

12 |

You are being logged in as {{$$ctrl.confirmUser.metadata.name}}.

13 | 14 | 15 |
16 | 17 |
18 |

Confirm User Change

19 |

You are about to change users from {{$ctrl.overriddenUser.metadata.name}} to {{$ctrl.confirmUser.metadata.name}}.

20 |

If this is unexpected, click Cancel. This could be an attempt to trick you into acting as another user.

21 | 22 | 23 |
24 | 25 |
26 |
27 |
28 | -------------------------------------------------------------------------------- /app/config.js: -------------------------------------------------------------------------------- 1 | // This is the default configuration for the dev mode of the web console. 2 | // A generated version of this config is created at run-time when running 3 | // the web console from the openshift binary. 4 | // 5 | // To change configuration for local development, copy this file to 6 | // assets/app/config.local.js and edit the copy. 7 | window.DEV_SERVER_PORT = 9001; 8 | window.DEV_TOOL_SETTING = 'cheap-eval-source-map'; 9 | 10 | window.OPENSHIFT_CONFIG = { 11 | apis: { 12 | hostPort: "localhost:8443", 13 | prefix: "/apis" 14 | }, 15 | api: { 16 | openshift: { 17 | hostPort: "localhost:8443", 18 | prefix: "/oapi" 19 | }, 20 | k8s: { 21 | hostPort: "localhost:8443", 22 | prefix: "/api" 23 | } 24 | }, 25 | auth: { 26 | oauth_authorize_uri: "https://localhost:8443/oauth/authorize", 27 | oauth_redirect_base: "https://localhost:" + window.DEV_SERVER_PORT, 28 | oauth_client_id: "openshift-web-console", 29 | logout_uri: "" 30 | }, 31 | loggingURL: "", 32 | metricsURL: "" 33 | }; 34 | 35 | window.OPENSHIFT_VERSION = { 36 | openshift: "dev-mode", 37 | kubernetes: "dev-mode" 38 | }; 39 | 40 | window.OPENSHIFT_CONSOLE_BASE_URL = "https://localhost:9000/dev-console"; 41 | 42 | // Uncomment to use mock data. 43 | // window.MOCK_ORIGIN_SERVICES = true; 44 | // window.MOCK_SERVICES_DATA = true; 45 | 46 | // Uncomment to generate a very large project list for testing when using mock data. 47 | // window.MOCK_LARGE_PROJECT_LIST = true; 48 | -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Openshift Catalog Components 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /app/mockServices/mockAPI.service.ts: -------------------------------------------------------------------------------- 1 | interface IAPIService { 2 | apiInfo(rgv: any): any; 3 | kindToResource(kind: string, humanize: boolean): string; 4 | getPreferredVersion(resource: string): any; 5 | } 6 | 7 | /** Backend service communications. */ 8 | export class APIService implements IAPIService { 9 | public static $inject = ['$filter']; 10 | 11 | private $filter : any; 12 | 13 | constructor ($filter: any) { 14 | this.$filter = $filter; 15 | } 16 | 17 | public apiInfo(rgv: any) { 18 | return {}; 19 | } 20 | 21 | public kindToResource(kind: string, humanize: boolean) { 22 | if (!kind) { 23 | return ""; 24 | } 25 | 26 | var resource = kind; 27 | if (humanize) { 28 | var humanizeKind = this.$filter("humanizeKind"); 29 | resource = humanizeKind(resource); 30 | } 31 | 32 | resource = String(resource).toLowerCase(); 33 | if (resource === 'endpoints' || resource === 'securitycontextconstraints') { 34 | // no-op, plural is the singular 35 | } else if (resource[resource.length - 1] === 's') { 36 | resource = resource + 'es'; 37 | } else if (resource[resource.length - 1] === 'y') { 38 | resource = resource.substring(0, resource.length - 1) + 'ies'; 39 | } else { 40 | resource = resource + 's'; 41 | } 42 | 43 | return resource; 44 | } 45 | 46 | public getPreferredVersion(resource: string): any { 47 | let preferredVersions = { 48 | // Using the anticipated name for the resources, even though they aren't yet prefixed with `cluster`. 49 | // https://github.com/kubernetes-incubator/service-catalog/issues/1288 50 | clusterserviceclasses: {group: 'servicecatalog.k8s.io', resource: 'clusterserviceclasses' }, 51 | clusterserviceplans: {group: 'servicecatalog.k8s.io', resource: 'clusterserviceplans' }, 52 | imagestreams: {group: 'image.openshift.io', version: 'v1', resource: 'imagestreams' }, 53 | // Using the anticipated name for this resource, even though it's not currently called servicebindings. 54 | servicebindings: {group: 'servicecatalog.k8s.io', resource: 'servicebindings' }, 55 | serviceinstances: {group: 'servicecatalog.k8s.io', resource: 'serviceinstances' }, 56 | templates: {group: 'template.openshift.io', verison: 'v1', resource: 'templates' } 57 | }; 58 | 59 | return preferredVersions[resource] || resource; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/mockServices/mockAlertMessage.service.ts: -------------------------------------------------------------------------------- 1 | interface IAlertMessageService { 2 | isAlertPermanentlyHidden(alertID: any, namespace: any): any; 3 | permanentlyHideAlert(alertID: any, namespace: any): any; 4 | } 5 | 6 | export class AlertMessageService implements IAlertMessageService { 7 | private hiddenAlerts: any = []; 8 | 9 | public isAlertPermanentlyHidden(alertID: any, namespace: any) { 10 | var key = this.alertHiddenKey(alertID, namespace); 11 | return this.hiddenAlerts.indexOf(key); 12 | } 13 | 14 | public permanentlyHideAlert(alertID: any, namespace: any) { 15 | var key = this.alertHiddenKey(alertID, namespace); 16 | this.hiddenAlerts.push(key); 17 | } 18 | 19 | private alertHiddenKey (alertID: any, namespace: any) { 20 | if (!namespace) { 21 | return 'hide/alert/' + alertID; 22 | } 23 | 24 | return 'hide/alert/' + namespace + '/' + alertID; 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /app/mockServices/mockApplications.service.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | 3 | interface IApplicationsService { 4 | getApplications(context: any): Promise; 5 | } 6 | 7 | /** Backend service communications. */ 8 | export class ApplicationsService implements IApplicationsService { 9 | public static $inject = ['$filter', '$q', 'DataService']; 10 | 11 | private $filter: any; 12 | private $q: any; 13 | private DataService: any; 14 | 15 | constructor ($filter: any, $q: any, DataService: any) { 16 | this.$filter = $filter; 17 | this.$q = $q; 18 | this.DataService = DataService; 19 | } 20 | 21 | public getApplications(context: any): Promise { 22 | var deferred: any = this.$q.defer(); 23 | var promises: any = []; 24 | 25 | // Load all the "application" types 26 | promises.push(this.DataService.list('deploymentconfigs', context)); 27 | promises.push(this.DataService.list('replicationcontrollers', context)); 28 | promises.push(this.DataService.list({group: 'apps', resource: 'deployments'}, context)); 29 | promises.push(this.DataService.list({group: 'extensions', resource: 'replicasets'}, context)); 30 | promises.push(this.DataService.list({group: 'apps', resource: 'statefulsets'}, context)); 31 | 32 | this.$q.all(promises).then(_.spread((deploymentConfigData: any, replicationControllerData: any, deploymentData: any, replicaSetData: any, statefulSetData: any) => { 33 | var deploymentConfigs: any = _.toArray(deploymentConfigData.by('metadata.name')); 34 | var replicationControllers: any = _.reject(replicationControllerData.by('metadata.name'), this.$filter('hasDeploymentConfig')); 35 | var deployments: any = _.toArray(deploymentData.by('metadata.name')); 36 | var replicaSets: any = _.reject(replicaSetData.by('metadata.name'), this.$filter('hasDeployment')); 37 | var statefulSets: any = _.toArray(statefulSetData.by('metadata.name')); 38 | 39 | var apiObjects: any = deploymentConfigs.concat(deployments) 40 | .concat(replicationControllers) 41 | .concat(replicaSets) 42 | .concat(statefulSets); 43 | deferred.resolve(_.sortBy(apiObjects, ['metadata.name', 'kind'])); 44 | }), function(e: any) { 45 | deferred.reject(e); 46 | }); 47 | 48 | return deferred.promise; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/mockServices/mockAuth.service.ts: -------------------------------------------------------------------------------- 1 | import * as angular from 'angular'; 2 | 3 | interface IAuthService { 4 | /** User Login Verification. */ 5 | withUser() : angular.IPromise < any >; 6 | } 7 | 8 | /** Backend service communications. */ 9 | export class AuthService implements IAuthService { 10 | public static $inject = ['$rootScope', '$q']; 11 | 12 | private $rootScope : any; 13 | private $q : any; 14 | 15 | constructor ($rootScope: any, $q: angular.IQService) { 16 | this.$rootScope = $rootScope; 17 | this.$q = $q; 18 | } 19 | 20 | public withUser () { 21 | var user: any = { 22 | apiVersion: 'v1', 23 | groups: null, 24 | identities: ['anypassword:dev'], 25 | kind: 'User', 26 | metadata: { 27 | creationTimestamp: '2017-02-28T13:54:53Z', 28 | name: 'mock developer', 29 | resourceVersion: '1127', 30 | selfLink: '/oapi/v1/usersdev', 31 | uid: '7afe3d30-fdbd-11e6-a552-080027242396' 32 | } 33 | }; 34 | this.$rootScope.user = user; 35 | 36 | return this.$q.when(user); 37 | }; 38 | 39 | public isLoggedIn() { 40 | return false; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/mockServices/mockAuthCfg.constant.ts: -------------------------------------------------------------------------------- 1 | export class AuthCfgConstant { 2 | constructor () { 3 | return { 4 | oauth_authorize_uri: "", 5 | oauth_redirect_base: "", 6 | oauth_client_id: "openshift-web-console", 7 | logout_uri: "" 8 | }; 9 | } 10 | } 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/mockServices/mockAuthorization.service.ts: -------------------------------------------------------------------------------- 1 | interface IAuthorizationService { 2 | canIAddToProject(projectName: string) : boolean; 3 | } 4 | import * as angular from 'angular'; 5 | 6 | export class AuthorizationService implements IAuthorizationService { 7 | public static $inject = ['$q']; 8 | 9 | private $q : any; 10 | 11 | constructor ($q: angular.IQService) { 12 | this.$q = $q; 13 | } 14 | 15 | public getProjectRules() : angular.IPromise < any > { 16 | return this.$q.when({}); 17 | } 18 | 19 | public canIAddToProject (projectName: string) { 20 | // can add to any project except 'my-proj-a' 21 | return !(projectName === 'my-proj-a'); 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /app/mockServices/mockBinding.service.ts: -------------------------------------------------------------------------------- 1 | import * as angular from 'angular'; 2 | import * as _ from 'lodash'; 3 | 4 | interface IBindingService { 5 | bindingResource: any; 6 | bindService(serviceInstance: any, application: any, serviceClass: any, parameters: any) : angular.IPromise < any >; 7 | getServiceClassForInstance(serviceInstance: any, serviceClasses: any) : any; 8 | isServiceBindable(serviceInstance: any, serviceClasses: any) : boolean; 9 | } 10 | 11 | export class BindingService implements IBindingService { 12 | public static $inject = ['$q', '$timeout']; 13 | 14 | public bindingResource = { 15 | group: 'servicecatalog.k8s.io', 16 | resource: 'serviceinstancecredentials' 17 | }; 18 | 19 | private $q: any; 20 | private $timeout: any; 21 | 22 | constructor ($q: angular.IQService, $timeout: any) { 23 | this.$q = $q; 24 | this.$timeout = $timeout; 25 | } 26 | 27 | public getServiceClassForInstance (serviceInstance: any, serviceClasses: any) { 28 | let serviceClassName = _.get(serviceInstance, 'spec.serviceClassName'); 29 | return _.get(serviceClasses, [serviceClassName]); 30 | } 31 | 32 | public makeParametersSecret(secretName: any, parameters: any, owner: any) { 33 | var secret: any = { 34 | apiVersion: 'v1', 35 | kind: 'Secret', 36 | metadata: { 37 | name: secretName, 38 | ownerReferences: [{ 39 | apiVersion: owner.apiVersion, 40 | kind: owner.kind, 41 | name: owner.metadata.name, 42 | uid: owner.metadata.uid, 43 | controller: false, 44 | blockOwnerDeletion: false 45 | }] 46 | }, 47 | type: 'Opaque', 48 | stringData: { 49 | parameters: JSON.stringify(parameters) 50 | } 51 | }; 52 | 53 | return secret; 54 | } 55 | 56 | public generateSecretName (prefix: string) { 57 | return prefix + '_shhhh_ima_secret'; 58 | } 59 | 60 | public bindService (serviceInstance: any, application: any, serviceClass: any, parameters: any): angular.IPromise < any > { 61 | let deferred = this.$q.defer(); 62 | 63 | var data: any = {}; 64 | 65 | this.$timeout(() => { 66 | deferred.resolve(data); 67 | }, 300); 68 | 69 | return deferred.promise; 70 | } 71 | 72 | public isServiceBindable(serviceInstance: any, serviceClasses: any) { 73 | let serviceClass: any = this.getServiceClassForInstance(serviceInstance, serviceClasses); 74 | if (!serviceClass) { 75 | return !!serviceInstance; 76 | } 77 | 78 | let planName = _.get(serviceInstance, 'clusterServicePlanRef.name'); 79 | let plan = _.find(serviceClass.plans, { name: planName }); 80 | let planBindable = _.get(plan, 'bindable'); 81 | if (planBindable === true) { 82 | return true; 83 | } 84 | 85 | if (planBindable === false) { 86 | return false; 87 | } 88 | 89 | return serviceClass.bindable; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /app/mockServices/mockConstants.service.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | 3 | export class ConstantsService { 4 | public constants: any; 5 | constructor () { 6 | let win: any = window; 7 | 8 | let publisherSynonyms = { 9 | 'Vendor B': 'Vendor B, Inc.', 10 | 'Vendor B, Inc': 'Vendor B, Inc.', 11 | 'Vendor B, Co.': 'Vendor B, Inc.' 12 | }; 13 | _.set(win, 'OPENSHIFT_CONSTANTS.PUBLISHER_SYNONYMS', publisherSynonyms); 14 | 15 | return _.clone(win.OPENSHIFT_CONSTANTS || {}); 16 | } 17 | } 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/mockServices/mockData/deployments.ts: -------------------------------------------------------------------------------- 1 | export const deploymentsData = [ 2 | ]; 3 | -------------------------------------------------------------------------------- /app/mockServices/mockData/instances.ts: -------------------------------------------------------------------------------- 1 | export const instanceData = [{ 2 | "metadata": { 3 | "name": "user-provided-service-vd9np", 4 | "generateName": "user-provided-service-", 5 | "namespace": "node-test", 6 | "selfLink": "/apis/servicecatalog.k8s.io/v1alpha1/namespaces/node-test/instances/user-provided-service-vd9np", 7 | "uid": "5f6198bb-409d-11e7-828d-0242ac11000d", 8 | "resourceVersion": "271", 9 | "creationTimestamp": "2017-05-24T16:23:51Z", 10 | "finalizers": [ 11 | "kubernetes" 12 | ] 13 | }, 14 | "spec": { 15 | "serviceClassName": "user-provided-service", 16 | "planName": "default", 17 | "osbGuid": "0ca410ef-631e-44d3-8e47-95d1062ee02d", 18 | "checksum": "762ee664f0c71e621d8fef5487f615afbdd2bd080443984b1c64d6d956eaf0b5" 19 | }, 20 | "status": { 21 | "conditions": [ 22 | { 23 | "type": "Ready", 24 | "status": "True", 25 | "lastTransitionTime": "2017-05-24T16:23:51Z", 26 | "reason": "ProvisionedSuccessfully", 27 | "message": "The instance was provisioned successfully" 28 | } 29 | ] 30 | } 31 | }]; 32 | -------------------------------------------------------------------------------- /app/mockServices/mockData/projects.ts: -------------------------------------------------------------------------------- 1 | export const projectsData = [ 2 | { 3 | "metadata": { 4 | "name": "svc-cat", 5 | "selfLink": "/oapi/v1/projectssvc-cat", 6 | "uid": "40f7a2df-0e78-11e7-ad0b-080027242396", 7 | "resourceVersion": "1398", 8 | "creationTimestamp": "2017-02-21T20:52:10Z", 9 | "annotations": { 10 | "openshift.io/description": "", 11 | "openshift.io/display-name": "Service Catalog API Server", 12 | "openshift.io/requester": "dev", 13 | "openshift.io/sa.scc.mcs": "s0:c7,c4", 14 | "openshift.io/sa.scc.supplemental-groups": "1000050000/10000", 15 | "openshift.io/sa.scc.uid-range": "1000050000/10000" 16 | } 17 | }, 18 | "spec": { 19 | "finalizers": ["openshift.io/origin", "kubernetes"] 20 | }, 21 | "status": { 22 | "phase": "Active" 23 | }, 24 | "kind": "Project", 25 | "apiVersion": "v1" 26 | }, 27 | { 28 | "metadata": { 29 | "name": "my-proj-a", 30 | "selfLink": "/oapi/v1/projectsmy-proj-a", 31 | "uid": "40f7a2df-0e78-11e7-ad0b-080027241116", 32 | "resourceVersion": "1398", 33 | "creationTimestamp": "2017-03-21T20:52:10Z", 34 | "annotations": { 35 | "openshift.io/description": "", 36 | "openshift.io/display-name": "My Project A", 37 | "openshift.io/requester": "dev", 38 | "openshift.io/sa.scc.mcs": "s0:c7,c4", 39 | "openshift.io/sa.scc.supplemental-groups": "1000050000/10000", 40 | "openshift.io/sa.scc.uid-range": "1000050000/10000" 41 | } 42 | }, 43 | "spec": { 44 | "finalizers": ["openshift.io/origin", "kubernetes"] 45 | }, 46 | "status": { 47 | "phase": "Active" 48 | }, 49 | "kind": "Project", 50 | "apiVersion": "v1" 51 | }, 52 | { 53 | "metadata": { 54 | "name": "my-proj-b", 55 | "selfLink": "/oapi/v1/projectsmy-proj-b", 56 | "uid": "40f7a2df-0e78-11e7-ad0b-080011241116", 57 | "resourceVersion": "1398", 58 | "creationTimestamp": "2017-04-01T20:52:10Z", 59 | "annotations": { 60 | "openshift.io/description": "", 61 | "openshift.io/display-name": "My Project B", 62 | "openshift.io/requester": "dev", 63 | "openshift.io/sa.scc.mcs": "s0:c7,c4", 64 | "openshift.io/sa.scc.supplemental-groups": "1000050000/10000", 65 | "openshift.io/sa.scc.uid-range": "1000050000/10000" 66 | } 67 | }, 68 | "spec": { 69 | "finalizers": ["openshift.io/origin", "kubernetes"] 70 | }, 71 | "status": { 72 | "phase": "Active" 73 | }, 74 | "kind": "Project", 75 | "apiVersion": "v1" 76 | } 77 | ]; 78 | -------------------------------------------------------------------------------- /app/mockServices/mockData/replicaSets.ts: -------------------------------------------------------------------------------- 1 | export const replicaSetsData = [ 2 | ]; 3 | -------------------------------------------------------------------------------- /app/mockServices/mockData/replicationControllers.ts: -------------------------------------------------------------------------------- 1 | export const replicationControllersData = [ 2 | ]; 3 | -------------------------------------------------------------------------------- /app/mockServices/mockData/statefulSets.ts: -------------------------------------------------------------------------------- 1 | export const statefulSetsData = [ 2 | ]; 3 | -------------------------------------------------------------------------------- /app/mockServices/mockKeywords.service.ts: -------------------------------------------------------------------------------- 1 | import * as angular from 'angular'; 2 | import * as _ from 'lodash'; 3 | 4 | interface IKeywordsService { 5 | generateKeywords(filterText: string): any; 6 | filterForKeywords(objects: any, filterFields: string[], keywords: any[]); 7 | weightedSearch(objects: any, filterFields: string[], keywords: any[]); 8 | } 9 | 10 | export class KeywordService implements IKeywordsService { 11 | public generateKeywords(filterText: string) { 12 | if (!filterText) { 13 | return []; 14 | } 15 | 16 | var keywords = _.uniq(filterText.match(/\S+/g)); 17 | 18 | // Sort the longest keyword first. 19 | keywords.sort(function(a: string, b: string){ 20 | return b.length - a.length; 21 | }); 22 | 23 | // Convert the keyword to a case-insensitive regular expression for the filter. 24 | return _.map(keywords, function(keyword: string) { 25 | return new RegExp(_.escapeRegExp(keyword), "i"); 26 | }); 27 | } 28 | 29 | public filterForKeywords(objects: any, filterFields: string[], keywords: any[]) { 30 | var filteredObjects = objects; 31 | if (_.isEmpty(keywords)) { 32 | return filteredObjects; 33 | } 34 | 35 | // Find resources that match all keywords. 36 | angular.forEach(keywords, function(regex: any) { 37 | var matchesKeyword = function(obj: any) { 38 | var i; 39 | for (i = 0; i < filterFields.length; i++) { 40 | var value = _.get(obj, filterFields[i]); 41 | if (value && regex.test(value)) { 42 | return true; 43 | } 44 | } 45 | 46 | return false; 47 | }; 48 | 49 | filteredObjects = _.filter(filteredObjects, matchesKeyword); 50 | }); 51 | return filteredObjects; 52 | } 53 | 54 | public weightedSearch(objects: any, filterFields: string[], keywords: any[]) { 55 | // Don't rewrite the weighted search function for mock data. Just do basic filtering. 56 | const fields = _.map(filterFields, 'path') as string[]; 57 | return this.filterForKeywords(objects, fields, keywords); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/mockServices/mockLogger.service.ts: -------------------------------------------------------------------------------- 1 | interface ILoggerService { 2 | get(name: string): any; 3 | log(...args: any[]): any; 4 | info(...args: any[]): any; 5 | debug(...args: any[]): any; 6 | warn(...args: any[]): any; 7 | error(...args: any[]): any; 8 | } 9 | 10 | export class LoggerService implements ILoggerService { 11 | 12 | public get(name: string) { 13 | return this; 14 | } 15 | 16 | public log(...args: any[]) { 17 | console.log(args); 18 | } 19 | 20 | public info(...args: any[]) { 21 | console.log(args); 22 | } 23 | 24 | public debug(...args: any[]) { 25 | console.log(args); 26 | } 27 | 28 | public warn(...args: any[]) { 29 | console.log(args); 30 | } 31 | 32 | public error(...args: any[]) { 33 | console.log(args); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/mockServices/mockProjects.service.ts: -------------------------------------------------------------------------------- 1 | import * as angular from 'angular'; 2 | 3 | interface IProjectsService { 4 | canCreate(): angular.IPromise < any >; 5 | get(projectName: string): angular.IPromise < any >; 6 | list(): angular.IPromise < any >; 7 | watch(context: any, callback: any): any; 8 | isProjectListIncomplete(): boolean; 9 | create(projectName: string, displayName: string, description: string): angular.IPromise < any >; 10 | delete(projectName: string): angular.IPromise < any >; 11 | update(projectName: string, data: any): angular.IPromise < any >; 12 | } 13 | 14 | export class ProjectsService implements IProjectsService { 15 | public static $inject = ['$q', 'DataService']; 16 | 17 | private $q : any; 18 | private DataService: any; 19 | 20 | constructor ($q: angular.IQService, DataService: any) { 21 | this.$q = $q; 22 | this.DataService = DataService; 23 | } 24 | 25 | public canCreate() { 26 | let deferred = this.$q.defer(); 27 | 28 | setTimeout(() => { 29 | deferred.resolve(true); 30 | }, 300); 31 | 32 | return deferred.promise; 33 | } 34 | 35 | public get(projectName: string) { 36 | let deferred = this.$q.defer(); 37 | var project: any = {}; 38 | var context: any = {}; 39 | 40 | setTimeout(() => { 41 | deferred.resolve([project, context]); 42 | }, 300); 43 | 44 | return deferred.promise; 45 | } 46 | 47 | public update(projectName: string, data: any) { 48 | let deferred = this.$q.defer(); 49 | var project: any = {}; 50 | var context: any = {}; 51 | 52 | setTimeout(() => { 53 | deferred.resolve([project, context]); 54 | }, 300); 55 | 56 | return deferred.promise; 57 | } 58 | 59 | public list() { 60 | return this.DataService.list("projects", {}); 61 | } 62 | 63 | public watch(context: any, callback: any): any { 64 | return this.DataService.watch("projects", context, callback); 65 | } 66 | 67 | public isProjectListIncomplete() { 68 | return false; 69 | } 70 | 71 | public create(name: string, displayName: string, desc: string): angular.IPromise < any > { 72 | let deferred = this.$q.defer(); 73 | let context: any = {}; 74 | 75 | let project: any = { 76 | apiVersion: "v1", 77 | kind: "Project", 78 | metadata: { 79 | name: name, 80 | annotations: {} 81 | } 82 | }; 83 | 84 | if (displayName) { 85 | project.metadata.annotations["openshift.io/display-name"] = displayName; 86 | } 87 | 88 | if (desc) { 89 | project.metadata.annotations["openshift.io/description"] = desc; 90 | } 91 | 92 | setTimeout(() => { 93 | deferred.resolve([project, context]); 94 | }, 300); 95 | 96 | return deferred.promise; 97 | } 98 | 99 | public delete(project: any) { 100 | return this.DataService.delete("projects", project.metadata.name, {}); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /app/mockServices/mockRecentlyViewedProjects.service.ts: -------------------------------------------------------------------------------- 1 | interface IRecentlyViewedProjectsService { 2 | orderByMostRecentlyViewed(projects: any): any; 3 | } 4 | 5 | export class RecentlyViewedProjectsService implements IRecentlyViewedProjectsService { 6 | public orderByMostRecentlyViewed(projects: any) { 7 | return projects; 8 | } 9 | public isRecentlyViewed(uid: string) { 10 | return uid === '40f7a2df-0e78-11e7-ad0b-080027242396' ? true : false; 11 | } 12 | } 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/mockServices/mockServices.module.ts: -------------------------------------------------------------------------------- 1 | import * as angular from 'angular'; 2 | import {ApplicationsService} from './mockApplications.service'; 3 | import {AuthService} from './mockAuth.service'; 4 | import {AuthCfgConstant} from './mockAuthCfg.constant'; 5 | import {AuthorizationService} from './mockAuthorization.service'; 6 | import {LoggerService} from './mockLogger.service'; 7 | import {APIService} from './mockAPI.service'; 8 | import {BindingService} from './mockBinding.service'; 9 | import {ConstantsService} from './mockConstants.service'; 10 | import {DataService} from './mockData.service'; 11 | import {ProjectsService} from './mockProjects.service'; 12 | import {AlertMessageService} from './mockAlertMessage.service'; 13 | import {KeywordService} from './mockKeywords.service'; 14 | import {RecentlyViewedProjectsService} from './mockRecentlyViewedProjects.service'; 15 | import {VersionsService} from './mockVersions.service'; 16 | 17 | export class MockServicesModule { 18 | 19 | static $inject = ['$window']; 20 | 21 | public moduleName: string = 'mockServices'; 22 | private $window: any; 23 | 24 | constructor($window: any) { 25 | this.$window = $window; 26 | 27 | angular 28 | .module(this.moduleName, []) 29 | .factory('Constants', ConstantsService) 30 | .constant('AUTH_CFG', AuthCfgConstant) 31 | .service('ApplicationsService', ApplicationsService) 32 | .service('AuthService', AuthService) 33 | .service('AuthorizationService', AuthorizationService) 34 | .service('APIService', APIService) 35 | .service('BindingService', BindingService) 36 | .service('ProjectsService', ProjectsService) 37 | .service('AlertMessageService', AlertMessageService) 38 | .service('Logger', LoggerService) 39 | .service('DataService', DataService) 40 | .service('KeywordService', KeywordService) 41 | .service('RecentlyViewedProjectsService', RecentlyViewedProjectsService) 42 | .service('VersionsService', VersionsService); 43 | } 44 | 45 | public useMockServices() { 46 | return this.$window.MOCK_ORIGIN_SERVICES; 47 | } 48 | } 49 | 50 | 51 | -------------------------------------------------------------------------------- /app/mockServices/mockVersions.service.ts: -------------------------------------------------------------------------------- 1 | interface IVersionsService { 2 | compare(v1: string, v2: string): number; 3 | rcompare(v1: string, v2: string): number; 4 | } 5 | 6 | export class VersionsService implements IVersionsService { 7 | // Don't reimplement this for mock data. Just use the order in the data. 8 | public compare(v1: string, v2: string): number { 9 | return 0; 10 | } 11 | public rcompare(v1: string, v2: string): number { 12 | return 0; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/pages/error/error-page.html: -------------------------------------------------------------------------------- 1 |
2 |

Error

3 |

{{$ctrl.errorMessage}}

4 |
{{$ctrl.errorDetails}}
5 |
6 | {{link.label}} 7 | | 8 |
9 |
10 |
Return to the console.
11 |
12 | -------------------------------------------------------------------------------- /app/pages/error/errorPage.ts: -------------------------------------------------------------------------------- 1 | import {ErrorPageController} from './errorPageController'; 2 | 3 | export const errorPage: angular.IComponentOptions = { 4 | controller: ErrorPageController, 5 | template: require('./error-page.html') 6 | }; 7 | -------------------------------------------------------------------------------- /app/pages/error/errorPageController.ts: -------------------------------------------------------------------------------- 1 | let URI = require('urijs'); 2 | 3 | export class ErrorPageController { 4 | 5 | static $inject = ['$window', 'Logger']; 6 | 7 | public ctrl: any = this; 8 | private $window : any; 9 | private logger: any; 10 | 11 | constructor($window: any, Logger: any) { 12 | this.$window = $window; 13 | this.logger = Logger; 14 | }; 15 | 16 | public $onInit() { 17 | var params = URI(window.location.href).query(true); 18 | var error = params.error; 19 | var win: any = window; 20 | 21 | switch (error) { 22 | case 'access_denied': 23 | this.ctrl.errorMessage = "Access denied"; 24 | break; 25 | case 'not_found': 26 | this.ctrl.errorMessage = "Not found"; 27 | break; 28 | case 'invalid_request': 29 | this.ctrl.errorMessage = "Invalid request"; 30 | break; 31 | case 'API_DISCOVERY': 32 | this.ctrl.errorLinks = [{ 33 | href: window.location.protocol + "//" + win.OPENSHIFT_CONFIG.api.openshift.hostPort + win.OPENSHIFT_CONFIG.api.openshift.prefix, 34 | label: "Check Server Connection", 35 | target: "_blank" 36 | }]; 37 | break; 38 | default: 39 | this.ctrl.errorMessage = "An error has occurred"; 40 | } 41 | 42 | if (params.error_description) { 43 | this.ctrl.errorDetails = params.error_description; 44 | } 45 | }; 46 | 47 | public reloadConsole() { 48 | this.logger.log("reloading console"); 49 | this.$window.location.href = "/"; 50 | }; 51 | } 52 | -------------------------------------------------------------------------------- /app/pages/home/home-page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 |
9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | -------------------------------------------------------------------------------- /app/pages/home/homePage.ts: -------------------------------------------------------------------------------- 1 | import {HomePageController} from './homePageController'; 2 | 3 | export const homePage: angular.IComponentOptions = { 4 | controller: HomePageController, 5 | template: require('./home-page.html') 6 | }; 7 | -------------------------------------------------------------------------------- /app/pages/home/homePageController.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | 3 | export class HomePageController { 4 | static $inject = ['$rootScope', '$state', '$timeout', 'AuthService', 'Catalog', 'Constants', 'GuidedTourService', 'HTMLService', 'NotificationsService']; 5 | 6 | public ctrl: any = this; 7 | private $rootScope: any; 8 | private $state: any; 9 | private $timeout: any; 10 | private authService: any; 11 | private Catalog: any; 12 | private GuidedTourService: any; 13 | private HTMLService: any; 14 | private constants: any; 15 | private tourConfig: any; 16 | private NotificationsService: any; 17 | 18 | constructor($rootScope: any, $state: any, $timeout: any, AuthService: any, Catalog: any, 19 | Constants: any, GuidedTourService: any, HTMLService: any, NotificationsService: any) { 20 | this.$rootScope = $rootScope; 21 | this.$state = $state; 22 | this.$timeout = $timeout; 23 | this.authService = AuthService; 24 | this.Catalog = Catalog; 25 | this.GuidedTourService = GuidedTourService; 26 | this.HTMLService = HTMLService; 27 | this.constants = Constants; 28 | this.NotificationsService = NotificationsService; 29 | }; 30 | 31 | public $onInit() { 32 | this.tourConfig = _.get(this.constants, 'GUIDED_TOURS.landing_page_tour'); 33 | 34 | // Assume a development environment for the console. Remove the trailing slash if set. 35 | let consoleBaseUrl = _.get(window, 'OPENSHIFT_CONSOLE_BASE_URL', 'https://localhost:9000/dev-console').replace(/\/$/, ''); 36 | this.ctrl.baseProjectUrl = consoleBaseUrl + "/project"; 37 | this.ctrl.projectsUrl = consoleBaseUrl + "/projects"; 38 | this.authService.withUser().then(() => { 39 | this.update(); 40 | }); 41 | }; 42 | 43 | public update() { 44 | this.Catalog.getCatalogItems(false).then( _.spread((catalogServiceItems: any, errorMessage: any) => { 45 | this.ctrl.catalogItems = catalogServiceItems; 46 | 47 | if (errorMessage) { 48 | this.NotificationsService.addNotification({ 49 | type: "error", 50 | message: errorMessage 51 | }); 52 | } 53 | 54 | if (_.get(this, 'tourConfig.auto_launch')) { 55 | // Check if this is the first time this user has visited the home page, if so launch the tour 56 | var viewedHomePageKey: string = "openshift/viewedHomePage/" + this.$rootScope.user.metadata.name; 57 | if (localStorage.getItem(viewedHomePageKey) !== 'true') { 58 | this.$timeout(() => { 59 | if (this.startGuidedTour()) { 60 | localStorage.setItem(viewedHomePageKey, 'true'); 61 | } 62 | }, 500); 63 | } 64 | } 65 | }), () => { 66 | this.ctrl.catalogItems = {}; 67 | }); 68 | 69 | this.ctrl.saasOfferings = this.constants.SAAS_OFFERINGS; 70 | }; 71 | 72 | public startGuidedTour = () : boolean => { 73 | if (this.HTMLService.isWindowBelowBreakpoint(this.HTMLService.WINDOW_SIZE_SM)) { 74 | return false; 75 | } 76 | 77 | if (!this.tourConfig || !this.tourConfig.enabled || !this.tourConfig.steps) { 78 | return false; 79 | } 80 | 81 | this.GuidedTourService.startTour(this.tourConfig.steps); 82 | return true; 83 | }; 84 | } 85 | -------------------------------------------------------------------------------- /app/pages/logout/logout-page.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

Log out

5 |
6 |
7 |
8 |
9 | -------------------------------------------------------------------------------- /app/pages/logout/logoutPage.ts: -------------------------------------------------------------------------------- 1 | import {LogoutPageController} from './logoutPageController'; 2 | 3 | export const logoutPage: angular.IComponentOptions = { 4 | controller: LogoutPageController, 5 | template: require('./logout-page.html') 6 | }; 7 | -------------------------------------------------------------------------------- /app/pages/logout/logoutPageController.ts: -------------------------------------------------------------------------------- 1 | export class LogoutPageController { 2 | 3 | static $inject = ['Logger', 'AuthService', 'AUTH_CFG']; 4 | 5 | public ctrl: any = this; 6 | private Logger: any; 7 | private AuthService: any; 8 | private AUTH_CFG: any; 9 | 10 | constructor(Logger: any, AuthService: any, AUTH_CFG: any) { 11 | this.Logger = Logger; 12 | this.AuthService = AuthService; 13 | this.AUTH_CFG = AUTH_CFG; 14 | }; 15 | 16 | public $onInit() { 17 | if (this.AuthService.isLoggedIn()) { 18 | this.Logger.debug("LogoutController, logged in, initiating logout"); 19 | this.ctrl.logoutMessage = "Logging out..."; 20 | 21 | this.AuthService.onLogout(this.loggedOut); 22 | this.AuthService.startLogout(); 23 | } else if (this.AUTH_CFG.logout_uri) { 24 | this.Logger.debug("LogoutController, logout completed, redirecting to AUTH_CFG.logout_uri", this.AUTH_CFG.logout_uri); 25 | this.ctrl.logoutMessage = "Logging out..."; 26 | window.location.href = this.AUTH_CFG.logout_uri; 27 | } else { 28 | this.Logger.debug("LogoutController, not logged in, logout complete"); 29 | this.ctrl.logoutMessage = 'You are logged out. Return to the console.'; 30 | } 31 | } 32 | 33 | private loggedOut = () => { 34 | if (this.AUTH_CFG.logout_uri) { 35 | this.Logger.debug("LogoutController, logout completed, redirecting to AUTH_CFG.logout_uri", this.AUTH_CFG.logout_uri); 36 | window.location.href = this.AUTH_CFG.logout_uri; 37 | } else { 38 | this.Logger.debug("LogoutController, logout completed, reloading the page"); 39 | window.location.reload(false); 40 | } 41 | } 42 | } 43 | 44 | -------------------------------------------------------------------------------- /app/routes.ts: -------------------------------------------------------------------------------- 1 | export default routesConfig; 2 | 3 | /** @ngInject */ 4 | function routesConfig($stateProvider: angular.ui.IStateProvider, $urlRouterProvider: angular.ui.IUrlRouterProvider, $locationProvider: angular.ILocationProvider) { 5 | $locationProvider.html5Mode(true).hashPrefix('!'); 6 | $urlRouterProvider.otherwise('/home'); 7 | 8 | $stateProvider 9 | .state('oauth', { 10 | url: '/oauth', 11 | component: 'oauth' 12 | }) 13 | .state('home', { 14 | url: '/home', 15 | component: 'homepage' 16 | }) 17 | .state('logout', { 18 | url: '/logout', 19 | component: 'logoutpage' 20 | }) 21 | .state('error', { 22 | url: '/error', 23 | component: 'errorpage' 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /app/styles/scrollbars.less: -------------------------------------------------------------------------------- 1 | // Mimick scrollbars from origin-web-console 2 | @console-bright-blue: #00a8e1; 3 | @sidebar-os-border-color: #050505; 4 | 5 | @scrollbar-thumb: rgba(0,0,0,.08); 6 | @scrollbar-thumb-inverse: rgba(255,255,255,.25); 7 | @scrollbar-thumb-hover: rgba(0,0,0,.18); 8 | @scrollbar-thumb-hover-inverse: rgba(255,255,255,.35); 9 | @scrollbar-track: rgba(0,0,0,.03); 10 | @scrollbar-track-inverse: @sidebar-os-border-color; 11 | @scrollbar-track-inverse-alt: rgba(255,255,255,.1); 12 | @scrollbar-track-landing: #042e43; 13 | @scrollbar-track-landing-side-bar: darken(@landing-side-bar-bg, 5%); 14 | @scrollbar-width: 15px; 15 | 16 | 17 | ::-webkit-scrollbar { 18 | height: 10px; 19 | overflow: visible; 20 | width: @scrollbar-width; 21 | } 22 | 23 | ::-webkit-scrollbar-corner { 24 | background: transparent; 25 | } 26 | 27 | ::-webkit-scrollbar-thumb { 28 | background-clip: padding-box; 29 | background-color: @scrollbar-thumb; 30 | border: solid transparent; 31 | border-width: 1px 1px 1px 1px; 32 | box-shadow: inset 1px 1px 0 rgba(0,0,0,.1),inset 0 -1px 0 rgba(0,0,0,.07); 33 | max-height: 60px; 34 | min-height: 28px; 35 | padding: 100px 0 0; 36 | &:active, 37 | &:hover { 38 | background-color: @scrollbar-thumb-hover; 39 | } 40 | } 41 | 42 | ::-webkit-scrollbar-track { 43 | background-clip:padding-box; 44 | background-color: @scrollbar-track; 45 | } 46 | 47 | .chromeless .middle, 48 | .landing, 49 | .landing-side-bar { 50 | &::-webkit-scrollbar-thumb { 51 | background-color: @scrollbar-thumb-inverse; 52 | box-shadow: inset 1px 1px 0 rgba(255,255,255,.1),inset 0 -1px 0 rgba(255,255,255,.07); 53 | &:active, 54 | &:hover { 55 | background-color: @scrollbar-thumb-hover-inverse; 56 | } 57 | } 58 | } 59 | 60 | .landing::-webkit-scrollbar-track { 61 | background-color: @scrollbar-track-landing; 62 | } 63 | 64 | .landing-side-bar::-webkit-scrollbar-track { 65 | background-color: @scrollbar-track-landing-side-bar; 66 | } 67 | -------------------------------------------------------------------------------- /app/styles/variables.less: -------------------------------------------------------------------------------- 1 | @oli-font-path: "../fonts"; 2 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "origin-web-catalog", 3 | "version": "3.10.0", 4 | "license": "Apache-2.0", 5 | "main": [ 6 | "dist/vendor-bundle.js", 7 | "dist/origin-web-catalogs.js", 8 | "dist/origin-web-catalogs.css" 9 | ], 10 | "ignore": [ 11 | ".babelrc", 12 | ".bowerrc", 13 | ".editorconfig", 14 | ".eslintignore", 15 | ".elsintrc", 16 | ".gitignore", 17 | ".travis.yml", 18 | "karma.conf.js", 19 | "tsconfig.json", 20 | "tslint.json", 21 | "webpack.config.js", 22 | "app", 23 | "bower_components", 24 | "config", 25 | "coverage", 26 | "hack", 27 | "node_modules", 28 | "src", 29 | "test" 30 | ], 31 | "dependencies": { 32 | "angular": "1.5.11", 33 | "angular-moment": "1.0.0", 34 | "angular-resource": "1.5.11", 35 | "angular-sanitize": "1.5.11", 36 | "angular-route": "1.5.11", 37 | "angular-bootstrap": "0.14.3", 38 | "angular-patternfly": ">=4.11.8 <5.0.0", 39 | "angular-schema-form": "^0.8.13", 40 | "patternfly": ">=3.29.3 <4.0.0", 41 | "jquery": "~3.2.1", 42 | "lodash": "~4.17.4", 43 | "origin-web-common": ">=3.10.0-alpha.1 <3.11.0", 44 | "angular-schema-form-bootstrap": "^0.2.0" 45 | }, 46 | "devDependencies": { 47 | "ace-builds": "1.2.2", 48 | "angular-extension-registry": "1.2.6", 49 | "angular-inview": "1.5.7", 50 | "angular-ui-ace": "0.2.3", 51 | "angular-utf8-base64": "0.0.5", 52 | "ansi_up": "1.3.0", 53 | "bootstrap-hover-dropdown": "2.1.3", 54 | "clipboard": "1.5.8", 55 | "es5-dom-shim": "*", 56 | "es5-shim": "3.1.1", 57 | "file-saver": "1.3.3", 58 | "json3": "3.3.2", 59 | "js-yaml": "3.6.1", 60 | "matchHeight": "0.7.0", 61 | "moment": "~2.18.1", 62 | "moment-timezone": "0.5.3", 63 | "ng-sortable": "1.3.4", 64 | "registry-image-widgets": "0.0.2", 65 | "ui-select": "angular-ui-select#0.19.4", 66 | "openshift-logos-icon": "^1.1.0" 67 | }, 68 | "overrides": { 69 | "angular": { 70 | "dependencies": { 71 | "jquery": "3.2.1" 72 | } 73 | }, 74 | "angular-patternfly": { 75 | "main": [ 76 | "dist/styles/angular-patternfly.css", 77 | "dist/angular-patternfly.js" 78 | ] 79 | } 80 | }, 81 | "resolutions": { 82 | "angular-bootstrap": "0.14.3", 83 | "moment": "~2.18.1", 84 | "jquery": "3.2.1", 85 | "font-awesome": "~4.7.0", 86 | "lodash": "~4.17.4", 87 | "matchHeight": "0.7.0", 88 | "bootstrap-select": "^1.10.0" 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /config/helpers.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const _root = path.resolve(__dirname, '..'); 3 | 4 | function root(args) { 5 | args = Array.prototype.slice.call(arguments, 0); 6 | return path.join.apply(path, [_root].concat(args)); 7 | } 8 | 9 | exports.root = root; 10 | -------------------------------------------------------------------------------- /config/webpack.common.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Modules 4 | var webpack = require('webpack'); 5 | var autoprefixer = require('autoprefixer'); 6 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 7 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 8 | var CopyWebpackPlugin = require('copy-webpack-plugin'); 9 | var ngAnnotatePlugin = require('ng-annotate-webpack-plugin'); 10 | 11 | var path = require('path'); 12 | 13 | /** 14 | * Env 15 | * Get npm lifecycle event to identify the environment 16 | * Example: When the `npm run build` command is executed, the ENV will be set to `build` 17 | */ 18 | 19 | var ENV = process.env.npm_lifecycle_event === undefined ? 'test' : process.env.npm_lifecycle_event; 20 | var isTest = ENV.indexOf('test') != -1; 21 | var isProd = ENV.indexOf('build') != -1; 22 | 23 | var prodEntry = { 24 | 'origin-web-catalogs': './src/index.ts', 25 | }; 26 | 27 | var prodExternals = { 28 | 'angular': 'angular', 29 | 'angular-patternfly': 'angular-patternfly', 30 | 'patternfly': 'patternfly', 31 | 'bootstrap': 'bootstrap', 32 | 'jquery': '$', 33 | 'lodash': '_', 34 | 'urijs': 'URI' 35 | }; 36 | 37 | var serverEntry = { 38 | 'catalogs-app': './app/app.ts' 39 | }; 40 | 41 | module.exports = { 42 | module: { 43 | loaders: [ 44 | { 45 | test: /.json$/, 46 | loaders: [ 47 | 'json-loader' 48 | ] 49 | }, 50 | { 51 | test: /.ts$/, 52 | exclude: /node_modules/, 53 | loader: 'tslint-loader', 54 | enforce: 'pre' 55 | }, 56 | { 57 | test: /\.ts$/, 58 | exclude: /node_modules/, 59 | loaders: [ 60 | 'ng-annotate-loader', 61 | 'ts-loader' 62 | ] 63 | }, 64 | { 65 | test: /\.(css|less)$/, 66 | exclude: /node_modules/, 67 | loader: ExtractTextPlugin.extract({fallback:'style-loader', use: 'css-loader!less-loader?indentedSyntax=true&sourceMap=true'}) 68 | }, 69 | { 70 | test: /\.js$/, 71 | loader: 'babel-loader', 72 | exclude: /node_modules/ 73 | }, 74 | { 75 | test: /\.(png|jpg|jpeg|gif)$/, 76 | loader: 'file-loader' 77 | }, 78 | { 79 | test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, 80 | loader: 'file-loader' 81 | }, 82 | { 83 | test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, 84 | loader: 'file-loader' 85 | }, 86 | { 87 | test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, 88 | loader: 'file-loader' 89 | }, 90 | { 91 | test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, 92 | loader: "file-loader" 93 | }, 94 | { 95 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, 96 | loader: 'file-loader' 97 | }, 98 | { 99 | test: /\.html$/, 100 | loader: 'html-loader' 101 | }, 102 | { 103 | test: /origin-web-common/, 104 | loader: [ 105 | 'imports-loader?Logger=js-logger/src/logger,angular,URI=urijs/src/URI,hopscotch=hopscotch/dist/js/hopscotch,this=>window', 106 | 'exports-loader?pluginLoader ' 107 | ] 108 | } 109 | ] 110 | } 111 | }; 112 | 113 | 114 | module.exports.entry = isProd ? prodEntry : serverEntry; 115 | 116 | module.exports.externals = isProd ? prodExternals : {}; 117 | 118 | /** 119 | * Plugins 120 | * Reference: http://webpack.github.io/docs/configuration.html#plugins 121 | * List: http://webpack.github.io/docs/list-of-plugins.html 122 | */ 123 | module.exports.plugins = [ 124 | new webpack.LoaderOptionsPlugin({ 125 | options: { 126 | postcss: () => [autoprefixer], 127 | resolve: {}, 128 | ts: { 129 | configFileName: 'tsconfig.json' 130 | }, 131 | tslint: { 132 | configuration: require('./../tslint.json') 133 | } 134 | }, 135 | debug: true 136 | }), 137 | new ExtractTextPlugin('[name].css'), 138 | new ngAnnotatePlugin({ 139 | add: true 140 | }), 141 | new webpack.ProvidePlugin({ 142 | jQuery: 'jquery', 143 | $: 'jquery', 144 | jquery: 'jquery', 145 | '_': 'lodash', 146 | 'URI': 'URI', 147 | 'OPENSHIFT_CONFIG': 'OPENSHIFT_CONFIG' 148 | }) 149 | ]; 150 | 151 | if (!isProd) { 152 | module.exports.plugins.push( 153 | new HtmlWebpackPlugin({ 154 | template: './app/index.html', 155 | inject: 'body' 156 | }) 157 | ); 158 | } 159 | 160 | // Add !test specific plugins 161 | if (!isTest) { 162 | module.exports.plugins.push( 163 | new webpack.optimize.CommonsChunkPlugin({ 164 | names: ['vendor-bundle'] 165 | }) 166 | ) 167 | } 168 | 169 | if (isProd) { 170 | module.exports.plugins.push( 171 | new webpack.optimize.OccurrenceOrderPlugin(), 172 | new webpack.NoEmitOnErrorsPlugin(), 173 | new CopyWebpackPlugin([ 174 | { 175 | from: './src/styles', 176 | to: 'less' 177 | } 178 | ]) 179 | ) 180 | } 181 | 182 | module.exports.resolve = { 183 | extensions: [ 184 | '.webpack.js', 185 | '.web.js', 186 | '.js', 187 | '.ts' 188 | ], 189 | modules: ["node_modules", "bower_components"], 190 | descriptionFiles: ["package.json", "bower.json"] 191 | }; 192 | 193 | 194 | // turn off performance warnings 195 | module.exports.performance = { hints: false }; 196 | 197 | // /** 198 | // * Dev server configuration 199 | // * Reference: http://webpack.github.io/docs/configuration.html#devserver 200 | // * Reference: http://webpack.github.io/docs/webpack-dev-server.html 201 | // */ 202 | // module.exports.devServer = { 203 | // contentBase: './src', 204 | // stats: 'minimal' 205 | // }; 206 | -------------------------------------------------------------------------------- /config/webpack.dev.js: -------------------------------------------------------------------------------- 1 | var webpackMerge = require('webpack-merge'); 2 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 3 | var commonConfig = require('./webpack.common.js'); 4 | var helpers = require('./helpers'); 5 | var FaviconsWebpackPlugin = require('favicons-webpack-plugin'); 6 | 7 | window = {}; 8 | 9 | try { 10 | require('../app/config.local.js'); 11 | } catch (e) { 12 | require('../app/config.js'); 13 | } 14 | 15 | var useHTTPS = !window.MOCK_ORIGIN_SERVICES; 16 | var port = window.DEV_SERVER_PORT || 9001; 17 | var devtoolMap = window.DEV_TOOL_SETTING || 'cheap-eval-source-map'; 18 | 19 | module.exports = webpackMerge(commonConfig, { 20 | devtool: devtoolMap, 21 | 22 | output: { 23 | path: helpers.root('dist'), 24 | publicPath: (useHTTPS ? 'https' : 'http') + '://localhost:' + port + '/', 25 | filename: '[name].js', 26 | chunkFilename: '[id].chunk.js' 27 | }, 28 | 29 | plugins: [ 30 | new ExtractTextPlugin('[name].css'), 31 | new FaviconsWebpackPlugin('./app/assets/img/favicon.png') 32 | ], 33 | 34 | devServer: { 35 | contentBase: './src', 36 | historyApiFallback: true, 37 | stats: 'minimal', 38 | port: port, 39 | https: useHTTPS 40 | } 41 | }); 42 | -------------------------------------------------------------------------------- /config/webpack.prod.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var webpackMerge = require('webpack-merge'); 3 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 4 | var commonConfig = require('./webpack.common.js'); 5 | var helpers = require('./helpers'); 6 | 7 | const ENV = process.env.NODE_ENV = process.env.ENV = 'production'; 8 | 9 | module.exports = webpackMerge(commonConfig, { 10 | devtool: 'source-map', 11 | 12 | output: { 13 | path: helpers.root('dist'), 14 | publicPath: '/', 15 | filename: '[name].js', 16 | chunkFilename: '[id].chunk.js' 17 | }, 18 | 19 | plugins: [ 20 | new webpack.optimize.UglifyJsPlugin({ // https://github.com/angular/angular/issues/10618 21 | compress: {}, 22 | mangle: {}, 23 | beautify: { 24 | beautify: true, 25 | indent_level: 0, // Don't waste characters indenting 26 | space_colon: false, // Don't waste characters 27 | width: 1000 28 | } 29 | }), 30 | new ExtractTextPlugin('[name].css'), 31 | new webpack.DefinePlugin({ 32 | 'process.env': { 33 | 'ENV': JSON.stringify(ENV) 34 | } 35 | }), 36 | new webpack.LoaderOptionsPlugin({ 37 | htmlLoader: { 38 | minimize: false // workaround for ng2 39 | } 40 | }) 41 | ] 42 | }); 43 | -------------------------------------------------------------------------------- /config/webpack.test.js: -------------------------------------------------------------------------------- 1 | var webpackMerge = require('webpack-merge'); 2 | var commonConfig = require('./webpack.common.js'); 3 | 4 | module.exports = webpackMerge(commonConfig, { 5 | entry: {}, 6 | devtool: 'inline-source-map' 7 | }); 8 | -------------------------------------------------------------------------------- /dist/less/animations.less: -------------------------------------------------------------------------------- 1 | @keyframes flash { 2 | 0% { opacity: 0; } 3 | 100% { opacity: 1; } 4 | } 5 | 6 | @keyframes modalBackdropIn { 7 | 0% { 8 | opacity: 0; 9 | } 10 | 100% { 11 | opacity: .5; 12 | } 13 | } 14 | @keyframes modalBackdropOut { 15 | 0% { 16 | opacity: .5; 17 | } 18 | 100% { 19 | opacity: 0; 20 | } 21 | } 22 | @keyframes modalSlideDown { 23 | 0% { 24 | opacity: .75; 25 | transform: translateY(-50px); 26 | } 27 | 100% { 28 | opacity: 1; 29 | transform: translateY(0); 30 | } 31 | } 32 | @keyframes modalSlideUp { 33 | 0% { 34 | opacity: .5; 35 | transform: translateY(0%); 36 | } 37 | 100% { 38 | opacity: 0; 39 | transform: translateY(-50px); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /dist/less/catalog.less: -------------------------------------------------------------------------------- 1 | // origin-web-catalog sans external dependency variables 2 | 3 | @import './animations.less'; 4 | @import './landing-page.less'; 5 | @import './mixins.less'; 6 | @import './catalogParameters.less'; 7 | @import './order-service.less'; 8 | @import './overlay-panel.less'; 9 | @import './projects-summary'; 10 | @import './saas-list.less'; 11 | @import './services-view.less'; 12 | @import './variables.less'; 13 | -------------------------------------------------------------------------------- /dist/less/catalogParameters.less: -------------------------------------------------------------------------------- 1 | .catalog-parameters { 2 | input.form-control[disabled] { 3 | background-color: transparent; 4 | border: 0; 5 | color: @text-color; 6 | cursor: default; 7 | margin-top: -3px; 8 | padding-left: 0; 9 | } 10 | &.readonly { 11 | .control-label { 12 | padding-right: 0; 13 | word-wrap: break-word; 14 | } 15 | 16 | form { 17 | margin-top: 10px; 18 | } 19 | 20 | .form-group { 21 | margin-bottom: 0; 22 | } 23 | 24 | fieldset { 25 | padding-left: 20px; 26 | > div { 27 | padding-left: 20px; 28 | } 29 | } 30 | legend { 31 | border: 0; 32 | font-size: @font-size-base; 33 | font-weight: 600; 34 | margin-bottom: 5px; 35 | } 36 | 37 | .schema-form-array { 38 | margin-left: 0; 39 | 40 | > div.clearfix { 41 | margin-top: -30px; 42 | } 43 | .list-group, .list-group-item { 44 | border: 0; 45 | margin-bottom: 0; 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /dist/less/landing-page.less: -------------------------------------------------------------------------------- 1 | .catalog-search { 2 | background: @landing-bg; 3 | background-position: @landing-bg-position; 4 | background-size: @landing-bg-size; 5 | border-bottom: solid 1px @search-area-border; 6 | padding: (@grid-gutter-width / 4) (@grid-gutter-width / 2); 7 | width: 100%; 8 | @media (max-width: @screen-xs-max) { 9 | .ios & { 10 | background-size: @landing-bg-size-ios-phone; 11 | } 12 | } 13 | @media (min-width: @screen-sm-min) { 14 | position: fixed; 15 | top: @navbar-height; 16 | width: calc(100% ~"-" @landing-side-bar-width-sm); 17 | } 18 | @media(min-width: @screen-lg-min) { 19 | width: calc(100% ~"-" @landing-side-bar-width-lg); 20 | } 21 | @media(min-width: @screen-xl-min) { 22 | width: calc(100% ~"-" @landing-side-bar-width-xl); 23 | } 24 | .catalog-search-icon { 25 | color: @color-pf-blue-300; 26 | font-size: 16px; 27 | left: 12px; 28 | position: absolute; 29 | top: 7px; 30 | } 31 | .catalog-search-input { 32 | text-indent: 30px; 33 | &:focus { 34 | border-color: @color-pf-blue-300; 35 | box-shadow: 0 0 8px @color-pf-light-blue-200; 36 | } 37 | } 38 | .catalog-search-match-info { 39 | flex: 1 1 0%; // for .word-break() 40 | .word-break(); 41 | } 42 | .dropdown-menu { 43 | margin-top: 0; 44 | padding: 0; 45 | width: 100%; 46 | > li > a { 47 | &.catalog-search-match { 48 | border-color: @color-pf-black-300; 49 | border-width: 0 0 1px 0; 50 | display: flex; 51 | line-height: 1.5; 52 | padding: 3px 10px 3px 5px; 53 | white-space: normal; 54 | } 55 | &.catalog-search-show-all { 56 | background-color: @color-pf-black-200; 57 | color: @link-color; 58 | padding: 5px 0; 59 | text-align: center; 60 | } 61 | } 62 | // important needed to override styles declared important in PatternFly 63 | .active > a { 64 | border-color: @color-pf-blue-100 !important; 65 | &.catalog-search-match { 66 | background-color: @color-pf-blue-50 !important; 67 | color: inherit !important; 68 | } 69 | &.catalog-search-show-all { 70 | background-color: @color-pf-black-200 !important; 71 | color: @link-hover-color !important; 72 | } 73 | } 74 | 75 | .catalog-search-show-none { 76 | background-color: @color-pf-black-200; 77 | border-color: @color-pf-black-300; 78 | border-width: 0 0 1px 0; 79 | padding: 5px 0; 80 | text-align: center; 81 | } 82 | } 83 | .landing-search-area & { 84 | z-index: @zindex-navbar-fixed - 1; 85 | .search-pf .has-clear .clear { 86 | height: 28px; 87 | } 88 | .search-pf-input-group { 89 | .form-control { 90 | font-size: (@font-size-base + 2); 91 | height: 30px; 92 | } 93 | .pficon-close { 94 | font-size: (@font-size-base + 4); 95 | } 96 | } 97 | } 98 | } 99 | 100 | // Special styles for mobile Safari 101 | @media only screen and (max-device-width: 736px) and (-webkit-min-device-pixel-ratio: 0) { 102 | .catalog-search { 103 | .catalog-search-icon { 104 | top: 9px; 105 | } 106 | .landing-search-form .search-pf-input-group .form-control { 107 | // font-size must be at least 16px on iOS to prevent the page from zooming when the search input is focused. 108 | font-size: 16px; 109 | } 110 | } 111 | } 112 | 113 | .catalog-search-match { 114 | .catalog-search-match-icon { 115 | flex: 0 0 40px; 116 | padding-right: 5px; 117 | padding-top: 2px; 118 | text-align: center; 119 | } 120 | .catalog-search-match-label { 121 | font-weight: 600; 122 | .line-clamp(3, 1.5em); // limit to 3 lines 123 | } 124 | .icon { 125 | font-size: 28px; 126 | } 127 | img { 128 | max-height: 28px; 129 | max-width: 28px; 130 | } 131 | .tag { 132 | margin-right: 5px; 133 | text-transform: uppercase; 134 | } 135 | } 136 | 137 | .catalog-search-toggle { 138 | padding: 0 5px; 139 | } 140 | 141 | .landing { 142 | background: @landing-bg; 143 | background-position: @landing-bg-position; 144 | background-size: @landing-bg-size; 145 | @media (max-width: @screen-xs-max) { 146 | .ios & { 147 | background-size: @landing-bg-size-ios-phone; 148 | } 149 | } 150 | @media(min-width: @screen-sm-min) { 151 | bottom: 0; 152 | overflow-x: hidden; 153 | overflow-y: auto; 154 | position: absolute; 155 | top: @landing-offset-top; 156 | width: calc(100% ~"-" @landing-side-bar-width-sm); 157 | } 158 | @media(min-width: @screen-lg-min) { 159 | width: calc(100% ~"-" @landing-side-bar-width-lg); 160 | } 161 | @media(min-width: @screen-xl-min) { 162 | width: calc(100% ~"-" @landing-side-bar-width-xl); 163 | } 164 | } 165 | 166 | landingbody, 167 | .landing-body, 168 | .landing-body-area, 169 | .landing-main-area { 170 | display: flex; 171 | flex: 1 1 auto; 172 | } 173 | 174 | .landing-main-area { 175 | flex-direction: column; 176 | min-height: 100%; 177 | } 178 | 179 | .landing-search-form { 180 | margin: 0 auto; 181 | width: 100%; 182 | @media (min-width: @screen-sm-min) { 183 | max-width: 600px; 184 | width: 80%; 185 | } 186 | } 187 | 188 | .landing-side-bar { 189 | background-color: @landing-side-bar-bg; 190 | color: @color-pf-white; 191 | // Needs to be less than the navbar so dropdown menus don't open under the sidebar. 192 | z-index: @zindex-navbar-fixed - 1; 193 | @media(min-width: @screen-sm-min) { 194 | bottom: 0; 195 | overflow-y: auto; 196 | position: fixed; 197 | right: 0; 198 | top: @landing-side-bar-top-offset; 199 | width: @landing-side-bar-width-sm; 200 | } 201 | @media(min-width: @screen-lg-min) { 202 | width: @landing-side-bar-width-lg; 203 | } 204 | @media(min-width: @screen-xl-min) { 205 | width: @landing-side-bar-width-xl; 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /dist/less/main.less: -------------------------------------------------------------------------------- 1 | // origin-web-catalog 2 | 3 | @import "~font-awesome/less/variables.less"; 4 | @import '~bootstrap/less/variables.less'; 5 | @import '~patternfly/dist/less/color-variables.less'; 6 | @import '~patternfly/dist/less/variables.less'; 7 | @import '~origin-web-common/dist/less/_variables.less'; 8 | @import '~origin-web-common/dist/less/_mixins.less'; 9 | 10 | // add partials to catalog.less 11 | @import './catalog.less'; 12 | -------------------------------------------------------------------------------- /dist/less/mixins.less: -------------------------------------------------------------------------------- 1 | .scroll-shadows-vertical(@shadow-width: 75%, @shadow-opacity: 0.2) { 2 | background-attachment: scroll; 3 | background-image: radial-gradient(ellipse at top, rgba(0, 0, 0, @shadow-opacity) 0%, rgba(0, 0, 0, 0) @shadow-width), radial-gradient(ellipse at bottom, rgba(0, 0, 0, @shadow-opacity) 0%, rgba(0, 0, 0, 0) @shadow-width); 4 | background-position: 0 0, 0 100%; 5 | background-repeat: no-repeat; 6 | background-size: 100% 6px; 7 | } 8 | 9 | .scroll-shadows-vertical-covers(@shadow-cover-bg-color: rgba(255,255,255,1), @shadow-cover-bg-color-transparent: rgba(255,255,255,0)) { 10 | background-attachment: local; 11 | background-image: linear-gradient(@shadow-cover-bg-color 30%, @shadow-cover-bg-color-transparent), linear-gradient(@shadow-cover-bg-color-transparent, @shadow-cover-bg-color 70%); 12 | background-position: 0 0, 0 100%; 13 | background-repeat: no-repeat; 14 | background-size: 100% 12px; 15 | } 16 | -------------------------------------------------------------------------------- /dist/less/order-service.less: -------------------------------------------------------------------------------- 1 | .order-service-details { 2 | .order-service-details-top { 3 | align-items: center; 4 | display: flex; 5 | &.order-service-details-top-icon-top { 6 | align-items: flex-start; 7 | } 8 | .service-icon { 9 | margin-right: 15px; 10 | .icon { 11 | font-size: 60px; 12 | @media (min-width: @order-service-title-sm-min) { 13 | font-size: 64px; 14 | } 15 | } 16 | .image { 17 | img { 18 | max-height: 52px; 19 | max-width: 52px; 20 | @media (min-width: @order-service-title-sm-min) { 21 | max-height: 60px; 22 | max-width: 60px; 23 | } 24 | &[src$=".svg"] { 25 | height: 52px; 26 | @media (min-width: @order-service-title-sm-min) { 27 | height: 60px; 28 | } 29 | } 30 | } 31 | } 32 | } 33 | .service-title { 34 | font-size: 18px; 35 | font-weight: 600; 36 | .line-clamp(3, 1.4em); // limit to 3 lines 37 | @media (min-width: @order-service-title-sm-min) { 38 | font-size: 22px; 39 | } 40 | } 41 | .service-title-area { 42 | flex: 1 1 0%; // for .word-break() 43 | .word-break(); 44 | } 45 | .service-vendor { 46 | margin-top: 5px; 47 | } 48 | .sub-title { 49 | color: @color-pf-black-600; 50 | font-size: 20px; 51 | font-weight: 600; 52 | } 53 | } 54 | .order-service-description-block { 55 | margin-top: 15px; 56 | .description { 57 | white-space: pre-wrap; 58 | } 59 | .learn-more-link { 60 | font-size: @font-size-small; 61 | white-space: nowrap; 62 | } 63 | .order-service-dependent-image { 64 | margin-bottom: 5px; 65 | .word-break(); 66 | .pficon { 67 | margin-right: 5px; 68 | vertical-align: -1px; 69 | } 70 | } 71 | .order-service-subheading { 72 | // Prevent top margin from collapsing. 73 | display: inline-block; 74 | font-size: @font-size-base + 1; 75 | margin-bottom: 7px; 76 | margin-top: 10px; 77 | } 78 | } 79 | .order-service-documentation-url { 80 | margin-top: 4px; 81 | } 82 | .order-service-tags { 83 | color: @text-muted; 84 | margin-top: 5px; 85 | .tag { 86 | margin-right: 5px; 87 | text-transform: uppercase; 88 | } 89 | } 90 | } 91 | 92 | .order-service-config { 93 | .config-top { 94 | .adv-ops { 95 | height: 30%; 96 | } 97 | .adv-ops-href { 98 | cursor: pointer; 99 | font-size: 14px; 100 | &::after { 101 | content: '\f105'; 102 | font-family: 'FontAwesome'; 103 | padding-left: 10px; 104 | } 105 | &.collapsed { 106 | &::after { 107 | content: '\f107'; 108 | } 109 | } 110 | } 111 | .adv-ops-container { 112 | padding-top: 2px; 113 | } 114 | } 115 | .config-bottom { 116 | height: 12%; 117 | float: right; 118 | margin-right: -13px; 119 | margin-top: 8px; 120 | margin-right: -10px; 121 | } 122 | .footer-panel { 123 | padding-top: 10px; 124 | text-align: center; 125 | 126 | a { 127 | color: @color-pf-white; 128 | &:hover { 129 | color: @color-pf-white; 130 | text-decoration: none; 131 | } 132 | } 133 | } 134 | .form-group h3 { 135 | margin-left: -10px; 136 | } 137 | h3 { 138 | line-height: 1.4; 139 | margin-top: 0; 140 | + .alert { 141 | margin-top: 20px; 142 | } 143 | } 144 | .no-projects-cant-create { 145 | div, 146 | p { 147 | text-align: center; 148 | } 149 | } 150 | .or { 151 | margin-left: 15px; 152 | } 153 | .select-plans { 154 | .plan-name { 155 | display: inline-block; 156 | font-size: 14px; 157 | margin-bottom: 5px; 158 | margin-top: -2px; 159 | } 160 | .plan-description { 161 | margin-left: 10px; 162 | } 163 | .radio { 164 | margin-top: 15px; 165 | } 166 | } 167 | .select-project-divider { 168 | border-bottom: 1px solid @color-pf-black-400; 169 | margin-bottom: 18px; 170 | margin-top: 22px; 171 | } 172 | .sub-title { 173 | margin: 0 0 10px 0; 174 | .error-message { 175 | white-space: pre-line; 176 | } 177 | } 178 | .success-check { 179 | color: @color-pf-green-400; 180 | } 181 | .related-services-container { 182 | background-color: @color-pf-black-200; 183 | margin-top: 62px; 184 | padding: 14px; 185 | display: flex; 186 | align-items: center; 187 | .related-services-label { 188 | font-size: 14px; 189 | font-weight: 600; 190 | padding-right: 14px; 191 | } 192 | .related-services-row { 193 | .card { 194 | background-color: @color-pf-white; 195 | border:1px solid @color-pf-black-400; 196 | float: right; 197 | margin-right: 6px; 198 | padding: 11px; 199 | } 200 | } 201 | } 202 | } 203 | 204 | .order-service-wizard-step { 205 | .schema-form-fieldset { 206 | margin-bottom: 40px; 207 | margin-top: 40px; 208 | legend { 209 | border-bottom: none; 210 | border-top: solid 1px rgba(0, 0, 0, 0.15); 211 | margin-bottom: 10px; 212 | padding-top: 20px; 213 | } 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /dist/less/overlay-panel.less: -------------------------------------------------------------------------------- 1 | body.overlay-open { 2 | &, 3 | .landing, 4 | .landing-side-bar { 5 | overflow: hidden; 6 | } 7 | } 8 | 9 | .catalogs-overlay-modal { 10 | display: block; 11 | .modal-backdrop { 12 | &.ng-enter { 13 | animation: modalBackdropIn 0.15s ease-out forwards; 14 | } 15 | &.ng-leave { 16 | animation: modalBackdropOut .45s ease-out forwards; 17 | } 18 | } 19 | } 20 | 21 | .catalogs-overlay-panel { 22 | bottom: 0; 23 | left: 0; 24 | position: fixed; 25 | right: 0; 26 | top: 0; 27 | @media(min-width: @screen-sm-min) { 28 | margin: 0 auto; 29 | max-width: 900px; 30 | position: relative; 31 | } 32 | @media(min-width: @screen-sm-min) and (min-height: (@catalog-overlay-panel-height + @navbar-pf-height)) { 33 | margin-top: @navbar-pf-height; 34 | } 35 | // make overlay-panel like a bootstrap modal 36 | .overlay-panel-as-modal & { 37 | position: relative; 38 | @media(max-width: @screen-xs-max) { 39 | margin: 10px; 40 | } 41 | @media(min-width: @screen-sm-min) { 42 | max-width: 640px; 43 | } 44 | } 45 | 46 | .wizard-pf-contents { 47 | flex: 1 1 auto; 48 | } 49 | 50 | .wizard-pf-footer { 51 | bottom: 0; 52 | left: 0; 53 | position: absolute; 54 | right: 0; 55 | @media(min-width: @screen-sm-min) { 56 | position: relative; 57 | } 58 | 59 | .btn { 60 | font-size: 12px; // so that wizard-pf-footer in the console is 58px tall 61 | } 62 | } 63 | 64 | .wizard-pf-main { 65 | display: flex; 66 | overflow-y: auto; 67 | padding: 0; 68 | .scroll-shadows-vertical(); 69 | -webkit-overflow-scrolling: touch; // enable momentum scrolling in mobile Safari 70 | @media(max-width: @screen-xs-max) { 71 | bottom: 58px; // height of .wizard-pf-footer 72 | height: auto; 73 | left: 1px; 74 | position: fixed; 75 | right: 1px; 76 | top: 99px; // 99px = 98px (.order-service) + 1px (modal top "margin") 77 | width: auto; 78 | } 79 | @media(min-width: @screen-sm-min) { 80 | height: @order-service-page-height + @grid-gutter-width; 81 | max-height: calc(100vh ~"-" 222px); // 222px = 1px (modal top "margin") + 41px (.modal-header) + 121px (.wizard-stesp) + 58 (.wizard-pf-footer) + 1px (modal bottom "margin") 82 | min-height: 200px; 83 | } 84 | } 85 | 86 | .wizard-pf-main-form-contents { 87 | position: relative; // so label.required:before are positioned correctly 88 | label.required:before { 89 | left: -10px; 90 | } 91 | } 92 | 93 | .wizard-pf-main-inner-shadow-covers { 94 | min-height: 100%; 95 | padding: (@grid-gutter-width / 2); 96 | .scroll-shadows-vertical-covers(); 97 | @media(min-width: @screen-sm-min) { 98 | padding-left: @grid-gutter-width; 99 | padding-right: @grid-gutter-width; 100 | } 101 | // so that input and textarea form-controls don't mask the inner scroll shadows 102 | input, textarea { 103 | &.form-control { 104 | background-color: transparent; 105 | // so that disabled or readonly form-controls get the correct bg-color 106 | &[disabled], 107 | &[readonly] { 108 | background-color: rgba(234, 234, 234, 0.5); 109 | } 110 | } 111 | } 112 | } 113 | 114 | .wizard-pf-row { 115 | // PatternFly sets this to control the height of the wizard, but our layout 116 | // is more complex, so we disable the max-height 117 | max-height: none; 118 | } 119 | } 120 | 121 | .catalogs-overlay-panel-close { 122 | color: @color-pf-black-600; 123 | cursor: pointer; 124 | font-size: 21px; 125 | position: absolute; 126 | right: 20px; 127 | top: 15px; 128 | 129 | &.pficon-close { 130 | color: @color-pf-black-600; 131 | cursor: pointer; 132 | } 133 | &.pficon-close.disabled, .pficon-close:hover.disabled { 134 | color: @color-pf-black-400; 135 | cursor: not-allowed; 136 | } 137 | &.pficon-close:hover { 138 | color: @color-pf-black-800; 139 | } 140 | } 141 | 142 | .catalogs-overlay-panel-wrapper { 143 | bottom: 0; 144 | left: 0; 145 | overflow-x: hidden; 146 | overflow-y: auto; 147 | position: fixed; 148 | right: 0; 149 | top: 0; 150 | // Set z-index to 1040 (same as .modal-backdrop), but will still appear above .modal-backdrop because of markup structure. This enables the uib-modal background to overlay the catalog wizard modal, when both are displayed. 151 | z-index: @zindex-modal-background; 152 | &.ng-enter { 153 | animation: modalSlideDown 0.3s ease-out forwards; 154 | } 155 | &.ng-leave { 156 | animation: modalSlideUp 0.2s ease-out forwards; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /dist/less/saas-list.less: -------------------------------------------------------------------------------- 1 | .build-applications-view { 2 | background: linear-gradient(fade(@color-pf-blue-700, 20%), fade(@color-pf-blue-700, 0%)); 3 | 4 | h1 { 5 | color: @saas-text; 6 | padding: (@grid-gutter-width / 2); 7 | } 8 | } 9 | 10 | .saas-offerings-container { 11 | 12 | .saas-list { 13 | display: flex; 14 | justify-content: flex-start; 15 | margin: 0; 16 | overflow-x: hidden; 17 | 18 | @media (min-width: @screen-lg-min) { 19 | justify-content: space-around; 20 | } 21 | 22 | &.expanded { 23 | flex-wrap: wrap; 24 | justify-content: flex-start; 25 | .card { 26 | &:nth-child(n+3) { 27 | display: flex; // display all in expanded mode 28 | } 29 | } 30 | } 31 | 32 | .card { 33 | color: @saas-text; 34 | display: flex; 35 | width: 25%; 36 | 37 | @media (max-width: @screen-md-max) { 38 | width: 50%; 39 | &:nth-child(n+3) { 40 | display: none; // display first 2 41 | } 42 | } 43 | @media (min-width: @screen-lg-min) { 44 | &:nth-child(n+5) { 45 | display: none; // display first 4 46 | } 47 | } 48 | 49 | &:hover { 50 | background-color: @saas-hover; 51 | } 52 | } 53 | 54 | .card-content { 55 | align-items: center; 56 | color: @saas-text; 57 | display: flex; 58 | flex: 1 1 0%; // IE 11 59 | flex-direction: column; 60 | padding: (@grid-gutter-width / 2); 61 | text-align: center; 62 | text-shadow: 1px 1px 3px rgba(0,0,0,.5); 63 | 64 | &:focus, 65 | &:hover { 66 | text-decoration: none; 67 | } 68 | 69 | .card-description { 70 | font-size: @font-size-base; 71 | line-height: 1.5; 72 | max-width: 100%; // IE 11 73 | } 74 | 75 | .card-icon { 76 | .icon { 77 | font-size: @card-icon-sm; 78 | } 79 | img { 80 | height: @card-icon-sm; 81 | } 82 | } 83 | 84 | .card-title { 85 | font-size: @font-size-base + 2; 86 | 87 | @media (min-width: 480px) { 88 | font-size: @font-size-base + 4; 89 | } 90 | 91 | font-weight: 700; 92 | line-height: 1.4; 93 | margin-bottom: 10px; 94 | margin-top: 15px; 95 | max-width: 100%; // IE 11 96 | padding-left: 0; 97 | 98 | @media (min-width: @screen-lg-min) { 99 | font-size: @font-size-base + 6; 100 | } 101 | } 102 | } 103 | } 104 | .sass-list-expander { 105 | color: @color-pf-white; 106 | font-size: @font-size-base; 107 | &:hover, &:focus { 108 | color: @color-pf-white; 109 | text-decoration: none; 110 | } 111 | &:after { 112 | content: @fa-var-angle-down; 113 | font-family: @icon-font-name-fa; 114 | font-size: (@font-size-base + 2); 115 | padding: 0 5px; 116 | } 117 | &.expanded { 118 | &:after { 119 | content: @fa-var-angle-up; 120 | } 121 | .less { 122 | display: inline; 123 | } 124 | .more { 125 | display: none; 126 | } 127 | } 128 | .less { 129 | display: none; 130 | } 131 | } 132 | .sass-list-expander-container { 133 | margin-bottom: (@grid-gutter-width / 4); 134 | text-align: center; 135 | } 136 | 137 | .spinner-container { 138 | padding-top: 60px; 139 | padding-bottom: 60px; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /dist/origin-web-catalogs.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":[],"names":[],"mappings":"","file":"origin-web-catalogs.css","sourceRoot":""} -------------------------------------------------------------------------------- /dist/vendor-bundle.js: -------------------------------------------------------------------------------- 1 | !function(e) { 2 | function r(n) { 3 | if (t[n]) return t[n].exports; 4 | var o = t[n] = { 5 | i: n, 6 | l: !1, 7 | exports: {} 8 | }; 9 | return e[n].call(o.exports, o, o.exports, r), o.l = !0, o.exports; 10 | } 11 | var n = window.webpackJsonp; 12 | window.webpackJsonp = function(t, u, c) { 13 | for (var i, a, f, l = 0, s = []; l < t.length; l++) a = t[l], o[a] && s.push(o[a][0]), 14 | o[a] = 0; 15 | for (i in u) Object.prototype.hasOwnProperty.call(u, i) && (e[i] = u[i]); 16 | for (n && n(t, u, c); s.length; ) s.shift()(); 17 | if (c) for (l = 0; l < c.length; l++) f = r(r.s = c[l]); 18 | return f; 19 | }; 20 | var t = {}, o = { 21 | 1: 0 22 | }; 23 | r.e = function(e) { 24 | function n() { 25 | u.onerror = u.onload = null, clearTimeout(c); 26 | var r = o[e]; 27 | 0 !== r && (r && r[1](new Error("Loading chunk " + e + " failed.")), o[e] = void 0); 28 | } 29 | if (0 === o[e]) return Promise.resolve(); 30 | if (o[e]) return o[e][2]; 31 | var t = document.getElementsByTagName("head")[0], u = document.createElement("script"); 32 | u.type = "text/javascript", u.charset = "utf-8", u.async = !0, u.timeout = 12e4, 33 | r.nc && u.setAttribute("nonce", r.nc), u.src = r.p + "" + e + ".chunk.js"; 34 | var c = setTimeout(n, 12e4); 35 | u.onerror = u.onload = n; 36 | var i = new Promise(function(r, n) { 37 | o[e] = [ r, n ]; 38 | }); 39 | return o[e][2] = i, t.appendChild(u), i; 40 | }, r.m = e, r.c = t, r.i = function(e) { 41 | return e; 42 | }, r.d = function(e, n, t) { 43 | r.o(e, n) || Object.defineProperty(e, n, { 44 | configurable: !1, 45 | enumerable: !0, 46 | get: t 47 | }); 48 | }, r.n = function(e) { 49 | var n = e && e.__esModule ? function() { 50 | return e.default; 51 | } : function() { 52 | return e; 53 | }; 54 | return r.d(n, "a", n), n; 55 | }, r.o = function(e, r) { 56 | return Object.prototype.hasOwnProperty.call(e, r); 57 | }, r.p = "/", r.oe = function(e) { 58 | throw console.error(e), e; 59 | }; 60 | }([]); -------------------------------------------------------------------------------- /hack/clean-deps.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # We don't need bower to be installed globally for the system, so 6 | # we can amend our path to look into the local node_modules for the 7 | # correct binaries. 8 | repo_root="$( dirname "${BASH_SOURCE}" )/.." 9 | export PATH="${PATH}:${repo_root}/node_modules/bower/bin" 10 | 11 | if which bower > /dev/null 2>&1 ; then 12 | # In case upstream components change things without incrementing versions 13 | echo "Clearing bower cache..." 14 | bower cache clean --allow-root 15 | else 16 | echo "Skipping bower cache clean, bower not installed." 17 | fi 18 | 19 | echo "Cleaning up bower_components and node_modules..." 20 | rm -rf bower_components/* node_modules/* 21 | -------------------------------------------------------------------------------- /hack/install-deps.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | STARTTIME=$(date +%s) 6 | 7 | TMPDIR="${TMPDIR:-"/tmp"}" 8 | LOG_DIR="${LOG_DIR:-$(mktemp -d ${TMPDIR}/openshift.assets.logs.XXXX)}" 9 | 10 | function cmd() { 11 | local cmd="$1" 12 | local tries="${2:-1}" 13 | local log_file=$(mktemp ${LOG_DIR}/install-assets.XXXX) 14 | echo "[install-assets] ${cmd}" 15 | rc="0" 16 | while [[ "$tries" -gt 0 ]]; do 17 | rc="0" 18 | $cmd &> ${log_file} || rc=$? 19 | [[ "$rc" == "0" ]] && return 0 20 | ((tries--)) 21 | done 22 | echo "[ERROR] Command '${cmd}' failed with rc ${rc}, logs:" && cat ${log_file} 23 | exit $rc 24 | } 25 | 26 | repo_root="$( dirname "${BASH_SOURCE}" )/.." 27 | 28 | # Install bower if needed 29 | if ! which bower > /dev/null 2>&1 ; then 30 | cmd "npm install bower" 31 | fi 32 | 33 | cmd "npm install --unsafe-perm" 34 | 35 | # In case upstream components change things without incrementing versions 36 | cmd "bower cache clean --allow-root" 37 | cmd "bower update --allow-root" 3 38 | 39 | ret=$?; ENDTIME=$(date +%s); echo "$0 took $(($ENDTIME - $STARTTIME)) seconds"; exit "$ret" 40 | -------------------------------------------------------------------------------- /hack/test-headless.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | 7 | function cleanup_xvfb() { 8 | if [[ -n "${xvfb_process:-}" ]]; then 9 | kill -SIGTERM "${xvfb_process}" 10 | fi 11 | } 12 | 13 | trap cleanup_xvfb EXIT 14 | 15 | if ! which xdpyinfo >/dev/null 2>&1; then 16 | echo "[ERROR] The \`xdpyinfo\` utility is required to run this script!." 17 | exit 1 18 | fi 19 | 20 | if ! which Xvfb >/dev/null 2>&1; then 21 | echo "[ERROR] The \`Xvfb\` utility is required to run this script!." 22 | exit 1 23 | fi 24 | 25 | echo "[INFO] Starting virtual framebuffer for headless tests..." 26 | export DISPLAY=':10' 27 | export SCREEN='0' 28 | Xvfb "${DISPLAY}" -screen "${SCREEN}" 1024x768x24 -ac & 29 | xvfb_process="$!" 30 | 31 | while ! xdpyinfo -d "${DISPLAY}" >/dev/null 2>&1; do 32 | sleep 0.2 33 | done 34 | 35 | npm "$@" 36 | -------------------------------------------------------------------------------- /hack/verify-dist.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | npm run build 6 | 7 | echo "Verifying that checked in built files under dist match the source..." 8 | if [[ $(git status -s -u dist*) ]]; then 9 | git status -vv -u dist* 10 | echo "Built dist does not match what is committed, run 'npm run build' and include the results in your commit." 11 | exit 1 12 | else 13 | echo "Verified." 14 | fi 15 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | var webpackConfig = require('./config/webpack.test'); 2 | 3 | module.exports = function karmaConfig(config) { 4 | config.set({ 5 | frameworks: [ 6 | 'jasmine', 7 | 'detectBrowsers' 8 | ], 9 | 10 | reporters: [ 11 | 'dots', 'coverage' 12 | ], 13 | 14 | files: [ 15 | 'test/test-bundle.ts' 16 | ], 17 | 18 | preprocessors: { 19 | 'test/test-bundle.ts': ['webpack', 'coverage', 'sourcemap'] 20 | }, 21 | 22 | webpack:webpackConfig, 23 | 24 | detectBrowsers: { 25 | enabled: true, 26 | usePhantomJS: false, 27 | postDetection: function(availableBrowsers) { 28 | var result = []; 29 | 30 | if (availableBrowsers.indexOf('Safari') > -1) { 31 | result.push('Safari'); 32 | } else if (availableBrowsers.indexOf('Firefox') > -1) { 33 | result.push('Firefox'); 34 | } else { 35 | result = availableBrowsers 36 | } 37 | 38 | return result; 39 | } 40 | }, 41 | 42 | singleRun: true, 43 | captureTimeout: 60000, 44 | browserDisconnectTimeout : 40000, 45 | browserDisconnectTolerance : 1, 46 | browserNoActivityTimeout : 60000, 47 | 48 | coverageReporter: { 49 | dir: 'coverage/', 50 | reporters: [ 51 | {type: 'text-summary'}, 52 | {type: 'html'} 53 | ] 54 | }, 55 | 56 | webpackMiddleware: { 57 | stats: 'errors-only' 58 | } 59 | }); 60 | }; 61 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "origin-web-catalog", 3 | "version": "3.10.0", 4 | "description": "The OpenShift Web Catalog", 5 | "scripts": { 6 | "clean": "rimraf dist && rimraf docs && rimraf coverage", 7 | "build": "rimraf dist && webpack --config config/webpack.prod.js --bail --progress --profile", 8 | "build:prod": "rimraf dist && webpack --config config/webpack.prod.js --bail --progress --profile", 9 | "build:run": "npm run build && live-server --port=3001 --open=dist/", 10 | "start": "webpack-dev-server --config config/webpack.dev.js --history-api-fallback --inline --progress --open", 11 | "coverage": "live-server --port=3001 --open=coverage/", 12 | "test": "karma start", 13 | "test:coverage": "npm run test && npm run coverage", 14 | "test:watch": "karma start --auto-watch --no-single-run", 15 | "docs": "jsdoc src/app -r -d docs", 16 | "docs:launch": "npm run docs && live-server --port=3002 --open=docs/" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/origin/origin-web-catalog.git" 21 | }, 22 | "author": "Red Hat, Inc", 23 | "license": "Apache-2.0", 24 | "bugs": { 25 | "url": "https://github.com/origin/origin-web-catalog/issues" 26 | }, 27 | "homepage": "https://github.com/origin/origin-web-catalog", 28 | "dependencies": { 29 | "angular": "1.5.11", 30 | "angular-animate": "1.5.11", 31 | "angular-drag-and-drop-lists": "2.0.0", 32 | "angular-moment": "1.0.0", 33 | "angular-patternfly": ">=4.11.8 <5.0.0", 34 | "angular-sanitize": "1.5.11", 35 | "angular-schema-form": "^0.8.13", 36 | "angular-schema-form-bootstrap": "^0.2.0", 37 | "angular-ui-bootstrap": "0.14.x", 38 | "bootstrap": "^3.3.6", 39 | "font-awesome": "~4.7.0", 40 | "jquery": "3.2.1", 41 | "origin-web-common": ">=3.10.0-alpha.1 <3.11.0", 42 | "paper-css": "^0.1.2", 43 | "patternfly": "^3.29.3", 44 | "urijs": "1.18.0" 45 | }, 46 | "devDependencies": { 47 | "@types/angular": "~1.5.16", 48 | "@types/angular-mocks": "~1.5.5", 49 | "@types/angular-ui-router": "^1.1.40", 50 | "@types/es6-shim": "~0.31.32", 51 | "@types/jasmine": "~2.5.38", 52 | "@types/jquery": "^3.2.15", 53 | "@types/lodash": "^4.14.77", 54 | "@types/node": "~0.0.2", 55 | "@types/urijs": "~1.15.26", 56 | "@uirouter/angularjs": "^1.0.0", 57 | "angular-mocks": "1.5.11", 58 | "autoprefixer": "^6.0.3", 59 | "babel-core": "^6.2.1", 60 | "babel-loader": "^6.2.0", 61 | "babel-preset-es2015": "^6.1.18", 62 | "bower": "^1.8.2", 63 | "copy-webpack-plugin": "^4.1.1", 64 | "css-loader": "^0.28.11", 65 | "eslint": "^2.8.0", 66 | "eslint-loader": "^1.3.0", 67 | "eslint-plugin-promise": "^1.1.0", 68 | "eslint-plugin-standard": "^1.3.2", 69 | "exports-loader": "^0.6.4", 70 | "extract-text-webpack-plugin": "^2.0.0-beta.5", 71 | "favicons-webpack-plugin": "0.0.9", 72 | "file-loader": "^0.8.4", 73 | "gulp-angular-templatecache": "^1.8.0", 74 | "hopscotch": "~0.2.7", 75 | "html-loader": "^0.4.3", 76 | "html-webpack-plugin": "^2.7.1", 77 | "imports-loader": "^0.7.1", 78 | "jasmine-core": "^2.3.4", 79 | "jsdoc": "^3.5.5", 80 | "json-loader": "^0.5.4", 81 | "karma": "^1.7.0", 82 | "karma-chrome-launcher": "^2.0.0", 83 | "karma-coverage": "^0.5.3", 84 | "karma-detect-browsers": "^2.2.5", 85 | "karma-firefox-launcher": "^1.0.1", 86 | "karma-jasmine": "^1.1.0", 87 | "karma-phantomjs-launcher": "^1.0.0", 88 | "karma-safari-launcher": "^1.0.0", 89 | "karma-sourcemap-loader": "^0.3.7", 90 | "karma-spec-reporter": "0.0.26", 91 | "karma-webpack": "^2.0.5", 92 | "less": "^2.3.1", 93 | "less-loader": "^2.2.2", 94 | "live-server": "^0.9.2", 95 | "lodash": "~4.17.4", 96 | "ng-annotate-loader": "^0.0.10", 97 | "ng-annotate-webpack-plugin": "^0.1.2", 98 | "node-libs-browser": "^1.0.0", 99 | "null-loader": "^0.1.1", 100 | "openshift-logos-icon": "^1.1.0", 101 | "phantomjs-prebuilt": "^2.1.4", 102 | "postcss-loader": "^0.8.0", 103 | "raw-loader": "^0.5.1", 104 | "rimraf": "^2.5.1", 105 | "style-loader": "^0.13.0", 106 | "ts-loader": "^1.2.2", 107 | "tslint": "^3.2.1", 108 | "tslint-loader": "^2.1.0", 109 | "typescript": "~2.3.4", 110 | "uglify-js": "3.0.18", 111 | "url-loader": "^0.5.7", 112 | "webpack": "2.2.0", 113 | "webpack-dev-server": "^2.9.2", 114 | "webpack-merge": "^3.0.0" 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/components/catalog-filter/catalog-filter.component.ts: -------------------------------------------------------------------------------- 1 | import {CatalogFilterController} from './catalog-filter.controller'; 2 | 3 | export const catalogFilter: angular.IComponentOptions = { 4 | bindings: { 5 | config: '<', 6 | vendors: '<', 7 | filterOnKeyword: '<', 8 | applyFilters: "&" 9 | }, 10 | controller: CatalogFilterController, 11 | template: require('./catalog-filter.html'), 12 | }; 13 | -------------------------------------------------------------------------------- /src/components/catalog-filter/catalog-filter.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 10 |
11 | {{filter.title}} 12 | 18 |
    19 |
  • 20 |
    21 | 27 |
    28 |
  • 29 |
30 |
31 |
32 |
33 | -------------------------------------------------------------------------------- /src/components/catalog-parameters/catalog-parameters.component.ts: -------------------------------------------------------------------------------- 1 | import {CatalogParametersController} from './catalog-parameters.controller'; 2 | 3 | export const catalogParameters: angular.IComponentOptions = { 4 | bindings: { 5 | parameterSchema: '<', 6 | parameterFormDefinition: '<', 7 | isHorizontal: ' 2 | 7 | 8 | -------------------------------------------------------------------------------- /src/components/catalog-search/catalog-search-result.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 |
8 | {{match.label}} 9 |
10 |
11 | 12 | {{tag}} 13 | 14 |
15 |
16 |
17 | {{match.model.text}} 18 |
{{match.model.text}}
19 | -------------------------------------------------------------------------------- /src/components/catalog-search/catalog-search.component.ts: -------------------------------------------------------------------------------- 1 | import {CatalogSearchController} from './catalog-search.controller'; 2 | 3 | export const catalogSearch: angular.IComponentOptions = { 4 | bindings: { 5 | baseProjectUrl: '@', 6 | catalogItems: '<', 7 | toggleAtMobile: '; 22 | 23 | constructor($rootScope: any, $scope: any, $timeout: any, $q: any, Catalog: any, Constants: any, KeywordService: any) { 24 | this.$rootScope = $rootScope; 25 | this.$scope = $scope; 26 | this.$timeout = $timeout; 27 | this.$q = $q; 28 | this.Catalog = Catalog; 29 | this.Constants = Constants; 30 | this.KeywordService = KeywordService; 31 | } 32 | 33 | public $onInit() { 34 | this.ctrl.searchText = ''; 35 | } 36 | 37 | public $onChanges(onChangesObj: angular.IOnChangesObject) { 38 | if (onChangesObj.catalogItems && this.ctrl.catalogItems) { 39 | 40 | this.loaded = true; 41 | 42 | // Show search results now if the user began typing before the items loaded. 43 | if (this.searchDeferred) { 44 | let searchResult = this.weightedSearch(this.ctrl.searchText); 45 | this.searchDeferred.resolve(searchResult); 46 | this.searchDeferred = null; 47 | } 48 | } 49 | } 50 | 51 | public onKeyPress = (keyEvent: any) => { 52 | if (keyEvent.which === 13 && this.ctrl.searchText) { 53 | this.$rootScope.$emit('filter-catalog-items', {searchText: this.ctrl.searchText}); 54 | this.ctrl.searchText = ''; 55 | } 56 | }; 57 | 58 | public itemSelected(item: any) { 59 | if (item.id === 'viewAll') { 60 | this.$rootScope.$emit('filter-catalog-items', {searchText: this.ctrl.searchText}); 61 | } else if (item.id !== 'viewNone') { 62 | this.$scope.$emit('open-overlay-panel', item); 63 | } 64 | this.ctrl.searchText = ''; 65 | this.ctrl.mobileSearchInputShown = false; 66 | if (_.isFunction(this.ctrl.searchToggleCallback)) { 67 | this.ctrl.searchToggleCallback(this.ctrl.mobileSearchInputShown); 68 | } 69 | } 70 | 71 | public search(searchText: string) { 72 | if (!searchText) { 73 | return []; 74 | } 75 | 76 | // If the items haven't loaded, return a promise instead. 77 | if (!this.loaded) { 78 | this.searchDeferred = this.$q.defer(); 79 | return this.searchDeferred.promise; 80 | } 81 | 82 | return this.weightedSearch(searchText); 83 | } 84 | 85 | public toggleMobileShowSearchInput() { 86 | this.ctrl.mobileSearchInputShown = !this.ctrl.mobileSearchInputShown; 87 | this.ctrl.searchText = ''; 88 | if (_.isFunction(this.ctrl.searchToggleCallback)) { 89 | this.ctrl.searchToggleCallback(this.ctrl.mobileSearchInputShown); 90 | } 91 | if (this.ctrl.mobileSearchInputShown) { 92 | this.setSearchInputFocus(0); 93 | } 94 | } 95 | 96 | public setSearchInputFocus(timeSoFar: number) { 97 | var searchInput: any = $('.catalog-search-input'); 98 | 99 | if (searchInput.is(':visible')) { 100 | searchInput.focus(); 101 | } else if (timeSoFar < 5) { 102 | this.$timeout(() => { 103 | this.setSearchInputFocus(timeSoFar + 1); 104 | }, 100); 105 | } 106 | } 107 | 108 | private weightedSearch(searchText: string) { 109 | let keywords = this.KeywordService.generateKeywords(searchText); 110 | let items = this.KeywordService.weightedSearch(this.ctrl.catalogItems, this.Constants.CATALOG_SEARCH_FIELDS, keywords); 111 | let totalNumItems: number = _.size(items); 112 | let results: any = _.take(items, this.maxResultsToShow); 113 | 114 | if (totalNumItems === 0) { 115 | results.push({id: 'viewNone', text: "No results found for Keyword: " + searchText, name: searchText}); 116 | } else if (totalNumItems === 1) { 117 | results.push({id: 'viewAll', text: "View the result for Keyword: " + searchText, name: searchText}); 118 | } else if (totalNumItems > 1) { 119 | results.push({id: 'viewAll', text: "View all " + totalNumItems + " results for Keyword: " + searchText, name: searchText}); 120 | } 121 | 122 | return results; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/components/catalog-search/catalog-search.html: -------------------------------------------------------------------------------- 1 | 42 | -------------------------------------------------------------------------------- /src/components/create-from-builder/create-from-builder-bind.html: -------------------------------------------------------------------------------- 1 |
2 | 8 | 9 |
10 | -------------------------------------------------------------------------------- /src/components/create-from-builder/create-from-builder-info.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | 6 |
7 |
8 |
9 | {{$ctrl.imageStream.name}} 10 |
11 |
12 | {{$ctrl.imageStream.vendor}} 13 |
14 |
15 | 16 | {{tag}} 17 | 18 |
19 | 27 |
28 |
29 |
30 |

31 |

32 | Sample Repository: 33 | 34 | 35 |

36 |
37 |
38 | -------------------------------------------------------------------------------- /src/components/create-from-builder/create-from-builder-results.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | Pending 6 |
7 |

8 | {{$ctrl.name}} is being created in {{$ctrl.selectedProject | displayName}}. 9 |

10 |
11 |
12 |
13 |
14 |
15 | 16 | Success 17 |
18 |

19 | {{$ctrl.name}} has been created in {{$ctrl.selectedProject | displayName}} successfully. 20 |

21 |
22 |
23 |
24 |
25 | 31 | 32 |
33 |
34 |

35 | Continue to the project overview to check the status of your application as it builds and deploys. 36 |

37 |
38 |
39 |
40 | 41 |
42 |

43 | {{$ctrl.name}} failed to create in {{$ctrl.selectedProject | displayName}}. 44 |

45 |
46 |
47 |
48 | 49 | {{$ctrl.error.data.message | upperFirst}} 50 | 51 | 52 | An error occurred creating the application. 53 | 54 |
55 | 56 |
    57 |
  • 58 | {{failure.data.message}} 59 |
  • 60 |
61 |
62 |
63 | -------------------------------------------------------------------------------- /src/components/create-from-builder/create-from-builder.component.ts: -------------------------------------------------------------------------------- 1 | import {CreateFromBuilderController} from './create-from-builder.controller'; 2 | 3 | export const createFromBuilder: angular.IComponentOptions = { 4 | bindings: { 5 | baseProjectUrl: '@', 6 | imageStream: '<', 7 | handleClose: '<', 8 | addToProject: ' 2 | 12 | 21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | 29 | -------------------------------------------------------------------------------- /src/components/landing-page/landing-page.component.ts: -------------------------------------------------------------------------------- 1 | import {LandingPageController} from './landing-page.controller'; 2 | 3 | export const landingPage: angular.IComponentOptions = { 4 | bindings: { 5 | baseProjectUrl: '@', 6 | onTemplateSelected: '&' 7 | }, 8 | controller: LandingPageController, 9 | template: require('./landing-page.html'), 10 | transclude: { 11 | 'landingsearch': 'landingsearch', 12 | 'landingheader': 'landingheader', 13 | 'landingbody': 'landingbody', 14 | 'landingside': 'landingside' 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /src/components/landing-page/landing-page.controller.ts: -------------------------------------------------------------------------------- 1 | import * as angular from 'angular'; 2 | import * as _ from 'lodash'; 3 | 4 | export class LandingPageController implements angular.IController { 5 | static $inject = ['$scope', 'Catalog', 'RecentlyViewedServiceItems']; 6 | 7 | public ctrl: any = this; 8 | private $scope: any; 9 | private Catalog: any; 10 | private RecentlyViewed: any; 11 | private plansByServiceClassName: any; 12 | 13 | constructor ($scope: any, Catalog: any, RecentlyViewedServiceItems: any) { 14 | this.$scope = $scope; 15 | this.Catalog = Catalog; 16 | this.RecentlyViewed = RecentlyViewedServiceItems; 17 | this.plansByServiceClassName = {}; 18 | } 19 | 20 | public $onInit() { 21 | this.ctrl.searchText = ''; 22 | this.ctrl.orderingPanelVisible = false; 23 | 24 | this.Catalog.getServicePlans().then((plans: any) => { 25 | if (!plans) { 26 | return; 27 | } 28 | 29 | plans = _.reject(plans.by("metadata.name"), { 30 | status: { 31 | removedFromBrokerCatalog: true 32 | } 33 | }); 34 | this.plansByServiceClassName = this.Catalog.groupPlansByServiceClassName(plans); 35 | }); 36 | 37 | this.$scope.$on('open-overlay-panel', (event: any, item: any) => { 38 | this.ctrl.servicePlansForItem = null; 39 | if (item.kind === 'Template') { 40 | let cb = this.ctrl.onTemplateSelected(); 41 | if (cb) { 42 | cb(item.resource); 43 | } 44 | return; 45 | } 46 | 47 | if (item.kind === 'ClusterServiceClass') { 48 | this.ctrl.servicePlansForItem = this.plansByServiceClassName[item.resource.metadata.name]; 49 | } 50 | 51 | this.ctrl.selectedItem = item; 52 | this.ctrl.orderingPanelVisible = true; 53 | }); 54 | } 55 | 56 | public $onDestroy() { 57 | if (this.ctrl.orderingPanelVisible) { 58 | this.closeOrderingPanel(); 59 | } 60 | } 61 | 62 | public closeOrderingPanel = () => { 63 | this.RecentlyViewed.addItem(this.ctrl.selectedItem.resource.metadata.uid); 64 | this.ctrl.orderingPanelVisible = false; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/components/landing-page/landing-page.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 10 | 11 | 16 | 17 | 18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | -------------------------------------------------------------------------------- /src/components/order-service/order-service-bind-parameters.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 9 | 10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /src/components/order-service/order-service-bind.html: -------------------------------------------------------------------------------- 1 |
2 | 10 | 11 |
12 | -------------------------------------------------------------------------------- /src/components/order-service/order-service-configure.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 8 | 13 | 14 |
15 |
16 | {{$ctrl.error}} 17 |
18 |
19 |
20 | -------------------------------------------------------------------------------- /src/components/order-service/order-service-info.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | 6 |
7 |
8 |
9 | {{$ctrl.serviceName}} 10 |
11 |
12 | {{$ctrl.serviceClass.vendor}} 13 |
14 |
15 | 16 | {{tag}} 17 | 18 |
19 | 27 |
28 |
29 |
30 |

31 | 32 | Plan {{$ctrl.selectedPlan.spec.externalMetadata.displayName}} 33 | 34 | 35 | {{$ctrl.selectedPlan.spec.description}} 36 |

37 |

No description provided.

38 |

39 |

40 |
41 |
Image Dependencies
42 |
43 | 44 | Image 45 | {{imageName}} 46 |
47 |
48 |
49 |
50 | -------------------------------------------------------------------------------- /src/components/order-service/order-service-plans.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 | -------------------------------------------------------------------------------- /src/components/order-service/order-service-results.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 | Pending 7 |
8 |

9 | {{$ctrl.serviceClass.name}} is being provisioned in {{$ctrl.projectDisplayName}}. 10 |

11 |

12 | The binding will be created after the service has been provisioned. 13 | This may take several minutes. 14 |

15 |
16 |
17 |

18 | Continue to the project overview to check the status of your service. 19 |

20 |
21 |
22 |
23 |
24 | 25 | Error 26 |
27 |

28 | {{$ctrl.serviceClass.name}} failed to provision in {{$ctrl.projectDisplayName}}. 29 |

30 |
31 |
32 |
33 | {{$ctrl.error.message}} 34 | An error occurred provisioning the service. 35 |
36 |
37 |
38 |
39 | 40 | Success 41 |
42 |

43 | {{$ctrl.serviceClass.name}} has been added to {{$ctrl.projectDisplayName}} successfully. 44 |

45 |
46 |
47 |
48 |
49 | 56 | 57 |

Continue to the project overview.

58 |
59 |
60 |

Continue to the project overview to bind this service to your application. Binding this service creates a secret containing the information necessary for your application to use the service.

61 |
62 |
63 |

- or -

64 |

Browse resources for {{$ctrl.serviceClass.name}}:

65 | 76 |
77 |
78 | -------------------------------------------------------------------------------- /src/components/order-service/order-service.component.ts: -------------------------------------------------------------------------------- 1 | import {OrderServiceController} from './order-service.controller'; 2 | 3 | export const orderService: angular.IComponentOptions = { 4 | bindings: { 5 | baseProjectUrl: '@', 6 | serviceClass: '<', 7 | servicePlans: '<', 8 | handleClose: '<', 9 | addToProject: ' 2 | 12 | 21 |
22 |
23 |
24 |
25 |
26 | 27 | -------------------------------------------------------------------------------- /src/components/overlay-panel/overlay-panel.component.ts: -------------------------------------------------------------------------------- 1 | import {OverlayPanelController} from './overlay-panel.controller'; 2 | 3 | export const overlayPanel: angular.IComponentOptions = { 4 | bindings: { 5 | showClose: '<', 6 | showPanel: '<', 7 | handleClose: '<' 8 | }, 9 | controller: OverlayPanelController, 10 | template: require('./overlay-panel.html'), 11 | transclude: true 12 | }; 13 | -------------------------------------------------------------------------------- /src/components/overlay-panel/overlay-panel.controller.ts: -------------------------------------------------------------------------------- 1 | import * as angular from 'angular'; 2 | import * as $ from 'jquery'; 3 | 4 | export class OverlayPanelController implements angular.IController { 5 | static $inject = ['$document', '$scope']; 6 | 7 | public ctrl: any = this; 8 | private $document: any; 9 | private $scope: any; 10 | 11 | constructor ($document: any, $scope: any) { 12 | this.$document = $document; 13 | this.$scope = $scope; 14 | this.ctrl.shown = false; 15 | } 16 | 17 | public $postLink() { 18 | if (this.ctrl.showPanel) { 19 | this.showDialog(); 20 | } 21 | } 22 | 23 | public $onChanges(onChangesObj: angular.IOnChangesObject) { 24 | if (onChangesObj.showPanel) { 25 | if (this.ctrl.showPanel) { 26 | this.showDialog(); 27 | } else { 28 | this.hideDialog(); 29 | } 30 | } 31 | } 32 | 33 | public $onDestroy() { 34 | $('body').removeClass('overlay-open'); 35 | } 36 | 37 | public closePanel = () => { 38 | if (angular.isFunction(this.ctrl.handleClose)) { 39 | this.ctrl.handleClose(); 40 | } 41 | }; 42 | 43 | private showDialog = () => { 44 | this.ctrl.shown = true; 45 | $('body').addClass('overlay-open'); 46 | this.$document.on('keydown', this.closeOnEsc); 47 | }; 48 | 49 | private hideDialog = () => { 50 | this.ctrl.shown = false; 51 | $('body').removeClass('overlay-open'); 52 | this.$document.off('keydown', this.closeOnEsc); 53 | }; 54 | 55 | private closeOnEsc = (event: any) => { 56 | if (event.which === 27) { 57 | // Only close this overlay if another handler has not called 58 | // preventDefault() on this event. This means only one overlay/modal will 59 | // be closed per escape keydown event. 60 | if (!event.isDefaultPrevented()) { 61 | event.preventDefault(); 62 | this.$scope.$evalAsync(() => { 63 | this.closePanel(); 64 | }); 65 | } 66 | } 67 | }; 68 | } 69 | -------------------------------------------------------------------------------- /src/components/overlay-panel/overlay-panel.html: -------------------------------------------------------------------------------- 1 | 15 | -------------------------------------------------------------------------------- /src/components/projects-summary/projects-summary.component.ts: -------------------------------------------------------------------------------- 1 | import {ProjectsSummaryController} from './projects-summary.controller'; 2 | 3 | export const projectsSummary: angular.IComponentOptions = { 4 | bindings: { 5 | baseProjectUrl: '@', 6 | catalogItems: '<', 7 | projectsUrl: '@', 8 | viewEditMembership: '&', 9 | startTour: '&' 10 | }, 11 | controller: ProjectsSummaryController, 12 | template: require('./projects-summary.html') 13 | }; 14 | -------------------------------------------------------------------------------- /src/components/saas-list/saas-list.component.ts: -------------------------------------------------------------------------------- 1 | import {SaasListController} from './saas-list.controller'; 2 | 3 | export const saasList: angular.IComponentOptions = { 4 | bindings: { 5 | saasTitle: ' 4) || (offeringCount > 2 && windowWidth < this.BREAKPOINTS.screenLgMin); 53 | } 54 | 55 | private onWindowResize = () => { 56 | this.$scope.$evalAsync(() => { 57 | this.updateListExpandVisibility(); 58 | }); 59 | }; 60 | } 61 | -------------------------------------------------------------------------------- /src/components/saas-list/saas-list.html: -------------------------------------------------------------------------------- 1 | 2 |

{{$ctrl.saasTitle}}

3 | 20 | 25 |
26 | -------------------------------------------------------------------------------- /src/components/select-plan/select-plan.component.ts: -------------------------------------------------------------------------------- 1 | import {SelectPlanController} from './select-plan.controller.ts'; 2 | 3 | export const selectPlan: angular.IComponentOptions = { 4 | bindings: { 5 | availablePlans: '<', // array of the available plans 6 | selectedPlan: '<', 7 | onPlanSelect: '<' 8 | }, 9 | controller: SelectPlanController, 10 | template: require('./selectPlan.html') 11 | }; 12 | -------------------------------------------------------------------------------- /src/components/select-plan/select-plan.controller.ts: -------------------------------------------------------------------------------- 1 | import * as angular from 'angular'; 2 | import * as _ from 'lodash'; 3 | 4 | export class SelectPlanController implements angular.IController { 5 | 6 | public ctrl: any = this; 7 | 8 | public $onInit() { 9 | this.updatePlans(); 10 | } 11 | 12 | public $onChanges(onChangesObj: angular.IOnChangesObject) { 13 | if ((onChangesObj.availablePlans && !onChangesObj.availablePlans.isFirstChange()) || 14 | (onChangesObj.selectedPlan && !onChangesObj.selectedPlan.isFirstChange())) { 15 | this.updatePlans(); 16 | } 17 | } 18 | 19 | private updatePlans() { 20 | this.ctrl.plansAvailable = _.size(this.ctrl.availablePlans) > 0; 21 | if (this.ctrl.plansAvailable) { 22 | if (!this.ctrl.selectedPlan) { 23 | this.ctrl.selectedPlan = this.ctrl.availablePlans[0]; 24 | } 25 | this.ctrl.planIndex = this.ctrl.availablePlans.indexOf(this.ctrl.selectedPlan); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/components/select-plan/selectPlan.html: -------------------------------------------------------------------------------- 1 |
2 |

Select a Plan

3 |
4 | 13 |
14 |
15 |
16 | 17 |
18 |

19 | No Plans Available 20 |

21 |

22 | There are no plans currently available for this service. 23 |

24 |
25 |
26 | -------------------------------------------------------------------------------- /src/components/select-project/select-project.component.ts: -------------------------------------------------------------------------------- 1 | import {SelectProjectController} from './select-project.controller.ts'; 2 | 3 | export const selectProject: angular.IComponentOptions = { 4 | bindings: { 5 | selectedProject: '=?', 6 | preselectProjectName: '@?', 7 | nameTaken: '<', 8 | onProjectSelected: ' 2 |
3 |
4 | 9 | 10 |
11 |
12 | {{$ctrl.error}} 13 |
14 |
15 | 16 | -------------------------------------------------------------------------------- /src/components/update-service/update-service-plans.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 | -------------------------------------------------------------------------------- /src/components/update-service/update-service-results.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | Updating 6 |
7 |

8 | {{$ctrl.displayName}} is being updated in {{$ctrl.project | displayName}}. 9 |

10 |
11 |
12 |
13 |
14 |
15 | 16 | Error 17 |
18 |

19 | Failed to update {{$ctrl.displayName}} in {{$ctrl.project | displayName}}. 20 |

21 |
22 |
23 |
24 | 25 | {{$ctrl.error.message}} 26 | 27 | 28 | An error occurred updating the service. 29 | 30 |
31 |
32 |
33 |
34 | 35 | Success 36 |
37 |

38 | {{$ctrl.displayName}} has been updated in {{$ctrl.project | displayName}} successfully. 39 |

40 |
41 |
42 |
43 |
44 | -------------------------------------------------------------------------------- /src/components/update-service/update-service.component.ts: -------------------------------------------------------------------------------- 1 | import {UpdateServiceController} from './update-service.controller'; 2 | 3 | export const updateService: angular.IComponentOptions = { 4 | bindings: { 5 | displayName: '<', 6 | project: '<', 7 | baseProjectUrl: '@', 8 | serviceInstance: '<', 9 | serviceClass: '<', 10 | servicePlans: '<', 11 | handleClose: '<' 12 | }, 13 | controller: UpdateServiceController, 14 | template: require('./update-service.html') 15 | }; 16 | -------------------------------------------------------------------------------- /src/components/update-service/update-service.html: -------------------------------------------------------------------------------- 1 |
2 | 11 | 20 |
21 |
22 |
23 |
24 |
25 |
26 | -------------------------------------------------------------------------------- /src/decorators/bootstrap/array.html: -------------------------------------------------------------------------------- 1 |
4 | 5 |
    6 |
  1. 10 | 17 |
  2. 18 |
19 |
20 |
23 | 24 | 31 |
32 |
33 | -------------------------------------------------------------------------------- /src/decorators/bootstrap/checkbox.html: -------------------------------------------------------------------------------- 1 |
2 |
4 | 14 |
15 |
16 |
17 | -------------------------------------------------------------------------------- /src/decorators/bootstrap/checkboxes.html: -------------------------------------------------------------------------------- 1 |
5 | 9 | 10 |
11 |
12 | 21 |
22 |
23 |
24 |
25 | -------------------------------------------------------------------------------- /src/decorators/bootstrap/default.html: -------------------------------------------------------------------------------- 1 |
2 |
4 | 5 | 6 |
7 | 21 | 22 |
24 | 27 | 39 | 40 | 43 |
44 | 45 | 49 | 50 | {{ hasSuccess() ? '(success)' : '(error)' }} 53 | 54 |
55 |
56 |
57 |
58 | 59 | 68 |
69 | {{form.title}} values don't match. 70 |
71 |
72 |
73 | -------------------------------------------------------------------------------- /src/decorators/bootstrap/select.html: -------------------------------------------------------------------------------- 1 |
3 | 6 |
7 | 12 | 13 | {{$select.selected.name}} 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |
22 | -------------------------------------------------------------------------------- /src/filters/escapeRegExp.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | 3 | export function escapeRegExpFilter() { 4 | return _.escapeRegExp; 5 | } 6 | -------------------------------------------------------------------------------- /src/filters/projectUrl.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | 3 | export function projectUrlFilter () { 4 | 5 | return function(project: any, base: any) { 6 | var baseUrl: string = base || 'project/'; 7 | var projectName : string; 8 | if (_.isString(project)) { 9 | projectName = project; 10 | } else { 11 | projectName = _.get(project, 'metadata.name', ''); 12 | } 13 | 14 | if (!baseUrl.endsWith('/')) { 15 | baseUrl += '/'; 16 | } 17 | 18 | // Make sure to use `/overview` so that the wizard doesn't trigger a route 19 | // change after "Continue to overview" is clicked when already on the 20 | // overview. This causes flicker and a page reload. 21 | return baseUrl + projectName + '/overview'; 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /src/filters/secretUrl.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | 3 | export function secretUrlFilter () { 4 | 5 | return function(project: any, base: any, secretName: any) { 6 | var baseUrl: string = base || 'project/'; 7 | var projectName : string; 8 | if (_.isString(project)) { 9 | projectName = project; 10 | } else { 11 | projectName = _.get(project, 'metadata.name', ''); 12 | } 13 | 14 | if (!baseUrl.endsWith('/')) { 15 | baseUrl += '/'; 16 | } 17 | 18 | return baseUrl + projectName + '/browse/secrets/' + secretName; 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as angular from 'angular'; 2 | import './styles/main.less'; 3 | 4 | // Aphabetical order please 5 | require('./constants'); 6 | 7 | 8 | // Filters 9 | import {escapeRegExpFilter} from './filters/escapeRegExp'; 10 | import {projectUrlFilter} from './filters/projectUrl'; 11 | import {secretUrlFilter} from './filters/secretUrl'; 12 | 13 | // Components, Alphabetical order please 14 | import {BuilderAppService} from './services/builder-app.service'; 15 | import {catalogParameters} from './components/catalog-parameters/catalog-parameters.component'; 16 | import {catalogSearch} from './components/catalog-search/catalog-search.component'; 17 | import {CatalogService} from './services/catalog.service'; 18 | import {createFromBuilder} from './components/create-from-builder/create-from-builder.component'; 19 | import {landingPage} from './components/landing-page/landing-page.component'; 20 | import {orderService} from './components/order-service/order-service.component'; 21 | import {overlayPanel} from './components/overlay-panel/overlay-panel.component'; 22 | import {projectsSummary} from './components/projects-summary/projects-summary.component'; 23 | import {saasList} from './components/saas-list/saas-list.component'; 24 | import {selectPlan} from './components/select-plan/select-plan.component'; 25 | import {selectProject} from './components/select-project/select-project.component'; 26 | import {servicesView} from './components/services-view/services-view.component'; 27 | import {updateService} from './components/update-service/update-service.component'; 28 | import {RecentlyViewedServiceItems} from './services/recently-viewed-service-items.service'; 29 | import {catalogFilter} from './components/catalog-filter/catalog-filter.component'; 30 | 31 | export const webCatalog: string = 'webCatalog'; 32 | 33 | angular 34 | .module(webCatalog, ['patternfly', 'ngAnimate', 'ui.bootstrap', 'angularMoment', 'ui.select', 'schemaForm']) 35 | .service('BuilderAppService', BuilderAppService) 36 | .service('Catalog', CatalogService) 37 | .service('RecentlyViewedServiceItems', RecentlyViewedServiceItems) 38 | .filter('escapeRegExp', escapeRegExpFilter) 39 | .filter('projectUrl', projectUrlFilter) 40 | .filter('secretUrl', secretUrlFilter) 41 | .component('catalogParameters', catalogParameters) 42 | .component('catalogSearch', catalogSearch) 43 | .component('createFromBuilder', createFromBuilder) 44 | .component('landingPage', landingPage) 45 | .component('orderService', orderService) 46 | .component('overlayPanel', overlayPanel) 47 | .component('projectsSummary', projectsSummary) 48 | .component('saasList', saasList) 49 | .component('selectPlan', selectPlan) 50 | .component('selectProject', selectProject) 51 | .component('servicesView', servicesView) 52 | .component('updateService', updateService) 53 | .component('catalogFilter', catalogFilter) 54 | .run(['$templateCache', function($templateCache: any) { 55 | $templateCache.put('catalog-search/catalog-search-result.html', require('./components/catalog-search/catalog-search-result.html')); 56 | $templateCache.put('create-from-builder/create-from-builder-info.html', require('./components/create-from-builder/create-from-builder-info.html')); 57 | $templateCache.put('create-from-builder/create-from-builder-configure.html', require('./components/create-from-builder/create-from-builder-configure.html')); 58 | $templateCache.put('create-from-builder/create-from-builder-bind.html', require('./components/create-from-builder/create-from-builder-bind.html')); 59 | $templateCache.put('create-from-builder/create-from-builder-results.html', require('./components/create-from-builder/create-from-builder-results.html')); 60 | $templateCache.put('order-service/order-service-info.html', require('./components/order-service/order-service-info.html')); 61 | $templateCache.put('order-service/order-service-plans.html', require('./components/order-service/order-service-plans.html')); 62 | $templateCache.put('order-service/order-service-configure.html', require('./components/order-service/order-service-configure.html')); 63 | $templateCache.put('order-service/order-service-bind.html', require('./components/order-service/order-service-bind.html')); 64 | $templateCache.put('order-service/order-service-bind-parameters.html', require('./components/order-service/order-service-bind-parameters.html')); 65 | $templateCache.put('order-service/order-service-results.html', require('./components/order-service/order-service-results.html')); 66 | $templateCache.put('update-service/update-service-plans.html', require('./components/update-service/update-service-plans.html')); 67 | $templateCache.put('update-service/update-service-configure.html', require('./components/update-service/update-service-configure.html')); 68 | $templateCache.put('update-service/update-service-results.html', require('./components/update-service/update-service-results.html')); 69 | 70 | // Override the default angular-schema-form-bootstrap decorators for custom 71 | // styles to support horizontal forms (and eventually valueFrom parameters). 72 | // Adapted from files in node_modules/angular-schema-form-bootstrap/src 73 | // See https://github.com/json-schema-form/angular-schema-form-bootstrap/issues/22 74 | // 75 | $templateCache.put('decorators/bootstrap/array.html', require('./decorators/bootstrap/array.html')); 76 | $templateCache.put('decorators/bootstrap/checkbox.html', require('./decorators/bootstrap/checkbox.html')); 77 | $templateCache.put('decorators/bootstrap/checkboxes.html', require('./decorators/bootstrap/checkboxes.html')); 78 | $templateCache.put('decorators/bootstrap/default.html', require('./decorators/bootstrap/default.html')); 79 | $templateCache.put('decorators/bootstrap/select.html', require('./decorators/bootstrap/select.html')); 80 | }]); 81 | -------------------------------------------------------------------------------- /src/services/recently-viewed-service-items.service.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | 3 | export class RecentlyViewedServiceItems { 4 | 5 | static $inject = ['$rootScope']; 6 | private $rootScope: any; 7 | 8 | constructor ($rootScope: any) { 9 | this.$rootScope = $rootScope; 10 | } 11 | 12 | public getItems() { 13 | let recentlyViewed: any = localStorage.getItem('catalog-recently-viewed-services'); 14 | return recentlyViewed ? JSON.parse(recentlyViewed) : []; 15 | } 16 | 17 | public addItem(uid: any) { 18 | let recentlyViewed: any = this.getItems(); 19 | 20 | // if previously viewed, remove from list 21 | _.remove(recentlyViewed, (rvUID) => { 22 | return rvUID === uid; 23 | }); 24 | 25 | // add to front of list 26 | recentlyViewed.unshift(uid); 27 | 28 | // limit to 4 items 29 | recentlyViewed = _.take(recentlyViewed, 4); 30 | 31 | this.setRecentlyViewedItems(recentlyViewed); 32 | } 33 | 34 | private setRecentlyViewedItems(recentlyViewed: any) { 35 | localStorage.setItem('catalog-recently-viewed-services', JSON.stringify(recentlyViewed)); 36 | this.$rootScope.$emit('recently-viewed-updated'); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/styles/animations.less: -------------------------------------------------------------------------------- 1 | @keyframes flash { 2 | 0% { opacity: 0; } 3 | 100% { opacity: 1; } 4 | } 5 | 6 | @keyframes modalBackdropIn { 7 | 0% { 8 | opacity: 0; 9 | } 10 | 100% { 11 | opacity: .5; 12 | } 13 | } 14 | @keyframes modalBackdropOut { 15 | 0% { 16 | opacity: .5; 17 | } 18 | 100% { 19 | opacity: 0; 20 | } 21 | } 22 | @keyframes modalSlideDown { 23 | 0% { 24 | opacity: .75; 25 | transform: translateY(-50px); 26 | } 27 | 100% { 28 | opacity: 1; 29 | transform: translateY(0); 30 | } 31 | } 32 | @keyframes modalSlideUp { 33 | 0% { 34 | opacity: .5; 35 | transform: translateY(0%); 36 | } 37 | 100% { 38 | opacity: 0; 39 | transform: translateY(-50px); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/styles/catalog.less: -------------------------------------------------------------------------------- 1 | // origin-web-catalog sans external dependency variables 2 | 3 | @import './animations.less'; 4 | @import './landing-page.less'; 5 | @import './mixins.less'; 6 | @import './catalogParameters.less'; 7 | @import './order-service.less'; 8 | @import './overlay-panel.less'; 9 | @import './projects-summary'; 10 | @import './saas-list.less'; 11 | @import './services-view.less'; 12 | @import './variables.less'; 13 | -------------------------------------------------------------------------------- /src/styles/catalogParameters.less: -------------------------------------------------------------------------------- 1 | .catalog-parameters { 2 | input.form-control[disabled] { 3 | background-color: transparent; 4 | border: 0; 5 | color: @text-color; 6 | cursor: default; 7 | margin-top: -3px; 8 | padding-left: 0; 9 | } 10 | &.readonly { 11 | .control-label { 12 | padding-right: 0; 13 | word-wrap: break-word; 14 | } 15 | 16 | form { 17 | margin-top: 10px; 18 | } 19 | 20 | .form-group { 21 | margin-bottom: 0; 22 | } 23 | 24 | fieldset { 25 | padding-left: 20px; 26 | > div { 27 | padding-left: 20px; 28 | } 29 | } 30 | legend { 31 | border: 0; 32 | font-size: @font-size-base; 33 | font-weight: 600; 34 | margin-bottom: 5px; 35 | } 36 | 37 | .schema-form-array { 38 | margin-left: 0; 39 | 40 | > div.clearfix { 41 | margin-top: -30px; 42 | } 43 | .list-group, .list-group-item { 44 | border: 0; 45 | margin-bottom: 0; 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/styles/landing-page.less: -------------------------------------------------------------------------------- 1 | .catalog-search { 2 | background: @landing-bg; 3 | background-position: @landing-bg-position; 4 | background-size: @landing-bg-size; 5 | border-bottom: solid 1px @search-area-border; 6 | padding: (@grid-gutter-width / 4) (@grid-gutter-width / 2); 7 | width: 100%; 8 | @media (max-width: @screen-xs-max) { 9 | .ios & { 10 | background-size: @landing-bg-size-ios-phone; 11 | } 12 | } 13 | @media (min-width: @screen-sm-min) { 14 | position: fixed; 15 | top: @navbar-height; 16 | width: calc(100% ~"-" @landing-side-bar-width-sm); 17 | } 18 | @media(min-width: @screen-lg-min) { 19 | width: calc(100% ~"-" @landing-side-bar-width-lg); 20 | } 21 | @media(min-width: @screen-xl-min) { 22 | width: calc(100% ~"-" @landing-side-bar-width-xl); 23 | } 24 | .catalog-search-icon { 25 | color: @color-pf-blue-300; 26 | font-size: 16px; 27 | left: 12px; 28 | position: absolute; 29 | top: 7px; 30 | } 31 | .catalog-search-input { 32 | text-indent: 30px; 33 | &:focus { 34 | border-color: @color-pf-blue-300; 35 | box-shadow: 0 0 8px @color-pf-light-blue-200; 36 | } 37 | } 38 | .catalog-search-match-info { 39 | flex: 1 1 0%; // for .word-break() 40 | .word-break(); 41 | } 42 | .dropdown-menu { 43 | margin-top: 0; 44 | padding: 0; 45 | width: 100%; 46 | > li > a { 47 | &.catalog-search-match { 48 | border-color: @color-pf-black-300; 49 | border-width: 0 0 1px 0; 50 | display: flex; 51 | line-height: 1.5; 52 | padding: 3px 10px 3px 5px; 53 | white-space: normal; 54 | } 55 | &.catalog-search-show-all { 56 | background-color: @color-pf-black-200; 57 | color: @link-color; 58 | padding: 5px 0; 59 | text-align: center; 60 | } 61 | } 62 | // important needed to override styles declared important in PatternFly 63 | .active > a { 64 | border-color: @color-pf-blue-100 !important; 65 | &.catalog-search-match { 66 | background-color: @color-pf-blue-50 !important; 67 | color: inherit !important; 68 | } 69 | &.catalog-search-show-all { 70 | background-color: @color-pf-black-200 !important; 71 | color: @link-hover-color !important; 72 | } 73 | } 74 | 75 | .catalog-search-show-none { 76 | background-color: @color-pf-black-200; 77 | border-color: @color-pf-black-300; 78 | border-width: 0 0 1px 0; 79 | padding: 5px 0; 80 | text-align: center; 81 | } 82 | } 83 | .landing-search-area & { 84 | z-index: @zindex-navbar-fixed - 1; 85 | .search-pf .has-clear .clear { 86 | height: 28px; 87 | } 88 | .search-pf-input-group { 89 | .form-control { 90 | font-size: (@font-size-base + 2); 91 | height: 30px; 92 | } 93 | .pficon-close { 94 | font-size: (@font-size-base + 4); 95 | } 96 | } 97 | } 98 | } 99 | 100 | // Special styles for mobile Safari 101 | @media only screen and (max-device-width: 736px) and (-webkit-min-device-pixel-ratio: 0) { 102 | .catalog-search { 103 | .catalog-search-icon { 104 | top: 9px; 105 | } 106 | .landing-search-form .search-pf-input-group .form-control { 107 | // font-size must be at least 16px on iOS to prevent the page from zooming when the search input is focused. 108 | font-size: 16px; 109 | } 110 | } 111 | } 112 | 113 | .catalog-search-match { 114 | .catalog-search-match-icon { 115 | flex: 0 0 40px; 116 | padding-right: 5px; 117 | padding-top: 2px; 118 | text-align: center; 119 | } 120 | .catalog-search-match-label { 121 | font-weight: 600; 122 | .line-clamp(3, 1.5em); // limit to 3 lines 123 | } 124 | .icon { 125 | font-size: 28px; 126 | } 127 | img { 128 | max-height: 28px; 129 | max-width: 28px; 130 | } 131 | .tag { 132 | margin-right: 5px; 133 | text-transform: uppercase; 134 | } 135 | } 136 | 137 | .catalog-search-toggle { 138 | padding: 0 5px; 139 | } 140 | 141 | .landing { 142 | background: @landing-bg; 143 | background-position: @landing-bg-position; 144 | background-size: @landing-bg-size; 145 | @media (max-width: @screen-xs-max) { 146 | .ios & { 147 | background-size: @landing-bg-size-ios-phone; 148 | } 149 | } 150 | @media(min-width: @screen-sm-min) { 151 | bottom: 0; 152 | overflow-x: hidden; 153 | overflow-y: auto; 154 | position: absolute; 155 | top: @landing-offset-top; 156 | width: calc(100% ~"-" @landing-side-bar-width-sm); 157 | } 158 | @media(min-width: @screen-lg-min) { 159 | width: calc(100% ~"-" @landing-side-bar-width-lg); 160 | } 161 | @media(min-width: @screen-xl-min) { 162 | width: calc(100% ~"-" @landing-side-bar-width-xl); 163 | } 164 | } 165 | 166 | landingbody, 167 | .landing-body, 168 | .landing-body-area, 169 | .landing-main-area { 170 | display: flex; 171 | flex: 1 1 auto; 172 | } 173 | 174 | .landing-main-area { 175 | flex-direction: column; 176 | min-height: 100%; 177 | } 178 | 179 | .landing-search-form { 180 | margin: 0 auto; 181 | width: 100%; 182 | @media (min-width: @screen-sm-min) { 183 | max-width: 600px; 184 | width: 80%; 185 | } 186 | } 187 | 188 | .landing-side-bar { 189 | background-color: @landing-side-bar-bg; 190 | color: @color-pf-white; 191 | // Needs to be less than the navbar so dropdown menus don't open under the sidebar. 192 | z-index: @zindex-navbar-fixed - 1; 193 | @media(min-width: @screen-sm-min) { 194 | bottom: 0; 195 | overflow-y: auto; 196 | position: fixed; 197 | right: 0; 198 | top: @landing-side-bar-top-offset; 199 | width: @landing-side-bar-width-sm; 200 | } 201 | @media(min-width: @screen-lg-min) { 202 | width: @landing-side-bar-width-lg; 203 | } 204 | @media(min-width: @screen-xl-min) { 205 | width: @landing-side-bar-width-xl; 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /src/styles/main.less: -------------------------------------------------------------------------------- 1 | // origin-web-catalog 2 | 3 | @import "~font-awesome/less/variables.less"; 4 | @import '~bootstrap/less/variables.less'; 5 | @import '~patternfly/dist/less/color-variables.less'; 6 | @import '~patternfly/dist/less/variables.less'; 7 | @import '~origin-web-common/dist/less/_variables.less'; 8 | @import '~origin-web-common/dist/less/_mixins.less'; 9 | 10 | // add partials to catalog.less 11 | @import './catalog.less'; 12 | -------------------------------------------------------------------------------- /src/styles/mixins.less: -------------------------------------------------------------------------------- 1 | .scroll-shadows-vertical(@shadow-width: 75%, @shadow-opacity: 0.2) { 2 | background-attachment: scroll; 3 | background-image: radial-gradient(ellipse at top, rgba(0, 0, 0, @shadow-opacity) 0%, rgba(0, 0, 0, 0) @shadow-width), radial-gradient(ellipse at bottom, rgba(0, 0, 0, @shadow-opacity) 0%, rgba(0, 0, 0, 0) @shadow-width); 4 | background-position: 0 0, 0 100%; 5 | background-repeat: no-repeat; 6 | background-size: 100% 6px; 7 | } 8 | 9 | .scroll-shadows-vertical-covers(@shadow-cover-bg-color: rgba(255,255,255,1), @shadow-cover-bg-color-transparent: rgba(255,255,255,0)) { 10 | background-attachment: local; 11 | background-image: linear-gradient(@shadow-cover-bg-color 30%, @shadow-cover-bg-color-transparent), linear-gradient(@shadow-cover-bg-color-transparent, @shadow-cover-bg-color 70%); 12 | background-position: 0 0, 0 100%; 13 | background-repeat: no-repeat; 14 | background-size: 100% 12px; 15 | } 16 | -------------------------------------------------------------------------------- /src/styles/order-service.less: -------------------------------------------------------------------------------- 1 | .order-service-details { 2 | .order-service-details-top { 3 | align-items: center; 4 | display: flex; 5 | &.order-service-details-top-icon-top { 6 | align-items: flex-start; 7 | } 8 | .service-icon { 9 | margin-right: 15px; 10 | .icon { 11 | font-size: 60px; 12 | @media (min-width: @order-service-title-sm-min) { 13 | font-size: 64px; 14 | } 15 | } 16 | .image { 17 | img { 18 | max-height: 52px; 19 | max-width: 52px; 20 | @media (min-width: @order-service-title-sm-min) { 21 | max-height: 60px; 22 | max-width: 60px; 23 | } 24 | &[src$=".svg"] { 25 | height: 52px; 26 | @media (min-width: @order-service-title-sm-min) { 27 | height: 60px; 28 | } 29 | } 30 | } 31 | } 32 | } 33 | .service-title { 34 | font-size: 18px; 35 | font-weight: 600; 36 | .line-clamp(3, 1.4em); // limit to 3 lines 37 | @media (min-width: @order-service-title-sm-min) { 38 | font-size: 22px; 39 | } 40 | } 41 | .service-title-area { 42 | flex: 1 1 0%; // for .word-break() 43 | .word-break(); 44 | } 45 | .service-vendor { 46 | margin-top: 5px; 47 | } 48 | .sub-title { 49 | color: @color-pf-black-600; 50 | font-size: 20px; 51 | font-weight: 600; 52 | } 53 | } 54 | .order-service-description-block { 55 | margin-top: 15px; 56 | .description { 57 | white-space: pre-wrap; 58 | } 59 | .learn-more-link { 60 | font-size: @font-size-small; 61 | white-space: nowrap; 62 | } 63 | .order-service-dependent-image { 64 | margin-bottom: 5px; 65 | .word-break(); 66 | .pficon { 67 | margin-right: 5px; 68 | vertical-align: -1px; 69 | } 70 | } 71 | .order-service-subheading { 72 | // Prevent top margin from collapsing. 73 | display: inline-block; 74 | font-size: @font-size-base + 1; 75 | margin-bottom: 7px; 76 | margin-top: 10px; 77 | } 78 | } 79 | .order-service-documentation-url { 80 | margin-top: 4px; 81 | } 82 | .order-service-tags { 83 | color: @text-muted; 84 | margin-top: 5px; 85 | .tag { 86 | margin-right: 5px; 87 | text-transform: uppercase; 88 | } 89 | } 90 | } 91 | 92 | .order-service-config { 93 | .config-top { 94 | .adv-ops { 95 | height: 30%; 96 | } 97 | .adv-ops-href { 98 | cursor: pointer; 99 | font-size: 14px; 100 | &::after { 101 | content: '\f105'; 102 | font-family: 'FontAwesome'; 103 | padding-left: 10px; 104 | } 105 | &.collapsed { 106 | &::after { 107 | content: '\f107'; 108 | } 109 | } 110 | } 111 | .adv-ops-container { 112 | padding-top: 2px; 113 | } 114 | } 115 | .config-bottom { 116 | height: 12%; 117 | float: right; 118 | margin-right: -13px; 119 | margin-top: 8px; 120 | margin-right: -10px; 121 | } 122 | .footer-panel { 123 | padding-top: 10px; 124 | text-align: center; 125 | 126 | a { 127 | color: @color-pf-white; 128 | &:hover { 129 | color: @color-pf-white; 130 | text-decoration: none; 131 | } 132 | } 133 | } 134 | .form-group h3 { 135 | margin-left: -10px; 136 | } 137 | h3 { 138 | line-height: 1.4; 139 | margin-top: 0; 140 | + .alert { 141 | margin-top: 20px; 142 | } 143 | } 144 | .no-projects-cant-create { 145 | div, 146 | p { 147 | text-align: center; 148 | } 149 | } 150 | .or { 151 | margin-left: 15px; 152 | } 153 | .select-plans { 154 | .plan-name { 155 | display: inline-block; 156 | font-size: 14px; 157 | margin-bottom: 5px; 158 | margin-top: -2px; 159 | } 160 | .plan-description { 161 | margin-left: 10px; 162 | } 163 | .radio { 164 | margin-top: 15px; 165 | } 166 | } 167 | .select-project-divider { 168 | border-bottom: 1px solid @color-pf-black-400; 169 | margin-bottom: 18px; 170 | margin-top: 22px; 171 | } 172 | .sub-title { 173 | margin: 0 0 10px 0; 174 | .error-message { 175 | white-space: pre-line; 176 | } 177 | } 178 | .success-check { 179 | color: @color-pf-green-400; 180 | } 181 | .related-services-container { 182 | background-color: @color-pf-black-200; 183 | margin-top: 62px; 184 | padding: 14px; 185 | display: flex; 186 | align-items: center; 187 | .related-services-label { 188 | font-size: 14px; 189 | font-weight: 600; 190 | padding-right: 14px; 191 | } 192 | .related-services-row { 193 | .card { 194 | background-color: @color-pf-white; 195 | border:1px solid @color-pf-black-400; 196 | float: right; 197 | margin-right: 6px; 198 | padding: 11px; 199 | } 200 | } 201 | } 202 | } 203 | 204 | .order-service-wizard-step { 205 | .schema-form-fieldset { 206 | margin-bottom: 40px; 207 | margin-top: 40px; 208 | legend { 209 | border-bottom: none; 210 | border-top: solid 1px rgba(0, 0, 0, 0.15); 211 | margin-bottom: 10px; 212 | padding-top: 20px; 213 | } 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /src/styles/overlay-panel.less: -------------------------------------------------------------------------------- 1 | body.overlay-open { 2 | &, 3 | .landing, 4 | .landing-side-bar { 5 | overflow: hidden; 6 | } 7 | } 8 | 9 | .catalogs-overlay-modal { 10 | display: block; 11 | .modal-backdrop { 12 | &.ng-enter { 13 | animation: modalBackdropIn 0.15s ease-out forwards; 14 | } 15 | &.ng-leave { 16 | animation: modalBackdropOut .45s ease-out forwards; 17 | } 18 | } 19 | } 20 | 21 | .catalogs-overlay-panel { 22 | bottom: 0; 23 | left: 0; 24 | position: fixed; 25 | right: 0; 26 | top: 0; 27 | @media(min-width: @screen-sm-min) { 28 | margin: 0 auto; 29 | max-width: 900px; 30 | position: relative; 31 | } 32 | @media(min-width: @screen-sm-min) and (min-height: (@catalog-overlay-panel-height + @navbar-pf-height)) { 33 | margin-top: @navbar-pf-height; 34 | } 35 | // make overlay-panel like a bootstrap modal 36 | .overlay-panel-as-modal & { 37 | position: relative; 38 | @media(max-width: @screen-xs-max) { 39 | margin: 10px; 40 | } 41 | @media(min-width: @screen-sm-min) { 42 | max-width: 640px; 43 | } 44 | } 45 | 46 | .wizard-pf-contents { 47 | flex: 1 1 auto; 48 | } 49 | 50 | .wizard-pf-footer { 51 | bottom: 0; 52 | left: 0; 53 | position: absolute; 54 | right: 0; 55 | @media(min-width: @screen-sm-min) { 56 | position: relative; 57 | } 58 | 59 | .btn { 60 | font-size: 12px; // so that wizard-pf-footer in the console is 58px tall 61 | } 62 | } 63 | 64 | .wizard-pf-main { 65 | display: flex; 66 | overflow-y: auto; 67 | padding: 0; 68 | .scroll-shadows-vertical(); 69 | -webkit-overflow-scrolling: touch; // enable momentum scrolling in mobile Safari 70 | @media(max-width: @screen-xs-max) { 71 | bottom: 58px; // height of .wizard-pf-footer 72 | height: auto; 73 | left: 1px; 74 | position: fixed; 75 | right: 1px; 76 | top: 99px; // 99px = 98px (.order-service) + 1px (modal top "margin") 77 | width: auto; 78 | } 79 | @media(min-width: @screen-sm-min) { 80 | height: @order-service-page-height + @grid-gutter-width; 81 | max-height: calc(100vh ~"-" 222px); // 222px = 1px (modal top "margin") + 41px (.modal-header) + 121px (.wizard-stesp) + 58 (.wizard-pf-footer) + 1px (modal bottom "margin") 82 | min-height: 200px; 83 | } 84 | } 85 | 86 | .wizard-pf-main-form-contents { 87 | position: relative; // so label.required:before are positioned correctly 88 | label.required:before { 89 | left: -10px; 90 | } 91 | } 92 | 93 | .wizard-pf-main-inner-shadow-covers { 94 | min-height: 100%; 95 | padding: (@grid-gutter-width / 2); 96 | .scroll-shadows-vertical-covers(); 97 | @media(min-width: @screen-sm-min) { 98 | padding-left: @grid-gutter-width; 99 | padding-right: @grid-gutter-width; 100 | } 101 | // so that input and textarea form-controls don't mask the inner scroll shadows 102 | input, textarea { 103 | &.form-control { 104 | background-color: transparent; 105 | // so that disabled or readonly form-controls get the correct bg-color 106 | &[disabled], 107 | &[readonly] { 108 | background-color: rgba(234, 234, 234, 0.5); 109 | } 110 | } 111 | } 112 | } 113 | 114 | .wizard-pf-row { 115 | // PatternFly sets this to control the height of the wizard, but our layout 116 | // is more complex, so we disable the max-height 117 | max-height: none; 118 | } 119 | } 120 | 121 | .catalogs-overlay-panel-close { 122 | color: @color-pf-black-600; 123 | cursor: pointer; 124 | font-size: 21px; 125 | position: absolute; 126 | right: 20px; 127 | top: 15px; 128 | 129 | &.pficon-close { 130 | color: @color-pf-black-600; 131 | cursor: pointer; 132 | } 133 | &.pficon-close.disabled, .pficon-close:hover.disabled { 134 | color: @color-pf-black-400; 135 | cursor: not-allowed; 136 | } 137 | &.pficon-close:hover { 138 | color: @color-pf-black-800; 139 | } 140 | } 141 | 142 | .catalogs-overlay-panel-wrapper { 143 | bottom: 0; 144 | left: 0; 145 | overflow-x: hidden; 146 | overflow-y: auto; 147 | position: fixed; 148 | right: 0; 149 | top: 0; 150 | // Set z-index to 1040 (same as .modal-backdrop), but will still appear above .modal-backdrop because of markup structure. This enables the uib-modal background to overlay the catalog wizard modal, when both are displayed. 151 | z-index: @zindex-modal-background; 152 | &.ng-enter { 153 | animation: modalSlideDown 0.3s ease-out forwards; 154 | } 155 | &.ng-leave { 156 | animation: modalSlideUp 0.2s ease-out forwards; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/styles/saas-list.less: -------------------------------------------------------------------------------- 1 | .build-applications-view { 2 | background: linear-gradient(fade(@color-pf-blue-700, 20%), fade(@color-pf-blue-700, 0%)); 3 | 4 | h1 { 5 | color: @saas-text; 6 | padding: (@grid-gutter-width / 2); 7 | } 8 | } 9 | 10 | .saas-offerings-container { 11 | 12 | .saas-list { 13 | display: flex; 14 | justify-content: flex-start; 15 | margin: 0; 16 | overflow-x: hidden; 17 | 18 | @media (min-width: @screen-lg-min) { 19 | justify-content: space-around; 20 | } 21 | 22 | &.expanded { 23 | flex-wrap: wrap; 24 | justify-content: flex-start; 25 | .card { 26 | &:nth-child(n+3) { 27 | display: flex; // display all in expanded mode 28 | } 29 | } 30 | } 31 | 32 | .card { 33 | color: @saas-text; 34 | display: flex; 35 | width: 25%; 36 | 37 | @media (max-width: @screen-md-max) { 38 | width: 50%; 39 | &:nth-child(n+3) { 40 | display: none; // display first 2 41 | } 42 | } 43 | @media (min-width: @screen-lg-min) { 44 | &:nth-child(n+5) { 45 | display: none; // display first 4 46 | } 47 | } 48 | 49 | &:hover { 50 | background-color: @saas-hover; 51 | } 52 | } 53 | 54 | .card-content { 55 | align-items: center; 56 | color: @saas-text; 57 | display: flex; 58 | flex: 1 1 0%; // IE 11 59 | flex-direction: column; 60 | padding: (@grid-gutter-width / 2); 61 | text-align: center; 62 | text-shadow: 1px 1px 3px rgba(0,0,0,.5); 63 | 64 | &:focus, 65 | &:hover { 66 | text-decoration: none; 67 | } 68 | 69 | .card-description { 70 | font-size: @font-size-base; 71 | line-height: 1.5; 72 | max-width: 100%; // IE 11 73 | } 74 | 75 | .card-icon { 76 | .icon { 77 | font-size: @card-icon-sm; 78 | } 79 | img { 80 | height: @card-icon-sm; 81 | } 82 | } 83 | 84 | .card-title { 85 | font-size: @font-size-base + 2; 86 | 87 | @media (min-width: 480px) { 88 | font-size: @font-size-base + 4; 89 | } 90 | 91 | font-weight: 700; 92 | line-height: 1.4; 93 | margin-bottom: 10px; 94 | margin-top: 15px; 95 | max-width: 100%; // IE 11 96 | padding-left: 0; 97 | 98 | @media (min-width: @screen-lg-min) { 99 | font-size: @font-size-base + 6; 100 | } 101 | } 102 | } 103 | } 104 | .sass-list-expander { 105 | color: @color-pf-white; 106 | font-size: @font-size-base; 107 | &:hover, &:focus { 108 | color: @color-pf-white; 109 | text-decoration: none; 110 | } 111 | &:after { 112 | content: @fa-var-angle-down; 113 | font-family: @icon-font-name-fa; 114 | font-size: (@font-size-base + 2); 115 | padding: 0 5px; 116 | } 117 | &.expanded { 118 | &:after { 119 | content: @fa-var-angle-up; 120 | } 121 | .less { 122 | display: inline; 123 | } 124 | .more { 125 | display: none; 126 | } 127 | } 128 | .less { 129 | display: none; 130 | } 131 | } 132 | .sass-list-expander-container { 133 | margin-bottom: (@grid-gutter-width / 4); 134 | text-align: center; 135 | } 136 | 137 | .spinner-container { 138 | padding-top: 60px; 139 | padding-bottom: 60px; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /test/landing-page.spec.ts: -------------------------------------------------------------------------------- 1 | import * as angular from 'angular'; 2 | import 'angular-mocks'; 3 | import * as jQuery from 'jquery'; 4 | 5 | import {TestHelpers} from '../test/utils/testHelpers'; 6 | import {ComponentTest} from '../test/utils/ComponentTest'; 7 | import {LandingPageController} from '../src/components/landing-page/landing-page.controller'; 8 | 9 | import 'angular-patternfly'; 10 | 11 | describe('landingPage', () => { 12 | var componentTest: ComponentTest; 13 | var testHelpers: TestHelpers = new TestHelpers(); 14 | 15 | beforeEach( () => { 16 | testHelpers.initTests(); 17 | 18 | angular.mock.module('webCatalog', 'openshiftCommonUI', 'mockServices'); 19 | }); 20 | 21 | beforeEach(() => { 22 | var landingPageHtml: string = '' + 23 | '' + 24 | ' ' + 25 | '
' + 26 | '

I am the search

' + 27 | '
' + 28 | '
' + 29 | ' ' + 30 | '
' + 31 | '

I am the header

' + 32 | '
' + 33 | '
' + 34 | ' ' + 35 | '
' + 36 | '

I am the body

' + 37 | '
' + 38 | '
' + 39 | ' ' + 40 | '
' + 41 | '

I am the body

' + 42 | '
' + 43 | '
' + 44 | '
' + 45 | ''; 46 | componentTest = new ComponentTest(landingPageHtml); 47 | componentTest.createComponent({}); 48 | }); 49 | 50 | // testing rendered HTML 51 | it('should include the correct transclusions', () => { 52 | var element = componentTest.rawElement; 53 | 54 | var searchArea = jQuery(element).find('.landing-search-area'); 55 | expect(searchArea.length).toBe(1); 56 | 57 | var testSearchArea = jQuery(searchArea).find('#testSearch'); 58 | expect(testSearchArea.length).toBe(1); 59 | 60 | var headerArea = jQuery(element).find('.landing-header-area'); 61 | expect(headerArea.length).toBe(1); 62 | 63 | var testHeaderArea = jQuery(headerArea).find('#testHeader'); 64 | expect(testHeaderArea.length).toBe(1); 65 | 66 | var bodyArea = jQuery(element).find('.landing-body-area'); 67 | expect(bodyArea.length).toBe(1); 68 | 69 | var testBodyArea = jQuery(bodyArea).find('#testBody'); 70 | expect(testBodyArea.length).toBe(1); 71 | 72 | var sideArea = jQuery(element).find('.landing-side-bar'); 73 | expect(sideArea.length).toBe(1); 74 | 75 | var testSideArea = jQuery(sideArea).find('#testSide'); 76 | expect(testSideArea.length).toBe(1); 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /test/overlay-panel.spec.ts: -------------------------------------------------------------------------------- 1 | import * as angular from 'angular'; 2 | import 'angular-mocks'; 3 | import * as jQuery from 'jquery'; 4 | 5 | import {TestHelpers} from '../test/utils/testHelpers'; 6 | import {ComponentTest} from '../test/utils/ComponentTest'; 7 | import {OverlayPanelController} from '../src/components/overlay-panel/overlay-panel.controller'; 8 | 9 | import 'angular-patternfly'; 10 | import 'angular-animate'; 11 | 12 | describe('landingPage', () => { 13 | var componentTest: ComponentTest; 14 | var showPanel: boolean = false; 15 | var showClose: boolean = true; 16 | var closeCount: number = 0; 17 | 18 | var handleClose: any = function () { 19 | closeCount++; 20 | }; 21 | 22 | var testHelpers: TestHelpers = new TestHelpers(); 23 | 24 | beforeEach( () => { 25 | testHelpers.initTests(); 26 | angular.mock.module('webCatalog'); 27 | }); 28 | 29 | beforeEach(() => { 30 | closeCount = 0; 31 | var overlayPanelHTML: string = '' + 32 | '' + 33 | '
' + 34 | '
' + 35 | ''; 36 | componentTest = new ComponentTest(overlayPanelHTML); 37 | var attributes: any = { 38 | showPanel: showPanel, 39 | showClose: showClose, 40 | handleClose: handleClose 41 | }; 42 | componentTest.createComponent(attributes); 43 | }); 44 | 45 | // testing the isoScope $ctrl 46 | it('should have the correct $ctrl properties', () => { 47 | var ctrl = componentTest.isoScope.$ctrl; 48 | expect(ctrl.showPanel).toBe(showPanel); 49 | expect(ctrl.showClose).toBe(showClose); 50 | expect(ctrl.handleClose).toBe(handleClose); 51 | }); 52 | 53 | it('should hide until shown', () => { 54 | var element = componentTest.rawElement; 55 | 56 | var bodyArea = jQuery(element).find('.catalogs-overlay-modal'); 57 | expect(bodyArea.length).toBe(1); 58 | 59 | var content = jQuery(bodyArea).find('.catalogs-overlay-panel'); 60 | expect(content.length).toBe(0); 61 | 62 | componentTest.scope.showPanel = true; 63 | componentTest.scope.$apply(); 64 | 65 | content = jQuery(bodyArea).find('.catalogs-overlay-panel'); 66 | expect(content.length).toBe(1); 67 | 68 | }); 69 | 70 | it('should include the correct transclusions', () => { 71 | var element = componentTest.rawElement; 72 | 73 | componentTest.scope.showPanel = true; 74 | componentTest.scope.$apply(); 75 | 76 | var bodyArea = jQuery(element).find('.catalogs-overlay-modal'); 77 | expect(bodyArea.length).toBe(1); 78 | 79 | var trancludedContent = jQuery(bodyArea).find('.test-transcluded-area'); 80 | expect(trancludedContent.length).toBe(1); 81 | }); 82 | 83 | it('should show the close button based on showClose parameter', () => { 84 | var element = componentTest.rawElement; 85 | 86 | componentTest.scope.showPanel = true; 87 | componentTest.scope.$apply(); 88 | 89 | var closeButton = jQuery(element).find('.catalogs-overlay-panel-close'); 90 | expect(closeButton.length).toBe(1); 91 | 92 | componentTest.scope.showClose = false; 93 | componentTest.scope.$apply(); 94 | 95 | closeButton = jQuery(element).find('.catalogs-overlay-panel-close'); 96 | expect(closeButton.length).toBe(0); 97 | }); 98 | 99 | it('should request close when close button is clicked', () => { 100 | var element = componentTest.rawElement; 101 | 102 | componentTest.scope.showPanel = true; 103 | componentTest.scope.$apply(); 104 | 105 | var closeButton = jQuery(element).find('.catalogs-overlay-panel-close'); 106 | expect(closeButton.length).toBe(1); 107 | 108 | expect(closeCount).toBe(0); 109 | componentTest.eventFire(closeButton[0], 'click'); 110 | expect(closeCount).toBe(1); 111 | }); 112 | }); 113 | -------------------------------------------------------------------------------- /test/saas-list.spec.ts: -------------------------------------------------------------------------------- 1 | import * as angular from 'angular'; 2 | import * as $ from 'jquery'; 3 | import 'angular-mocks'; 4 | 5 | import {TestHelpers} from '../test/utils/testHelpers'; 6 | import {ComponentTest} from '../test/utils/ComponentTest'; 7 | import {SaasListController} from '../src/components/saas-list/saas-list.controller'; 8 | 9 | describe('saasOfferingsList', () => { 10 | var saasOfferings: any, saasTitle: string; 11 | var componentTest: ComponentTest; 12 | var testHelpers: TestHelpers = new TestHelpers(); 13 | var Constants: any; 14 | 15 | beforeEach( () => { 16 | testHelpers.initTests(); 17 | 18 | angular.mock.module('webCatalog', 'openshiftCommonUI', 'mockServices'); 19 | }); 20 | 21 | beforeEach(() => { 22 | inject(function (_Constants_: any) { 23 | Constants = _Constants_; 24 | }); 25 | }); 26 | 27 | beforeEach(() => { 28 | saasOfferings = angular.copy(Constants.SAAS_OFFERINGS); 29 | saasTitle = 'What do you want to build?'; 30 | }); 31 | 32 | beforeEach(() => { 33 | componentTest = new ComponentTest( 34 | '' 35 | ); 36 | }); 37 | 38 | it('should display SaaS Offerings correctly', () => { 39 | var attributes: any = { saasTitle: saasTitle, saasOfferings: saasOfferings}; 40 | componentTest.createComponent(attributes); 41 | 42 | var ctrl = componentTest.isoScope.$ctrl; 43 | expect(ctrl.saasTitle).toBe('What do you want to build?'); 44 | expect(ctrl.saasOfferings.length).toBe(4); 45 | 46 | var element = componentTest.rawElement; 47 | expect($(element).find('.card').length).toBe(4); 48 | }); 49 | 50 | it('should hide SaaS Offerings when none are defined', () => { 51 | saasOfferings = {}; 52 | var attributes: any = { saasTitle: saasTitle, saasOfferings: saasOfferings}; 53 | componentTest.createComponent(attributes); 54 | 55 | var ctrl = componentTest.isoScope.$ctrl; 56 | expect(ctrl.hasSaasOfferings()).toBeFalsy(); 57 | 58 | var element = componentTest.rawElement; 59 | expect($(element).find('.card').length).toBe(0); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /test/select-project.spec.ts: -------------------------------------------------------------------------------- 1 | import * as angular from 'angular'; 2 | import 'angular-mocks'; 3 | import * as jQuery from 'jquery'; 4 | import * as _ from 'lodash'; 5 | 6 | import {TestHelpers} from '../test/utils/testHelpers'; 7 | import {ComponentTest} from '../test/utils/ComponentTest'; 8 | import {SelectProjectController} from '../src/components/select-project/select-project.controller'; 9 | // import {DataServiceData} from '../app/mockServices/mockData.service'; 10 | // import {projectsData} from '../app/mockServices/mockData/projects'; 11 | 12 | import 'angular-patternfly'; 13 | import 'angular-animate'; 14 | 15 | describe('Select Project Component', () => { 16 | var testHelpers: TestHelpers = new TestHelpers(); 17 | var componentTest: ComponentTest; 18 | var selectedProject: any; 19 | var nameTaken: boolean; 20 | var $timeout: any; 21 | var $window: any; 22 | var $q: any; 23 | var ProjectsService: any; 24 | var DataService: any; 25 | var expectedCanCreate: boolean = true; 26 | 27 | beforeEach( () => { 28 | testHelpers.initTests(); 29 | 30 | angular.mock.module('webCatalog', 'openshiftCommonUI', 'mockServices'); 31 | }); 32 | 33 | beforeEach(() => { 34 | inject(function (_$window_: any, _$timeout_: any, _$q_: any, _ProjectsService_: any, _DataService_: any) { 35 | $window = _$window_; 36 | $timeout = _$timeout_; 37 | $q = _$q_; 38 | ProjectsService = _ProjectsService_; 39 | DataService = _DataService_; 40 | }); 41 | }); 42 | 43 | var createSelectProjectDropdown = function() { 44 | selectedProject = null; 45 | nameTaken = false; 46 | var selectProjectHtml: string = ''; 47 | componentTest = new ComponentTest(selectProjectHtml); 48 | 49 | var attributes: any = { 50 | selectedProject: selectedProject, 51 | nameTaken: nameTaken, 52 | }; 53 | 54 | componentTest.createComponent(attributes); 55 | }; 56 | 57 | beforeEach(() => { 58 | expectedCanCreate = true; 59 | spyOn(ProjectsService, 'canCreate').and.callFake(function() { 60 | let deferred = this.$q.defer(); 61 | if (!expectedCanCreate) { 62 | deferred.reject({status: 403}); 63 | } else { 64 | deferred.resolve(); 65 | } 66 | return deferred.promise; 67 | }); 68 | }); 69 | 70 | it('should have the correct project list', () => { 71 | createSelectProjectDropdown(); 72 | 73 | var element = componentTest.rawElement; 74 | 75 | $timeout.flush(); 76 | 77 | var projectSelect = jQuery(element).find('.ui-select-toggle'); 78 | expect(projectSelect.length).toBe(1); 79 | 80 | componentTest.eventFire(projectSelect[0], 'click'); 81 | 82 | var projects = jQuery(element).find('.ui-select-choices-row'); 83 | // 'Create Project' + 3 projects 84 | expect(projects.length).toBe(4); 85 | 86 | // 'Create Project' should be first in the list 87 | expect(_.trim(jQuery(projects[0]).text())).toBe('Create Project'); 88 | }); 89 | 90 | it("should not show 'Create Project' when user cannot create projects", () => { 91 | expectedCanCreate = false; 92 | createSelectProjectDropdown(); 93 | 94 | var element = componentTest.rawElement; 95 | 96 | $timeout.flush(); 97 | 98 | var projectSelect = jQuery(element).find('.ui-select-toggle'); 99 | expect(projectSelect.length).toBe(1); 100 | 101 | componentTest.eventFire(projectSelect[0], 'click'); 102 | 103 | var projects = jQuery(element).find('.ui-select-choices-row'); 104 | // 3 projects, no 'Create Project' 105 | expect(projects.length).toBe(3); 106 | 107 | // 'Create Project' should not be first in the list 108 | expect(_.trim(jQuery(projects[0]).text())).not.toBe('Create Project'); 109 | }); 110 | 111 | it("should show the new project fields when 'Create Project' selected", () => { 112 | createSelectProjectDropdown(); 113 | 114 | var element = componentTest.rawElement; 115 | 116 | $timeout.flush(); 117 | 118 | // 'Create Project' form fields initially hidden 119 | expect(jQuery(element).find('#name').length).toBe(0); 120 | expect(jQuery(element).find('#displayName').length).toBe(0); 121 | expect(jQuery(element).find('#description').length).toBe(0); 122 | 123 | var projectSelect = jQuery(element).find('.ui-select-toggle'); 124 | expect(projectSelect.length).toBe(1); 125 | 126 | componentTest.eventFire(projectSelect[0], 'click'); 127 | 128 | var projects = jQuery(element).find('.ui-select-choices-row'); 129 | // 'Create Project' + 3 projects 130 | expect(projects.length).toBe(4); 131 | 132 | // 'Create Project' should be first in the list 133 | expect(_.trim(jQuery(projects[0]).text())).toBe('Create Project'); 134 | 135 | componentTest.eventFire(projects[0], 'click'); 136 | componentTest.scope.$digest(); 137 | 138 | expect(jQuery(element).find('#name').length).toBe(1); 139 | expect(jQuery(element).find('#displayName').length).toBe(1); 140 | expect(jQuery(element).find('#description').length).toBe(1); 141 | }); 142 | }); 143 | -------------------------------------------------------------------------------- /test/test-bundle.ts: -------------------------------------------------------------------------------- 1 | // this file is an entry point for angular tests 2 | // avoids some weird issues when using webpack + angular. 3 | 4 | requireAll((require).context('./', true, /spec.ts$/)); 5 | function requireAll(r: any): any { 6 | r.keys().forEach(r); 7 | } 8 | -------------------------------------------------------------------------------- /test/utils/ComponentTest.ts: -------------------------------------------------------------------------------- 1 | import * as angular from 'angular'; 2 | import IRootScopeService = angular.IRootScopeService; 3 | import ICompileService = angular.ICompileService; 4 | import * as $ from 'jquery'; 5 | 6 | export class ComponentTest { 7 | public element: ng.IAugmentedJQuery; 8 | public scope: ng.IScope; 9 | public isoScope: ng.IScope; 10 | public rawElement: any; 11 | private rootScope: ng.IScope; 12 | private compile: ng.ICompileService; 13 | 14 | constructor(private template: string) { 15 | angular.mock.inject(($rootScope: IRootScopeService, $compile: ICompileService) => { 16 | this.rootScope = $rootScope; 17 | this.compile = $compile; 18 | }); 19 | } 20 | 21 | public createComponent(attributes: any): void { 22 | this.scope = this.rootScope.$new(); 23 | for (var key in attributes) { 24 | if (attributes.hasOwnProperty(key)) { 25 | this.scope[key] = attributes[key]; 26 | } 27 | } 28 | this.element = this.compile(this.template)(this.scope); 29 | this.scope.$digest(); 30 | this.isoScope = this.element.isolateScope(); 31 | this.rawElement = this.element[0]; 32 | } 33 | 34 | public eventFire (el: any, etype: any) { 35 | // Workaround for redirect issue with tests running in safari 36 | if (etype === 'click') { 37 | if ($(el).attr("href") === '') { 38 | $(el).attr("href", "javascript:void(0)"); 39 | } 40 | } 41 | 42 | if (el.fireEvent) { 43 | (el.fireEvent('on' + etype)); 44 | } else { 45 | var evObj = document.createEvent('Events'); 46 | evObj.initEvent(etype, true, false); 47 | el.dispatchEvent(evObj); 48 | } 49 | } 50 | 51 | public fireKeyPressEvent (element: any, keyCode: number) { 52 | var win: any = window; 53 | var keyboardEvent: any = new win.KeyboardEvent( 54 | 'keypress', 55 | { 56 | bubbles: true, 57 | cancelable: true, 58 | shiftKey: false 59 | } 60 | ); 61 | 62 | delete keyboardEvent.keyCode; 63 | delete keyboardEvent.which; 64 | Object.defineProperty(keyboardEvent, 'keyCode', {'value': keyCode}); 65 | Object.defineProperty(keyboardEvent, 'which', {'value': keyCode}); 66 | 67 | element.dispatchEvent(keyboardEvent); 68 | 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /test/utils/testHelpers.ts: -------------------------------------------------------------------------------- 1 | require('patternfly/dist/js/patternfly'); 2 | require('angular-patternfly/dist/angular-patternfly'); 3 | require('angular-ui-bootstrap/ui-bootstrap'); 4 | require('angular-ui-bootstrap/ui-bootstrap-tpls'); 5 | require('ui-select/dist/select'); 6 | require('angular-sanitize/angular-sanitize'); 7 | require('angular-drag-and-drop-lists/angular-drag-and-drop-lists.min.js'); 8 | require('angular-animate/angular-animate.min.js'); 9 | require('angular-moment/angular-moment'); 10 | 11 | require('jquery/dist/jquery.min.js'); 12 | require('lodash/index.js'); 13 | 14 | require('imports-loader?define=>false!js-logger/src/logger'); 15 | require('urijs'); 16 | require('urijs/src/URITemplate.js'); 17 | require('angular-utf8-base64'); 18 | require('origin-web-common/dist/origin-web-common-ui.js'); 19 | require('angular-schema-form'); 20 | require('angular-schema-form-bootstrap'); 21 | 22 | import '../../src/index'; 23 | 24 | import {MockServicesModule} from '../../app/mockServices/mockServices.module'; 25 | 26 | export class TestHelpers { 27 | 28 | constructor() { 29 | beforeAll(() => { 30 | window.onbeforeunload = () => { 31 | return null; 32 | }; 33 | }); 34 | } 35 | 36 | public initTests(): void { 37 | new MockServicesModule(window); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "sourceMap": true, 4 | "emitDecoratorMetadata": true, 5 | "experimentalDecorators": true, 6 | "removeComments": false, 7 | "noImplicitAny": false, 8 | "typeRoots": ["node_modules/@types/"], 9 | "skipLibCheck": true, 10 | "types": [ 11 | "angular", 12 | "angular-mocks", 13 | "jquery", 14 | "node", 15 | "jasmine", 16 | "es6-shim", 17 | "lodash", 18 | "urijs" 19 | ] 20 | }, 21 | "compileOnSave": false, 22 | "filesGlob": [ 23 | "src/**/*.ts", 24 | "src/**/*.tsx", 25 | "!node_modules/**" 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "ban": [true, 4 | ["_", "extend"], 5 | ["_", "isNull"], 6 | ["_", "isDefined"] 7 | ], 8 | "class-name": true, 9 | "curly": true, 10 | "eofline": true, 11 | "forin": true, 12 | "indent": [true, 2], 13 | "interface-name": true, 14 | "jsdoc-format": true, 15 | "label-position": true, 16 | "label-undefined": true, 17 | "max-line-length": [false, 140], 18 | "member-ordering": [true, 19 | "public-before-private", 20 | "static-before-instance", 21 | "variables-before-functions" 22 | ], 23 | "no-arg": true, 24 | "no-bitwise": true, 25 | "no-console": [true, 26 | "debug", 27 | "info", 28 | "time", 29 | "timeEnd", 30 | "trace" 31 | ], 32 | "no-construct": true, 33 | "no-constructor-vars": false, 34 | "no-debugger": true, 35 | "no-duplicate-key": true, 36 | "no-duplicate-variable": true, 37 | "no-empty": true, 38 | "no-eval": true, 39 | "no-string-literal": true, 40 | "no-switch-case-fall-through": true, 41 | "trailing-comma": true, 42 | "no-trailing-whitespace": true, 43 | "no-unused-expression": true, 44 | "no-unused-variable": true, 45 | "no-unreachable": true, 46 | "no-use-before-declare": true, 47 | "no-var-requires": false, 48 | "one-line": [true, 49 | "check-open-brace", 50 | "check-catch", 51 | "check-else", 52 | "check-whitespace" 53 | ], 54 | "quotemark": [false], 55 | "radix": true, 56 | "semicolon": true, 57 | "triple-equals": [true, "allow-null-check"], 58 | "typedef": [true, 59 | "callSignature", 60 | "indexSignature", 61 | "parameter", 62 | "propertySignature", 63 | "variableDeclarator" 64 | ], 65 | "typedef-whitespace": [true, 66 | ["callSignature", "noSpace"], 67 | ["catchClause", "noSpace"], 68 | ["indexSignature", "space"] 69 | ], 70 | "use-strict": false, 71 | "variable-name": false, 72 | "whitespace": [true, 73 | "check-branch", 74 | "check-decl", 75 | "check-operator", 76 | "check-separator", 77 | "check-type" 78 | ] 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @author: @AngularClass 4 | */ 5 | 6 | // Look in ./config folder for webpack.dev.js 7 | switch (process.env.NODE_ENV) { 8 | case 'prod': 9 | case 'production': 10 | module.exports = require('./config/webpack.prod')({env: 'production'}); 11 | break; 12 | case 'test': 13 | case 'testing': 14 | module.exports = require('./config/webpack.test')({env: 'test'}); 15 | break; 16 | case 'dev': 17 | case 'development': 18 | default: 19 | module.exports = require('./config/webpack.dev')({env: 'development'}); 20 | } 21 | --------------------------------------------------------------------------------