├── .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 | [](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 |
2 |
13 |
47 |
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 |
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 |
9 |
10 |
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 |
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 |
17 |
18 |
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: '',
8 | readOnly: '',
9 | opaqueKeys: '',
10 | hideValues: '',
11 | model: '='
12 | },
13 | controller: CatalogParametersController,
14 | template: require('./catalog-parameters.html')
15 | };
16 |
--------------------------------------------------------------------------------
/src/components/catalog-parameters/catalog-parameters.html:
--------------------------------------------------------------------------------
1 |
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: '',
8 | searchToggleCallback: ''
9 | },
10 | controller: CatalogSearchController,
11 | template: require('./catalog-search.html')
12 | };
13 |
--------------------------------------------------------------------------------
/src/components/catalog-search/catalog-search.controller.ts:
--------------------------------------------------------------------------------
1 | import * as angular from 'angular';
2 | import * as _ from 'lodash';
3 | import * as $ from 'jquery';
4 |
5 | export class CatalogSearchController implements angular.IController {
6 | static $inject = ['$rootScope', '$scope', '$timeout', '$q', 'Catalog', 'Constants', 'KeywordService'];
7 |
8 | public ctrl: any = this;
9 |
10 | private Catalog: any;
11 | private Constants: any;
12 | private KeywordService: any;
13 | private $rootScope: any;
14 | private $scope: any;
15 | private $timeout: any;
16 | private $q: any;
17 | private loaded: boolean = false;
18 | private maxResultsToShow: number = 5;
19 |
20 | // Used when the user starts typing before the items have loaded.
21 | private searchDeferred: ng.IDeferred;
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 |
2 |
7 |
8 | Catalog Search
9 |
10 |
41 |
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 |
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: ''
9 | },
10 | controller: CreateFromBuilderController,
11 | template: require('./create-from-builder.html')
12 | };
13 |
14 |
--------------------------------------------------------------------------------
/src/components/create-from-builder/create-from-builder.html:
--------------------------------------------------------------------------------
1 |
2 |
12 |
21 |
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 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/components/order-service/order-service-bind-parameters.html:
--------------------------------------------------------------------------------
1 |
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 |
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 |
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 |
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: ''
10 |
11 | },
12 | controller: OrderServiceController,
13 | template: require('./order-service.html')
14 | };
15 |
--------------------------------------------------------------------------------
/src/components/order-service/order-service.html:
--------------------------------------------------------------------------------
1 |
2 |
12 |
21 |
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: '',
6 | saasOfferings: '<'
7 | },
8 | controller: SaasListController,
9 | template: require('./saas-list.html')
10 | };
11 |
--------------------------------------------------------------------------------
/src/components/saas-list/saas-list.controller.ts:
--------------------------------------------------------------------------------
1 | import * as angular from 'angular';
2 | import * as _ from 'lodash';
3 |
4 | export class SaasListController implements angular.IController {
5 | static $inject = ['$scope', '$window', '$element', 'BREAKPOINTS'];
6 |
7 | public ctrl: any = this;
8 | private $scope: any;
9 | private $window: any;
10 | private $element: any;
11 | private BREAKPOINTS: any;
12 | private debounceResize: any;
13 |
14 | constructor($scope: any, $window: any, $element: any, BREAKPOINTS: any) {
15 | this.$scope = $scope;
16 | this.$window = $window;
17 | this.$element = $element;
18 | this.BREAKPOINTS = BREAKPOINTS;
19 | this.ctrl.sassListExpanded = false;
20 | this.ctrl.itemsOverflow = false;
21 | }
22 |
23 | public $postLink() {
24 | this.debounceResize = _.debounce(this.onWindowResize, 50, { maxWait: 250 });
25 | angular.element(this.$window).on('resize', this.debounceResize);
26 |
27 | this.updateListExpandVisibility();
28 | }
29 |
30 | public $onDestroy() {
31 | angular.element(this.$window).off('resize', this.debounceResize);
32 | }
33 |
34 | public hasSaasOfferings(): boolean {
35 | return !_.isEmpty(this.ctrl.saasOfferings);
36 | }
37 |
38 | public $onChanges(onChangesObj: angular.IOnChangesObject) {
39 | if ((onChangesObj.saasOfferings && !onChangesObj.saasOfferings.isFirstChange()) ) {
40 | this.ctrl.saasOfferings = onChangesObj.saasOfferings.currentValue;
41 | this.updateListExpandVisibility();
42 | }
43 | }
44 |
45 | public toggleListExpand() {
46 | this.ctrl.sassListExpanded = !this.ctrl.sassListExpanded;
47 | }
48 |
49 | private updateListExpandVisibility() {
50 | var windowWidth: number = this.$window.innerWidth;
51 | var offeringCount: number = _.size(this.ctrl.saasOfferings);
52 | this.ctrl.itemsOverflow = (offeringCount > 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 |
5 |
10 | {{plan.spec.externalMetadata.displayName || plan.spec.externalName}}
11 |
12 |
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: '',
9 | onOpen: '',
10 | availableProjects: '',
11 | showDivider: '',
12 | skipCanAddValidation: '',
13 | hideCreateProject: '',
14 | hideLabel: '',
15 | isRequired: ''
16 | },
17 | controller: SelectProjectController,
18 | template: require('./selectProject.html')
19 | };
20 |
--------------------------------------------------------------------------------
/src/components/services-view/services-view.component.ts:
--------------------------------------------------------------------------------
1 | import {ServicesViewController} from './services-view.controller';
2 |
3 | export const servicesView: angular.IComponentOptions = {
4 | bindings: {
5 | sectionTitle: '@?',
6 | baseProjectUrl: '@',
7 | catalogItems: '<',
8 | servicePlans: '<',
9 | keywordFilter: '',
10 | onDeployImageSelected: '<',
11 | onFromFileSelected: '<',
12 | onCreateFromProject: '<',
13 | },
14 | controller: ServicesViewController,
15 | template: require('./services-view.html')
16 | };
17 |
--------------------------------------------------------------------------------
/src/components/update-service/update-service-configure.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
12 | {{$ctrl.error}}
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/components/update-service/update-service-plans.html:
--------------------------------------------------------------------------------
1 |
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 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/decorators/bootstrap/array.html:
--------------------------------------------------------------------------------
1 |
33 |
--------------------------------------------------------------------------------
/src/decorators/bootstrap/checkbox.html:
--------------------------------------------------------------------------------
1 |
17 |
--------------------------------------------------------------------------------
/src/decorators/bootstrap/checkboxes.html:
--------------------------------------------------------------------------------
1 |
25 |
--------------------------------------------------------------------------------
/src/decorators/bootstrap/default.html:
--------------------------------------------------------------------------------
1 |
73 |
--------------------------------------------------------------------------------
/src/decorators/bootstrap/select.html:
--------------------------------------------------------------------------------
1 |
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 | ' ' +
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 |
--------------------------------------------------------------------------------