diff --git a/dashboard/README.md b/dashboard/README.md index 7af471d23cd..ae6c01c93ea 100644 --- a/dashboard/README.md +++ b/dashboard/README.md @@ -1,10 +1,13 @@ ## About Eclipse Che + Eclipse Che is a next generation Eclipse IDE and open source alternative to IntelliJ. This repository is licensed under the Eclipse Public License 2.0. Visit [Eclipse Che's Web site](https://eclipse.org/che) for feature information or the main [Che assembly repository](https://github.com/codenvy/che) for a description of all participating repositories. Che Dashboard + ============== ## Requirements + - Docker ## Quick start @@ -22,54 +25,61 @@ $ mvn -Pnative clean install ``` Required tools for native build: + - Python `v2.7.x`(`v3.x.x`currently not supported) - Node.js `v8.x.x` or `v9.x.x` - yarn `v1.13.0` or higher - gulp -Installation instructions for Node.js can be found on the following [link](https://docs.npmjs.com/getting-started/installing-node). +Installation instructions for Node.js can be found on the following [link](https://docs.npmjs.com/getting-started/installing-node). ## Running + In order to run the project, the serve command is used + ```sh $ gulp serve ``` + It will launch the server and then the project can be tested on http://localhost:3000 By default it will use http://localhost:8080 as a remote server, so make sure that Che is running locally. More details about how to do that can be found on the following [link](https://github.com/eclipse/che/wiki/Development-Workflow#build-and-run---tomcat) The argument `--server ` may allow to use another server. (url is for example http://my-server.com) - ```sh $ gulp serve:dist ``` + This command will provide the 'minified/optimised' version of the application This is a good check for testing if the final rendering is OK - ## Tests + The application contains both unit tests and e2e tests (end-to-end) Unit tests + ```sh $ gulp test ``` e2e tests + ```sh $ gulp protractor ``` Both tests + ```sh $ gulp alltests ``` Note: before pushing contribution, these tests should always work -#Architecture design +# Architecture design ## Ecmascript 2015/es6 @@ -80,54 +90,60 @@ So application is written with the new language but the resulting build is Javas Among new features, Class, arrow functions, etc ## Styling/css + [Stylus](https://github.com/LearnBoost/stylus) is used for produced the final CSS. Variables, simple syntax, etc is then provided - ## Code convention ### indent + There is a .editorconfig file that is indicating the current identation which is + ``` indent_style = space indent_size = 2 ``` ### syntax + The syntax is checked by jshint (through .jshintrc file) Also when launching gulp serve command, there is a report on each file that may have invalid data -For example use single quote 'hello', no "double quote", use === and !=== and not == or != - +For example use single quote `'hello'`, no `"double quote"`, use `===` and `!===` and not `==` or `!=` ### name of the files -Controllers are in files named .controller.js -Directives: .directive.js +Controllers are in files named `[name].controller.ts` + +Directives: `[name].directive.ts` -Templates: .html +Templates: `[name].html` -Factories: .factory.js +Factories: `[name].factory.ts` -Unit test: .spec.js (for my-example.factory.js will be named my-example.spec.js) +Unit test: `[name].spec.ts` (for `my-example.factory.ts` will be named `my-example.spec.ts`) #### About e2e tests + If a 'project' page needs to be tested: -project.po.js will contain the Page Object pattern (all methods allowing to get HTML elements on the page) +`project.po.ts` will contain the Page Object pattern (all methods allowing to get HTML elements on the page) -project.spec.js will have the e2e test +`project.spec.ts` will have the e2e test -project.mock.js will provide some mock for the test +`project.mock.ts` will provide some mock for the test ## source tree + Each 'page' needs to have its own folder which include: controller, directive, template, style for the page for a 'list-projects' page, the folder tree will have + ``` list-projects - list-projects.controller.js @@ -135,15 +151,16 @@ list-projects - list-projects.styl ``` -## AngularJS recommandation -As classes are available, the controller will be designed as es6 classes. +## AngularJS recommendation -All injection required will be done through the constructor by adding also the static $inject = ['$toBeInjected']; line. +As classes are available, the controller will be designed as es6 classes. +All injection required will be done through the constructor by adding also the static `$inject = ['$toBeInjected'];` line. Also properties are bound with this. scope (so avoid to use $scope in injection as this will be more aligned with AngularJS 2.0 where scope will disappear) example + ```js /** * Defines a controller @@ -173,8 +190,8 @@ export default CheToggleCtrl; So, no need to add specific arrays for injection. - By using the this syntax, the controllerAs needs to be used when adding the router view + ```js .when('/myURL', { templateUrl: 'mytemplate.html', @@ -183,11 +200,12 @@ By using the this syntax, the controllerAs needs to be used when adding the rout }) ``` -And then, when there is a need to interact with code of a controller, the controllerAs value is used. +And then, when there is a need to interact with code of a controller, the `controllerAs` value is used. ```html
Selected book is {{myCtrl.selectedBook}}
``` + Note that as if scope was using, the values are always prefixed ## Directives @@ -196,23 +214,38 @@ The whole idea is to be able to reuse some 'widgets' when designing HTML pages So instead that each page make the design/css for all buttons, inputs, it should be defined in some widget components. -The components are located in the src/components/widget folder +The components are located in the `src/components/widget` folder -It includes toggle buttons, selecter, etc. +It includes toggle buttons, selector, etc. A demo page is also provided to browse them: localhost:5000/#/demo-components - ## API of Che Each call to the Che API shouldn't be made directly from the controller of the page. -For that, it has to use Che API fatories which are handling the job (with promises operations) +For that, it has to use Che API factories which are handling the job (with promises operations) By injecting 'cheAPI' inside a controller, all operations can be called. -for example cheAPI.getWorkspace().getWorkspaces() for getting the array of the current workspaces of the user +for example `cheAPI.getWorkspace().getWorkspaces()` for getting the array of the current workspaces of the user Mocks are also provided for the Che API, allowing to emulate a real backend for unit tests or e2e tests +## Configurability +The `configuration.menu.disabled` field in [product.json](/src/assets/branding/product.json) allows users to list there menu entries they want to hide in left navigation bar. Along with that corresponding routes also will be disabled. +Available values are `'administration'`, `'factories'`, `'getstarted'`, `'organizations'`, `'stacks'`. + +```json +// product.json +{ + // disables the `Organizations` menu item and prevents opening views + // with list of available organizations or an organization details + "configuration": { + "menu": { + "disabled": ["organizations"] + } + } +} +``` diff --git a/dashboard/src/app/administration/administration-config.service.ts b/dashboard/src/app/administration/administration-config.service.ts new file mode 100644 index 00000000000..579d4e7ad68 --- /dev/null +++ b/dashboard/src/app/administration/administration-config.service.ts @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2015-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +import { CheDashboardConfigurationService } from '../../components/branding/che-dashboard-configuration.service'; + +export class AdministrationConfigService { + + static $inject = [ + 'cheDashboardConfigurationService' + ]; + + private cheDashboardConfigurationService: CheDashboardConfigurationService; + + constructor( + cheDashboardConfigurationService: CheDashboardConfigurationService, + ) { + this.cheDashboardConfigurationService = cheDashboardConfigurationService; + } + + allowAdministrationRoutes(): ng.IPromise { + return this.cheDashboardConfigurationService.allowRoutes('administration'); + } + +} diff --git a/dashboard/src/app/administration/administration-config.ts b/dashboard/src/app/administration/administration-config.ts index a79f9465f9f..18f60394a94 100644 --- a/dashboard/src/app/administration/administration-config.ts +++ b/dashboard/src/app/administration/administration-config.ts @@ -11,11 +11,10 @@ */ 'use strict'; - import {DockerRegistryList} from './docker-registry/docker-registry-list/docker-registry-list.directive'; import {DockerRegistryListController} from './docker-registry/docker-registry-list/docker-registry-list.controller'; import {EditRegistryController} from './docker-registry/docker-registry-list/edit-registry/edit-registry.controller'; - +import { AdministrationConfigService } from './administration-config.service'; export class AdministrationConfig { @@ -25,11 +24,20 @@ export class AdministrationConfig { register.controller('EditRegistryController', EditRegistryController); + register.service('administrationConfigService', AdministrationConfigService); + // config routes register.app.config(['$routeProvider', ($routeProvider: che.route.IRouteProvider) => { $routeProvider.accessWhen('/administration', { title: 'Administration', - templateUrl: 'app/administration/administration.html' + templateUrl: 'app/administration/administration.html', + resolve: { + init: [ + 'administrationConfigService', + (svc: AdministrationConfigService) => { + return svc.allowAdministrationRoutes(); + }] + } }); }]); diff --git a/dashboard/src/app/dashboard/dashboard-config.ts b/dashboard/src/app/dashboard/dashboard-config.ts index 200c9d98ae3..501d2e159ba 100644 --- a/dashboard/src/app/dashboard/dashboard-config.ts +++ b/dashboard/src/app/dashboard/dashboard-config.ts @@ -38,7 +38,7 @@ export class DashboardConfig { const defer = $q.defer(); cheWorkspace.fetchWorkspaces().then(() => { if (cheWorkspace.getWorkspaces().length === 0) { - $window.open(MENU_ITEM.getStarted, '_self'); + $window.open(MENU_ITEM.getstarted, '_self'); defer.reject(); } else { defer.resolve(); diff --git a/dashboard/src/app/factories/factories-config.ts b/dashboard/src/app/factories/factories-config.ts index f7904734e3b..c88a8471596 100644 --- a/dashboard/src/app/factories/factories-config.ts +++ b/dashboard/src/app/factories/factories-config.ts @@ -11,7 +11,6 @@ */ 'use strict'; - import {FactoryDetailsConfig} from './factory-details/factory-details-config'; import {CreateFactoryConfig} from './create-factory/create-factory-config'; import {LastFactoriesConfig} from './last-factories/last-factories-config'; @@ -20,6 +19,7 @@ import {FactoryItemController} from './list-factories/factory-item/factory-item. import {CheFactoryItem} from './list-factories/factory-item/factory-item.directive'; import {LoadFactoryController} from './load-factory/load-factory.controller'; import {LoadFactoryService} from './load-factory/load-factory.service'; +import { FactoryConfigService } from './factory-config.service'; export class FactoryConfig { @@ -32,27 +32,44 @@ export class FactoryConfig { register.controller('LoadFactoryController', LoadFactoryController); register.service('loadFactoryService', LoadFactoryService); + register.service('factoryConfigService', FactoryConfigService); + // config routes register.app.config(['$routeProvider', ($routeProvider: che.route.IRouteProvider) => { - $routeProvider.accessWhen('/factories', { - title: 'Factories', - templateUrl: 'app/factories/list-factories/list-factories.html', - controller: 'ListFactoriesController', - controllerAs: 'listFactoriesCtrl' - }) + $routeProvider + .accessWhen('/factories', { + title: 'Factories', + templateUrl: 'app/factories/list-factories/list-factories.html', + controller: 'ListFactoriesController', + controllerAs: 'listFactoriesCtrl', + resolve: { + initData: ['factoryConfigService', (svc: FactoryConfigService) => { + return svc.allowFactoriesRoutes(); + }] + } + }) .accessWhen('/load-factory', { title: 'Load Factory', templateUrl: 'app/factories/load-factory/load-factory.html', controller: 'LoadFactoryController', - controllerAs: 'loadFactoryController' + controllerAs: 'loadFactoryController', + resolve: { + initData: ['factoryConfigService', (svc: FactoryConfigService) => { + return svc.allowFactoriesRoutes(); + }] + } }) - .accessWhen('/load-factory/:id', { - title: 'Load Factory', - templateUrl: 'app/factories/load-factory/load-factory.html', - controller: 'LoadFactoryController', - controllerAs: 'loadFactoryController' - }); - + .accessWhen('/load-factory/:id', { + title: 'Load Factory', + templateUrl: 'app/factories/load-factory/load-factory.html', + controller: 'LoadFactoryController', + controllerAs: 'loadFactoryController', + resolve: { + initData: ['factoryConfigService', (svc: FactoryConfigService) => { + return svc.allowFactoriesRoutes(); + }] + } + }); }]); // config files @@ -63,4 +80,3 @@ export class FactoryConfig { /* tslint:enable */ } } - diff --git a/dashboard/src/app/factories/factory-config.service.ts b/dashboard/src/app/factories/factory-config.service.ts new file mode 100644 index 00000000000..52d51214839 --- /dev/null +++ b/dashboard/src/app/factories/factory-config.service.ts @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2015-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +import { CheDashboardConfigurationService } from '../../components/branding/che-dashboard-configuration.service'; + +export class FactoryConfigService { + + static $inject = [ + 'cheDashboardConfigurationService' + ]; + + private cheDashboardConfigurationService: CheDashboardConfigurationService; + + constructor( + cheDashboardConfigurationService: CheDashboardConfigurationService, + ) { + this.cheDashboardConfigurationService = cheDashboardConfigurationService; + } + + allowFactoriesRoutes(): ng.IPromise { + return this.cheDashboardConfigurationService.allowRoutes('factories'); + } + +} diff --git a/dashboard/src/app/factories/factory-details/factory-details-config.ts b/dashboard/src/app/factories/factory-details/factory-details-config.ts index 19cbe4e453a..c9ccfc82881 100644 --- a/dashboard/src/app/factories/factory-details/factory-details-config.ts +++ b/dashboard/src/app/factories/factory-details/factory-details-config.ts @@ -13,7 +13,7 @@ import {FactoryDetailsController} from '../factory-details/factory-details.controller'; import {InformationTabConfig} from './information-tab/information-tab-config'; - +import { FactoryConfigService } from '../factory-config.service'; export class FactoryDetailsConfig { @@ -26,7 +26,12 @@ export class FactoryDetailsConfig { title: 'Factory', templateUrl: 'app/factories/factory-details/factory-details.html', controller: 'FactoryDetailsController', - controllerAs: 'factoryDetailsController' + controllerAs: 'factoryDetailsController', + resolve: { + initData: ['factoryConfigService', (svc: FactoryConfigService) => { + return svc.allowFactoriesRoutes(); + }] + } }; $routeProvider.accessWhen('/factory/:id', locationProvider) diff --git a/dashboard/src/app/get-started/get-started-config.service.ts b/dashboard/src/app/get-started/get-started-config.service.ts new file mode 100644 index 00000000000..e48e0df6bf1 --- /dev/null +++ b/dashboard/src/app/get-started/get-started-config.service.ts @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2015-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ + +import { CheDashboardConfigurationService } from '../../components/branding/che-dashboard-configuration.service'; + +export class GetStartedConfigService { + + static $inject = [ + 'cheDashboardConfigurationService' + ]; + + private cheDashboardConfigurationService: CheDashboardConfigurationService; + + constructor( + cheDashboardConfigurationService: CheDashboardConfigurationService, + ) { + this.cheDashboardConfigurationService = cheDashboardConfigurationService; + } + + allowGetStartedRoutes(): ng.IPromise { + return this.cheDashboardConfigurationService.allowRoutes('getstarted'); + } + +} diff --git a/dashboard/src/app/get-started/get-started-config.ts b/dashboard/src/app/get-started/get-started-config.ts index 66c6673f8b8..51bfa40ca2e 100644 --- a/dashboard/src/app/get-started/get-started-config.ts +++ b/dashboard/src/app/get-started/get-started-config.ts @@ -13,9 +13,10 @@ import {TemplateListController} from './template-list/template-list.controller'; import {Template} from './template/template.directive'; +import { CheDashboardConfigurationService } from '../../components/branding/che-dashboard-configuration.service'; +import { GetStartedConfigService } from './get-started-config.service'; /** - * @ngdoc controller * @name getStarted:GetStartedConfig * @description This class is used for configuring all get started devfiles. * @author Oleksii Orel @@ -27,13 +28,20 @@ export class GetStartedConfig { register.controller('TemplateListController', TemplateListController); + register.service('getStartedConfigService', GetStartedConfigService); + // config routes register.app.config(['$routeProvider', ($routeProvider: any) => { $routeProvider.accessWhen('/getstarted', { title: 'Get Started', templateUrl: 'app/get-started/template-list/template-list.html', controller: 'TemplateListController', - controllerAs: 'templateListController' + controllerAs: 'templateListController', + resolve: { + initData: ['getStartedConfigService', (svc: GetStartedConfigService) => { + return svc.allowGetStartedRoutes(); + }] + } }); }]); } diff --git a/dashboard/src/app/navbar/navbar.controller.ts b/dashboard/src/app/navbar/navbar.controller.ts index e063dadaeb6..095a7ca5216 100644 --- a/dashboard/src/app/navbar/navbar.controller.ts +++ b/dashboard/src/app/navbar/navbar.controller.ts @@ -13,30 +13,36 @@ import {CheAPI} from '../../components/api/che-api.factory'; import {CheKeycloak} from '../../components/api/che-keycloak.factory'; import {CheService} from '../../components/api/che-service.factory'; +import { CheDashboardConfigurationService } from '../../components/branding/che-dashboard-configuration.service'; -export const MENU_ITEM = { - dashboard: '#/', - getStarted: '#/getstarted', - workspaces: '#/workspaces', - stacks: '#/stacks', - factories: '#/factories', +type ConfigurableMenu = { [key in che.ConfigurableMenuItem ]: string }; + +const CONFIGURABLE_MENU: ConfigurableMenu = { administration: '#/administration', - usermanagement: '#/admin/usermanagement', + factories: '#/factories', + getstarted: '#/getstarted', organizations: '#/organizations', - account: '#/account' + stacks: '#/stacks', }; +export const MENU_ITEM = angular.extend({ + account: '#/account', + dashboard: '#/', + usermanagement: '#/admin/usermanagement', + workspaces: '#/workspaces', +}, CONFIGURABLE_MENU); + export class CheNavBarController { - static $inject = ['$mdSidenav', - '$scope', + static $inject = [ '$location', - '$route', + '$scope', 'cheAPI', - '$window', - 'chePermissions', + 'cheDashboardConfigurationService', 'cheKeycloak', - 'cheService']; + 'chePermissions', + 'cheService', + ]; menuItemUrl = MENU_ITEM; @@ -55,19 +61,18 @@ export class CheNavBarController { } ]; - private $mdSidenav: ng.material.ISidenavService; - private $scope: ng.IScope; - private $window: ng.IWindowService; private $location: ng.ILocationService; - private $route: ng.route.IRouteService; + private $scope: ng.IScope; private cheAPI: CheAPI; - private profile: che.IProfile; + private cheDashboardConfigurationService: CheDashboardConfigurationService; + private cheKeycloak: CheKeycloak; private chePermissions: che.api.IChePermissions; + private cheService: CheService; + + private profile: che.IProfile; private userServices: che.IUserServices; private hasPersonalAccount: boolean; private organizations: Array; - private cheKeycloak: CheKeycloak; - private cheService: CheService; private isPermissionServiceAvailable: boolean; private isKeycloackPresent: boolean; @@ -77,23 +82,21 @@ export class CheNavBarController { /** * Default constructor */ - constructor($mdSidenav: ng.material.ISidenavService, - $scope: ng.IScope, - $location: ng.ILocationService, - $route: ng.route.IRouteService, - cheAPI: CheAPI, - $window: ng.IWindowService, - chePermissions: che.api.IChePermissions, - cheKeycloak: CheKeycloak, - cheService: CheService) { - this.$mdSidenav = $mdSidenav; - this.$scope = $scope; + constructor( + $location: ng.ILocationService, + $scope: ng.IScope, + cheAPI: CheAPI, + cheDashboardConfigurationService: CheDashboardConfigurationService, + cheKeycloak: CheKeycloak, + chePermissions: che.api.IChePermissions, + cheService: CheService, + ) { this.$location = $location; - this.$route = $route; + this.$scope = $scope; this.cheAPI = cheAPI; - this.$window = $window; - this.chePermissions = chePermissions; + this.cheDashboardConfigurationService = cheDashboardConfigurationService; this.cheKeycloak = cheKeycloak; + this.chePermissions = chePermissions; this.cheService = cheService; const handler = (workspaces: Array) => { @@ -219,6 +222,10 @@ export class CheNavBarController { return rootOrganizations.length; } + showMenuItem(menuItem: che.ConfigurableMenuItem | string): boolean { + return this.cheDashboardConfigurationService.allowedMenuItem(menuItem); + } + /** * Opens user profile in new browser page. */ @@ -232,4 +239,5 @@ export class CheNavBarController { private logout(): void { this.cheKeycloak.logout(); } + } diff --git a/dashboard/src/app/navbar/navbar.html b/dashboard/src/app/navbar/navbar.html index 8840769dafb..13ec4020189 100644 --- a/dashboard/src/app/navbar/navbar.html +++ b/dashboard/src/app/navbar/navbar.html @@ -29,7 +29,9 @@
- - + + - + + - + + - + + - + + + + ng-if="navbarController.showMenuItem('organizations') && + navbarController.isPermissionServiceAvailable && + !navbarController.userServices.hasInstallationManagerService && + !navbarController.hasPersonalAccount">