From 4e7f9fae9d82be36497a4e287aabee6d01878653 Mon Sep 17 00:00:00 2001 From: Oleksii Kurinnyi Date: Fri, 16 Sep 2016 12:33:18 +0300 Subject: [PATCH] CHE-1771: add Runtime page to Workspace details. Signed-off-by: Oleksii Kurinnyi --- dashboard/bower.json | 6 +- .../select-stack/recipe/workspace-recipe.styl | 28 +- .../environments/environments.controller.js | 136 +++++++ .../environments/environments.directive.js | 49 +++ .../environments/environments.html | 74 ++++ .../environments/environments.styl | 17 + .../add-variable-dialog.controller.js | 56 +++ .../add-variable-dialog.html | 42 ++ .../add-variable-dialog.styl | 14 + .../edit-variable-dialog.controller.js | 49 +++ .../edit-variable-dialog.html | 39 ++ .../edit-variable-dialog.styl | 14 + .../list-env-variables.controller.js | 181 +++++++++ .../list-env-variables.directive.js | 47 +++ .../list-env-variables.html | 100 +++++ .../list-env-variables.styl | 26 ++ .../add-port-dialog.controller.js | 79 ++++ .../add-port-dialog/add-port-dialog.html | 35 ++ .../add-port-dialog/add-port-dialog.styl | 14 + .../edit-port-dialog.controller.js | 69 ++++ .../edit-port-dialog/edit-port-dialog.html | 36 ++ .../edit-port-dialog/edit-port-dialog.styl | 14 + .../list-ports/list-ports.controller.js | 197 ++++++++++ .../list-ports/list-ports.directive.js | 47 +++ .../environments/list-ports/list-ports.html | 100 +++++ .../environments/list-ports/list-ports.styl | 25 ++ .../edit-machine-name-dialog.controller.js | 53 +++ .../edit-machine-name-dialog.html | 32 ++ .../edit-machine-name-dialog.styl | 14 + .../machine-config.controller.js | 209 ++++++++++ .../machine-config.directive.js | 52 +++ .../machine-config/machine-config.html | 96 +++++ .../machine-config/machine-config.styl | 60 +++ .../workspace-details.controller.js | 101 ++--- .../workspace-details/workspace-details.html | 31 +- .../src/app/workspaces/workspaces-config.js | 34 +- .../src/components/api/che-api-config.js | 2 + .../components/api/che-workspace.factory.js | 9 +- .../che-environment-registry.factory.js | 52 +++ .../compose-environment-manager.js | 359 ++++++++++++++++++ .../compose-environment-manager.spec.js | 167 ++++++++ .../docker-file-environment-manager.js | 216 +++++++++++ .../docker-file-environment-manager.spec.js | 155 ++++++++ .../api/environment/docker-file-parser.js | 159 ++++++++ .../environment/docker-file-parser.spec.js | 110 ++++++ .../docker-image-environment-manager.js | 75 ++++ .../docker-image-environment-manager.spec.js | 76 ++++ .../api/environment/environment-manager.js | 134 +++++++ .../validator/custom-validator.directive.js | 41 ++ .../validator/custom-validator.spec.js | 77 ++++ .../components/validator/validator-config.js | 2 + .../widget/input/che-input.directive.js | 6 +- .../components/widget/input/che-input.styl | 11 +- .../widget/input/che-textarea.directive.js | 79 ++++ .../src/components/widget/widget-config.js | 2 + 55 files changed, 3801 insertions(+), 107 deletions(-) create mode 100644 dashboard/src/app/workspaces/workspace-details/environments/environments.controller.js create mode 100644 dashboard/src/app/workspaces/workspace-details/environments/environments.directive.js create mode 100644 dashboard/src/app/workspaces/workspace-details/environments/environments.html create mode 100644 dashboard/src/app/workspaces/workspace-details/environments/environments.styl create mode 100644 dashboard/src/app/workspaces/workspace-details/environments/list-env-variables/add-variable-dialog/add-variable-dialog.controller.js create mode 100644 dashboard/src/app/workspaces/workspace-details/environments/list-env-variables/add-variable-dialog/add-variable-dialog.html create mode 100644 dashboard/src/app/workspaces/workspace-details/environments/list-env-variables/add-variable-dialog/add-variable-dialog.styl create mode 100644 dashboard/src/app/workspaces/workspace-details/environments/list-env-variables/edit-variable-dialog/edit-variable-dialog.controller.js create mode 100644 dashboard/src/app/workspaces/workspace-details/environments/list-env-variables/edit-variable-dialog/edit-variable-dialog.html create mode 100644 dashboard/src/app/workspaces/workspace-details/environments/list-env-variables/edit-variable-dialog/edit-variable-dialog.styl create mode 100644 dashboard/src/app/workspaces/workspace-details/environments/list-env-variables/list-env-variables.controller.js create mode 100644 dashboard/src/app/workspaces/workspace-details/environments/list-env-variables/list-env-variables.directive.js create mode 100644 dashboard/src/app/workspaces/workspace-details/environments/list-env-variables/list-env-variables.html create mode 100644 dashboard/src/app/workspaces/workspace-details/environments/list-env-variables/list-env-variables.styl create mode 100644 dashboard/src/app/workspaces/workspace-details/environments/list-ports/add-port-dialog/add-port-dialog.controller.js create mode 100644 dashboard/src/app/workspaces/workspace-details/environments/list-ports/add-port-dialog/add-port-dialog.html create mode 100644 dashboard/src/app/workspaces/workspace-details/environments/list-ports/add-port-dialog/add-port-dialog.styl create mode 100644 dashboard/src/app/workspaces/workspace-details/environments/list-ports/edit-port-dialog/edit-port-dialog.controller.js create mode 100644 dashboard/src/app/workspaces/workspace-details/environments/list-ports/edit-port-dialog/edit-port-dialog.html create mode 100644 dashboard/src/app/workspaces/workspace-details/environments/list-ports/edit-port-dialog/edit-port-dialog.styl create mode 100644 dashboard/src/app/workspaces/workspace-details/environments/list-ports/list-ports.controller.js create mode 100644 dashboard/src/app/workspaces/workspace-details/environments/list-ports/list-ports.directive.js create mode 100644 dashboard/src/app/workspaces/workspace-details/environments/list-ports/list-ports.html create mode 100644 dashboard/src/app/workspaces/workspace-details/environments/list-ports/list-ports.styl create mode 100644 dashboard/src/app/workspaces/workspace-details/environments/machine-config/edit-machine-name-dialog/edit-machine-name-dialog.controller.js create mode 100644 dashboard/src/app/workspaces/workspace-details/environments/machine-config/edit-machine-name-dialog/edit-machine-name-dialog.html create mode 100644 dashboard/src/app/workspaces/workspace-details/environments/machine-config/edit-machine-name-dialog/edit-machine-name-dialog.styl create mode 100644 dashboard/src/app/workspaces/workspace-details/environments/machine-config/machine-config.controller.js create mode 100644 dashboard/src/app/workspaces/workspace-details/environments/machine-config/machine-config.directive.js create mode 100644 dashboard/src/app/workspaces/workspace-details/environments/machine-config/machine-config.html create mode 100644 dashboard/src/app/workspaces/workspace-details/environments/machine-config/machine-config.styl create mode 100644 dashboard/src/components/api/environment/che-environment-registry.factory.js create mode 100644 dashboard/src/components/api/environment/compose-environment-manager.js create mode 100644 dashboard/src/components/api/environment/compose-environment-manager.spec.js create mode 100644 dashboard/src/components/api/environment/docker-file-environment-manager.js create mode 100644 dashboard/src/components/api/environment/docker-file-environment-manager.spec.js create mode 100644 dashboard/src/components/api/environment/docker-file-parser.js create mode 100644 dashboard/src/components/api/environment/docker-file-parser.spec.js create mode 100644 dashboard/src/components/api/environment/docker-image-environment-manager.js create mode 100644 dashboard/src/components/api/environment/docker-image-environment-manager.spec.js create mode 100644 dashboard/src/components/api/environment/environment-manager.js create mode 100644 dashboard/src/components/validator/custom-validator.directive.js create mode 100644 dashboard/src/components/validator/custom-validator.spec.js create mode 100644 dashboard/src/components/widget/input/che-textarea.directive.js diff --git a/dashboard/bower.json b/dashboard/bower.json index 087ebb12b1a..401c1dd3483 100644 --- a/dashboard/bower.json +++ b/dashboard/bower.json @@ -33,10 +33,10 @@ "zeroclipboard": "2.2.0", "angular-uuid4": "0.3.0", "angular-file-upload": "2.0.0", - "jquery": "2.1.3" - }, - "devDependencies": { + "jquery": "2.1.3", + "js-yaml": "3.6.1" }, + "devDependencies": {}, "resolutions": { "angular": "~1.4.8" }, diff --git a/dashboard/src/app/workspaces/create-workspace/select-stack/recipe/workspace-recipe.styl b/dashboard/src/app/workspaces/create-workspace/select-stack/recipe/workspace-recipe.styl index af8960b306d..ca2c0bafa53 100644 --- a/dashboard/src/app/workspaces/create-workspace/select-stack/recipe/workspace-recipe.styl +++ b/dashboard/src/app/workspaces/create-workspace/select-stack/recipe/workspace-recipe.styl @@ -1,24 +1,24 @@ .recipe-widget margin-left 10px -.recipe-widget md-radio-button - outline 0 + md-radio-button + outline 0 -.recipe-editor - min-width 500px - max-width 1000px - margin-top 20px + .recipe-editor + min-width 500px + max-width 1000px + margin-top 20px -.recipe-editor .CodeMirror - border 1px solid $list-separator-color - min-height 500px + .recipe-editor .CodeMirror + border 1px solid $list-separator-color + min-height 500px -.recipe-docs-link - margin-top 5px + .recipe-docs-link + margin-top 5px -.recipe-widget .recipe-url - width calc(100% - 10px) - margin-bottom 20px + .recipe-url + width calc(100% - 10px) + margin-bottom 20px .recipe-format .che-toggle-button-enabled color $primary-color !important diff --git a/dashboard/src/app/workspaces/workspace-details/environments/environments.controller.js b/dashboard/src/app/workspaces/workspace-details/environments/environments.controller.js new file mode 100644 index 00000000000..ee17f225548 --- /dev/null +++ b/dashboard/src/app/workspaces/workspace-details/environments/environments.controller.js @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2015-2016 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + */ +'use strict'; + +/** + * @ngdoc controller + * @name workspace.details.controller:WorkspaceEnvironmentsController + * @description This class is handling the controller for details of workspace : section environments + * @author Oleksii Kurinnyi + */ +export class WorkspaceEnvironmentsController { + + /** + * Default constructor that is using resource injection + * @ngInject for Dependency injection + */ + constructor($timeout, cheEnvironmentRegistry) { + this.cheEnvironmentRegistry = cheEnvironmentRegistry; + + this.editorOptions = { + lineWrapping: true, + lineNumbers: false, + readOnly: true, + gutters: [], + onLoad: (editor) => { + $timeout(() => { + editor.refresh(); + }, 1000); + } + }; + + this.machinesViewStatus = {}; + + this.init(); + } + + /** + * Sets initial values + */ + init() { + this.newEnvironmentName = this.environmentName; + this.environment = this.workspaceConfig.environments[this.environmentName]; + + this.recipeType = this.environment.recipe.type; + this.environmentManager = this.cheEnvironmentRegistry.getEnvironmentManager(this.recipeType); + + this.editorOptions.mode = this.environmentManager.editorMode; + + this.machines = this.environmentManager.getMachines(this.environment); + } + + /** + * Returns true if environment name is unique + * + * @param name {string} environment name to validate + * @returns {boolean} + */ + isUnique(name) { + return name === this.environmentName || !this.workspaceConfig.environments[name]; + } + + /** + * Updates name of environment + * @param isFormValid {boolean} + */ + updateEnvironmentName(isFormValid) { + if (!isFormValid || this.newEnvironmentName === this.environmentName) { + return; + } + + this.workspaceConfig.environments[this.newEnvironmentName] = this.environment; + delete this.workspaceConfig.environments[this.environmentName]; + + if (this.workspaceConfig.defaultEnv === this.environmentName) { + this.workspaceConfig.defaultEnv = this.newEnvironmentName; + } + + this.doUpdateEnvironments(); + } + + /** + * Callback which is called in order to update environment config + * @returns {Promise} + */ + updateEnvironmentConfig() { + let newEnvironment = this.environmentManager.getEnvironment(this.environment, this.machines); + this.workspaceConfig.environments[this.newEnvironmentName] = newEnvironment; + return this.doUpdateEnvironments().then(() => { + this.init(); + }); + } + + /** + * Callback which is called in order to rename specified machine + * @param oldName + * @param newName + * @returns {*} + */ + updateMachineName(oldName, newName) { + let newEnvironment = this.environmentManager.renameMachine(this.environment, oldName, newName); + this.workspaceConfig.environments[this.newEnvironmentName] = newEnvironment; + return this.doUpdateEnvironments().then(() => { + this.init(); + }) + } + + /** + * Callback which is called in order to delete specified machine + * @param name + * @returns {*} + */ + deleteMachine(name) { + let newEnvironment = this.environmentManager.deleteMachine(this.environment, name); + this.workspaceConfig.environments[this.newEnvironmentName] = newEnvironment; + return this.doUpdateEnvironments().then(() => { + this.init(); + }) + } + + /** + * Calls parent controller's callback to update environment + * @returns {IPromise|*|Promise.} + */ + doUpdateEnvironments() { + return this.environmentOnChange(); + } + +} diff --git a/dashboard/src/app/workspaces/workspace-details/environments/environments.directive.js b/dashboard/src/app/workspaces/workspace-details/environments/environments.directive.js new file mode 100644 index 00000000000..0199eec9480 --- /dev/null +++ b/dashboard/src/app/workspaces/workspace-details/environments/environments.directive.js @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2015-2016 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + */ +'use strict'; + +/** + * @ngdoc directive + * @name workspaces.details.directive:workspaceEnvironments + * @restrict E + * @element + * + * @description + * ` for displaying workspace environments. + * + * @usage + * + * + * @author Oleksii Kurinnyi + */ +export class WorkspaceEnvironments { + + /** + * Default constructor that is using resource + * @ngInject for Dependency injection + */ + constructor () { + this.restrict = 'E'; + this.templateUrl = 'app/workspaces/workspace-details/environments/environments.html'; + + this.controller = 'WorkspaceEnvironmentsController'; + this.controllerAs = 'workspaceEnvironmentsController'; + this.bindToController = true; + + this.scope = { + environmentName: '=', + environmentViewStatus: '=', + workspaceConfig: '=', + environmentOnChange: '&' + } + } +} + diff --git a/dashboard/src/app/workspaces/workspace-details/environments/environments.html b/dashboard/src/app/workspaces/workspace-details/environments/environments.html new file mode 100644 index 00000000000..31a98f1f54b --- /dev/null +++ b/dashboard/src/app/workspaces/workspace-details/environments/environments.html @@ -0,0 +1,74 @@ + +
+ + + + +
+ +
A name is required.
+
The name should not contain special characters like space, dollar, etc. +
+
The name has to be less than 128 characters long.
+
The name has to be less than 128 characters long.
+
This environment name is already used.
+
+
+
+
+ + + + + + + + +
+
+ +
+
+
+ + + + +
diff --git a/dashboard/src/app/workspaces/workspace-details/environments/environments.styl b/dashboard/src/app/workspaces/workspace-details/environments/environments.styl new file mode 100644 index 00000000000..dcdaa6879cd --- /dev/null +++ b/dashboard/src/app/workspaces/workspace-details/environments/environments.styl @@ -0,0 +1,17 @@ +.workspace-environments + padding 0 14px 14px + + .workspace-environments-input + margin -6px 0 + + .recipe-editor + margin -4px 0 + min-width 500px + max-width 1000px + + .CodeMirror + height auto + border 1px solid $list-separator-color + + .recipe-location a + word-break break-all diff --git a/dashboard/src/app/workspaces/workspace-details/environments/list-env-variables/add-variable-dialog/add-variable-dialog.controller.js b/dashboard/src/app/workspaces/workspace-details/environments/list-env-variables/add-variable-dialog/add-variable-dialog.controller.js new file mode 100644 index 00000000000..3017ad349eb --- /dev/null +++ b/dashboard/src/app/workspaces/workspace-details/environments/list-env-variables/add-variable-dialog/add-variable-dialog.controller.js @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2015-2016 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + */ +'use strict'; + +/** + * @ngdoc controller + * @name list.environment.variables.controller:AddVariableDialogController + * @description This class is handling the controller for the dialog box about adding the environment variable. + * @author Oleksii Kurinnyi + */ +export class AddVariableDialogController { + + /** + * Default constructor that is using resource + * @ngInject for Dependency injection + */ + constructor($mdDialog) { + this.$mdDialog = $mdDialog; + this.updateInProgress = false; + + this.name = ''; + this.value = ''; + } + + isUnique(name) { + return !this.variables[name]; + } + + /** + * It will hide the dialog box. + */ + hide() { + this.$mdDialog.hide(); + } + + /** + * Adds new environment variable + */ + addVariable() { + this.updateInProgress = true; + + this.callbackController.updateEnvVariable(this.name, this.value).finally(() => { + this.updateInProgress = false; + this.hide(); + }); + } + +} diff --git a/dashboard/src/app/workspaces/workspace-details/environments/list-env-variables/add-variable-dialog/add-variable-dialog.html b/dashboard/src/app/workspaces/workspace-details/environments/list-env-variables/add-variable-dialog/add-variable-dialog.html new file mode 100644 index 00000000000..6d3d2c3b887 --- /dev/null +++ b/dashboard/src/app/workspaces/workspace-details/environments/list-env-variables/add-variable-dialog/add-variable-dialog.html @@ -0,0 +1,42 @@ + + + +
+ +
A name is required.
+
This environment variable name is already used.
+
+
+
+ +
+ +
A value is required.
+
+
+
+ + +
+ + +
+
+
+
diff --git a/dashboard/src/app/workspaces/workspace-details/environments/list-env-variables/add-variable-dialog/add-variable-dialog.styl b/dashboard/src/app/workspaces/workspace-details/environments/list-env-variables/add-variable-dialog/add-variable-dialog.styl new file mode 100644 index 00000000000..53e508811a3 --- /dev/null +++ b/dashboard/src/app/workspaces/workspace-details/environments/list-env-variables/add-variable-dialog/add-variable-dialog.styl @@ -0,0 +1,14 @@ +.add-variable-dialog-content + padding 0 14px + + .add-variable-dialog-input + margin -6px 0 + + .add-variable-dialog-textarea + margin -8px 0 + + button + margin 0 30px 0 0 + + input, textarea + width 300px diff --git a/dashboard/src/app/workspaces/workspace-details/environments/list-env-variables/edit-variable-dialog/edit-variable-dialog.controller.js b/dashboard/src/app/workspaces/workspace-details/environments/list-env-variables/edit-variable-dialog/edit-variable-dialog.controller.js new file mode 100644 index 00000000000..5e63b98102f --- /dev/null +++ b/dashboard/src/app/workspaces/workspace-details/environments/list-env-variables/edit-variable-dialog/edit-variable-dialog.controller.js @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2015-2016 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + */ +'use strict'; + +/** + * @ngdoc controller + * @name list.environment.variables.controller:EditVariableDialogController + * @description This class is handling the controller for the dialog box about editing the environment variable. + * @author Oleksii Kurinnyi + */ +export class EditVariableDialogController { + + /** + * Default constructor that is using resource + * @ngInject for Dependency injection + */ + constructor($mdDialog) { + this.$mdDialog = $mdDialog; + + this.updateInProgress = false; + } + + /** + * It will hide the dialog box. + */ + hide() { + this.$mdDialog.hide(); + } + + /** + * Update environment variable + */ + updateVariable() { + this.updateInProgress = true; + + this.callbackController.updateEnvVariable(this.name, this.value).finally(() => { + this.updateInProgress = false; + this.hide(); + }); + } +} diff --git a/dashboard/src/app/workspaces/workspace-details/environments/list-env-variables/edit-variable-dialog/edit-variable-dialog.html b/dashboard/src/app/workspaces/workspace-details/environments/list-env-variables/edit-variable-dialog/edit-variable-dialog.html new file mode 100644 index 00000000000..088ccd58b90 --- /dev/null +++ b/dashboard/src/app/workspaces/workspace-details/environments/list-env-variables/edit-variable-dialog/edit-variable-dialog.html @@ -0,0 +1,39 @@ + + + +
+ +
A name is required.
+
+
+
+ +
+ +
A value is required.
+
+
+
+ + +
+ + + +
+
+
+
diff --git a/dashboard/src/app/workspaces/workspace-details/environments/list-env-variables/edit-variable-dialog/edit-variable-dialog.styl b/dashboard/src/app/workspaces/workspace-details/environments/list-env-variables/edit-variable-dialog/edit-variable-dialog.styl new file mode 100644 index 00000000000..ea0a1efdfc2 --- /dev/null +++ b/dashboard/src/app/workspaces/workspace-details/environments/list-env-variables/edit-variable-dialog/edit-variable-dialog.styl @@ -0,0 +1,14 @@ +.edit-variable-dialog-content + padding 0 14px + + .edit-variable-dialog-input + margin -6px 0 + + .edit-variable-dialog-textarea + margin -8px 0 + + button + margin 0 30px 0 0 + + input, textarea + width 300px diff --git a/dashboard/src/app/workspaces/workspace-details/environments/list-env-variables/list-env-variables.controller.js b/dashboard/src/app/workspaces/workspace-details/environments/list-env-variables/list-env-variables.controller.js new file mode 100644 index 00000000000..c253838c3cb --- /dev/null +++ b/dashboard/src/app/workspaces/workspace-details/environments/list-env-variables/list-env-variables.controller.js @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2015-2016 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + */ +'use strict'; + +/** + * @ngdoc controller + * @name workspace.details.controller:ListEnvVariablesController + * @description This class is handling the controller for list of environment variables. + * @author Oleksii Kurinnyi + */ +export class ListEnvVariablesController { + + /** + * Default constructor that is using resource + * @ngInject for Dependency injection + */ + constructor($mdDialog, lodash) { + this.$mdDialog = $mdDialog; + this.lodash = lodash; + + this.isNoSelected = true; + this.isBulkChecked = false; + this.envVariablesSelectedStatus = {}; + this.envVariablesSelectedNumber = 0; + this.envVariableOrderBy = 'name'; + + this.buildVariablesList(); + } + + buildVariablesList() { + this.envVariablesList = this.lodash.map(this.envVariables, (value, name) => { + return {name: name, value: value}; + }); + } + + /** + * Update environment variable selected status + */ + updateSelectedStatus() { + this.envVariablesSelectedNumber = 0; + this.isBulkChecked = true; + this.envVariablesList.forEach((envVariable) => { + if (this.envVariablesSelectedStatus[envVariable.name]) { + this.envVariablesSelectedNumber++; + } else { + this.isBulkChecked = false; + } + }); + } + + changeEnvVariableSelection(name) { + this.envVariablesSelectedStatus[name] = !this.envVariablesSelectedStatus[name]; + this.updateSelectedStatus(); + } + + /** + * Change bulk selection value + */ + changeBulkSelection() { + if (this.isBulkChecked) { + this.deselectAllVariables(); + this.isBulkChecked = false; + return; + } + this.selectAllVariables(); + this.isBulkChecked = true; + } + + /** + * Check all environment variables in list + */ + selectAllVariables() { + this.envVariablesSelectedNumber = this.envVariablesList.length; + this.envVariablesList.forEach((envVariable) => { + this.envVariablesSelectedStatus[envVariable.name] = true; + }) + } + + /** + * Uncheck all environment variables in list + */ + deselectAllVariables() { + this.envVariablesSelectedStatus = {}; + this.envVariablesSelectedNumber = 0; + } + + updateEnvVariable(name, value) { + this.envVariables[name] = value; + + return this.envVariablesOnChange().then(() => { + this.buildVariablesList(); + }); + } + + /** + * Show dialog to add new environment variable + * @param $event + */ + showAddDialog($event) { + this.$mdDialog.show({ + targetEvent: $event, + controller: 'AddVariableDialogController', + controllerAs: 'addVariableDialogController', + bindToController: true, + clickOutsideToClose: true, + locals: { + callbackController: this, + variables: this.envVariables + }, + templateUrl: 'app/workspaces/workspace-details/environments/list-env-variables/add-variable-dialog/add-variable-dialog.html' + }); + } + + /** + * Show dialog to edit existing environment variable + * @param $event + * @param name environment variable's name + * @param value environment variable's value + */ + showEditDialog($event, name, value) { + this.$mdDialog.show({ + targetEvent: $event, + controller: 'EditVariableDialogController', + controllerAs: 'editVariableDialogController', + bindToController: true, + clickOutsideToClose: true, + locals: { + name: name, + value: value, + callbackController: this + }, + templateUrl: 'app/workspaces/workspace-details/environments/list-env-variables/edit-variable-dialog/edit-variable-dialog.html' + }); + } + + /** + * Removes selected environment variables + */ + deleteSelectedEnvVariables() { + this.showDeleteConfirmation(this.envVariablesSelectedNumber).then(() => { + this.lodash.forEach(this.envVariablesSelectedStatus, (value, name) => { + delete this.envVariables[name]; + }); + this.deselectAllVariables(); + this.isBulkChecked = false; + this.envVariablesOnChange().then(() => {this.buildVariablesList();}); + }) + } + + /** + * Show confirmation popup before environment variable to delete + * @param numberToDelete + * @returns {*} + */ + showDeleteConfirmation(numberToDelete) { + let confirmTitle = 'Would you like to delete '; + if (numberToDelete > 1) { + confirmTitle += 'these ' + numberToDelete + ' variables?'; + } + else { + confirmTitle += 'this selected variable?'; + } + let confirm = this.$mdDialog.confirm() + .title(confirmTitle) + .ariaLabel('Remove environment variables') + .ok('Delete!') + .cancel('Cancel') + .clickOutsideToClose(true); + + return this.$mdDialog.show(confirm); + } + +} diff --git a/dashboard/src/app/workspaces/workspace-details/environments/list-env-variables/list-env-variables.directive.js b/dashboard/src/app/workspaces/workspace-details/environments/list-env-variables/list-env-variables.directive.js new file mode 100644 index 00000000000..2e0fd2797f4 --- /dev/null +++ b/dashboard/src/app/workspaces/workspace-details/environments/list-env-variables/list-env-variables.directive.js @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2015-2016 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + */ +'use strict'; + +/** + * @ngdoc directive + * @name workspaces.details.directive:listEnvVariables + * @restrict E + * @element + * + * @description + * `` for displaying list of environment variables. + * + * @usage + * + * + * @author Oleksii Kurinnyi + */ +export class ListEnvVariables { + + /** + * Default constructor that is using resource + * @ngInject for Dependency injection + */ + constructor () { + this.restrict = 'E'; + this.templateUrl = 'app/workspaces/workspace-details/environments/list-env-variables/list-env-variables.html'; + + this.controller = 'ListEnvVariablesController'; + this.controllerAs = 'listEnvVariablesController'; + this.bindToController = true; + + // scope values + this.scope = { + envVariables: '=', + envVariablesOnChange: '&' + }; + } +} diff --git a/dashboard/src/app/workspaces/workspace-details/environments/list-env-variables/list-env-variables.html b/dashboard/src/app/workspaces/workspace-details/environments/list-env-variables/list-env-variables.html new file mode 100644 index 00000000000..523c050a5c0 --- /dev/null +++ b/dashboard/src/app/workspaces/workspace-details/environments/list-env-variables/list-env-variables.html @@ -0,0 +1,100 @@ + +
+ +
+ + +
+
+
+ +
+
+
+ + + +
+
+
+ + + +
+
+ +
+
+
+ {{envVariable.name}} +
+
+ {{envVariable.value}} +
+
+
+ +
+
+
+
+
+
+
+ + +
+
+ +
+
+ +
+
+
+ diff --git a/dashboard/src/app/workspaces/workspace-details/environments/list-env-variables/list-env-variables.styl b/dashboard/src/app/workspaces/workspace-details/environments/list-env-variables/list-env-variables.styl new file mode 100644 index 00000000000..eafbe648696 --- /dev/null +++ b/dashboard/src/app/workspaces/workspace-details/environments/list-env-variables/list-env-variables.styl @@ -0,0 +1,26 @@ +.list-environment-variables + * + outline none !important + + .che-list + margin-bottom 12px + background-color inherit + + .che-list-item-checkbox-main + padding-left 0 + + .md-button + margin-left 0 + margin-right 0 + margin-top 18px + filter none !important + + .env-variable-item-row + min-height 33px + + .che-list-header-content > *, .che-list + margin 0 + + .che-list-header md-item + border-top none + diff --git a/dashboard/src/app/workspaces/workspace-details/environments/list-ports/add-port-dialog/add-port-dialog.controller.js b/dashboard/src/app/workspaces/workspace-details/environments/list-ports/add-port-dialog/add-port-dialog.controller.js new file mode 100644 index 00000000000..9f8fcf4db6f --- /dev/null +++ b/dashboard/src/app/workspaces/workspace-details/environments/list-ports/add-port-dialog/add-port-dialog.controller.js @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2015-2016 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + */ +'use strict'; + +/** + * @ngdoc controller + * @name list.environment.variables.controller:AddPortDialogController + * @description This class is handling the controller for the dialog box about adding the port. + * @author Oleksii Kurinnyi + */ +export class AddPortDialogController { + + /** + * Default constructor that is using resource + * @ngInject for Dependency injection + */ + constructor($mdDialog, lodash) { + this.$mdDialog = $mdDialog; + this.updateInProgress = false; + this.lodash = lodash; + + this.usedPorts = []; + + this.portMin = 1024; + this.portMax = 65535; + this.protocol = 'http'; + + this.fillInUsedPorts(); + this.port = this.getLowestFreePort(); + } + + isUnique(port) { + return !this.usedPorts.includes(port); + } + + fillInUsedPorts() { + this.lodash.forEach(this.servers, (server) => { + this.usedPorts.push(parseInt(server.port, 10)); + }); + } + + getLowestFreePort() { + let port; + for (port=this.portMin; port<=this.portMax; port++) { + if (!this.usedPorts.includes(port)) { + break; + } + } + return port; + } + + /** + * It will hide the dialog box. + */ + hide() { + this.$mdDialog.hide(); + } + + /** + * Adds new port + */ + addPort() { + this.updateInProgress = true; + + this.callbackController.addPort(this.port, this.protocol).finally(() => { + this.updateInProgress = false; + this.hide(); + }); + } + +} diff --git a/dashboard/src/app/workspaces/workspace-details/environments/list-ports/add-port-dialog/add-port-dialog.html b/dashboard/src/app/workspaces/workspace-details/environments/list-ports/add-port-dialog/add-port-dialog.html new file mode 100644 index 00000000000..152fdedeec7 --- /dev/null +++ b/dashboard/src/app/workspaces/workspace-details/environments/list-ports/add-port-dialog/add-port-dialog.html @@ -0,0 +1,35 @@ + + + +
+ +
A number is required.
+
This port is already in use.
+
A number should be greater than 1023.
+
A number should be less than 65536.
+
+
+
+ + +
+ + +
+
+
+
diff --git a/dashboard/src/app/workspaces/workspace-details/environments/list-ports/add-port-dialog/add-port-dialog.styl b/dashboard/src/app/workspaces/workspace-details/environments/list-ports/add-port-dialog/add-port-dialog.styl new file mode 100644 index 00000000000..f6a6d906b5c --- /dev/null +++ b/dashboard/src/app/workspaces/workspace-details/environments/list-ports/add-port-dialog/add-port-dialog.styl @@ -0,0 +1,14 @@ +.add-port-dialog-content + padding 0 14px + + .add-port-dialog-input + margin -6px 0 + + .add-port-dialog-textarea + margin -8px 0 + + button + margin 0 30px 0 0 + + input, textarea + width 300px diff --git a/dashboard/src/app/workspaces/workspace-details/environments/list-ports/edit-port-dialog/edit-port-dialog.controller.js b/dashboard/src/app/workspaces/workspace-details/environments/list-ports/edit-port-dialog/edit-port-dialog.controller.js new file mode 100644 index 00000000000..5bb803f759a --- /dev/null +++ b/dashboard/src/app/workspaces/workspace-details/environments/list-ports/edit-port-dialog/edit-port-dialog.controller.js @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2015-2016 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + */ +'use strict'; + +/** + * @ngdoc controller + * @name list.environment.variables.controller:EditPortDialogController + * @description This class is handling the controller for the dialog box about editing the server ports. + * @author Oleksii Kurinnyi + */ +export class EditPortDialogController { + + /** + * Default constructor that is using resource + * @ngInject for Dependency injection + */ + constructor($mdDialog, lodash) { + this.$mdDialog = $mdDialog; + this.lodash = lodash; + + this.usedPorts = []; + + this.updateInProgress = false; + this.port = parseInt(this.servers[this.serverName].port, 10); + this.protocol = this.servers[this.serverName].protocol; + + this.fillInUsedPorts(); + } + + isUnique(port) { + return !this.usedPorts.includes(port); + } + + fillInUsedPorts() { + this.lodash.forEach(this.servers, (server) => { + let _port = parseInt(server.port, 10); + if (this.port !== _port) { + this.usedPorts.push(_port); + } + }); + } + + /** + * It will hide the dialog box. + */ + hide() { + this.$mdDialog.hide(); + } + + /** + * Update port + */ + updatePort() { + this.updateInProgress = true; + + this.callbackController.updatePort(this.serverName, this.port, this.protocol).finally(() => { + this.updateInProgress = false; + this.hide(); + }); + } +} diff --git a/dashboard/src/app/workspaces/workspace-details/environments/list-ports/edit-port-dialog/edit-port-dialog.html b/dashboard/src/app/workspaces/workspace-details/environments/list-ports/edit-port-dialog/edit-port-dialog.html new file mode 100644 index 00000000000..bb9e58d0741 --- /dev/null +++ b/dashboard/src/app/workspaces/workspace-details/environments/list-ports/edit-port-dialog/edit-port-dialog.html @@ -0,0 +1,36 @@ + + + +
+ +
A number is required.
+
This port is already in use.
+
A number should be greater than 1023.
+
A number should be less than 65536.
+
+
+
+ + +
+ + + +
+
+
+
diff --git a/dashboard/src/app/workspaces/workspace-details/environments/list-ports/edit-port-dialog/edit-port-dialog.styl b/dashboard/src/app/workspaces/workspace-details/environments/list-ports/edit-port-dialog/edit-port-dialog.styl new file mode 100644 index 00000000000..be6eb953f78 --- /dev/null +++ b/dashboard/src/app/workspaces/workspace-details/environments/list-ports/edit-port-dialog/edit-port-dialog.styl @@ -0,0 +1,14 @@ +.edit-port-dialog-content + padding 0 14px + + .edit-port-dialog-input + margin -6px 0 + + .edit-port-dialog-textarea + margin -8px 0 + + button + margin 0 30px 0 0 + + input, textarea + width 300px diff --git a/dashboard/src/app/workspaces/workspace-details/environments/list-ports/list-ports.controller.js b/dashboard/src/app/workspaces/workspace-details/environments/list-ports/list-ports.controller.js new file mode 100644 index 00000000000..1a46053e041 --- /dev/null +++ b/dashboard/src/app/workspaces/workspace-details/environments/list-ports/list-ports.controller.js @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2015-2016 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + */ +'use strict'; + +/** + * @ngdoc controller + * @name workspace.details.controller:ListPortsController + * @description This class is handling the controller for list of ports + * @author Oleksii Kurinnyi + */ +export class ListPortsController { + + /** + * Default constructor that is using resource + * @ngInject for Dependency injection + */ + constructor($mdDialog, lodash) { + this.$mdDialog = $mdDialog; + this.lodash = lodash; + + this.isNoSelected = true; + this.isBulkChecked = false; + this.serversSelectedStatus = {}; + this.serversSelectedNumber = 0; + this.serversOrderBy = 'name'; + + this.buildServersList(); + } + + buildServersList() { + this.serversList = this.lodash.map(this.servers, (server, name) => { + server.name = name; + server.protocol = server.protocol ? server.protocol : 'http'; + return server; + }); + } + + /** + * Update port selected status + */ + updateSelectedStatus() { + this.serversSelectedNumber = 0; + this.isBulkChecked = !!this.serversList.length; + this.serversList.forEach((server) => { + if (this.serversSelectedStatus[server.name]) { + this.serversSelectedNumber++; + } else { + this.isBulkChecked = false; + } + }); + } + + changePortSelection(name) { + this.serversSelectedStatus[name] = !this.serversSelectedStatus[name]; + this.updateSelectedStatus(); + } + + /** + * Change bulk selection value + */ + changeBulkSelection() { + if (this.isBulkChecked) { + this.deselectAllPorts(); + this.isBulkChecked = false; + return; + } + this.selectAllPorts(); + this.isBulkChecked = true; + } + + /** + * Check all ports in list + */ + selectAllPorts() { + this.serversSelectedNumber = this.serversList.length; + this.serversList.forEach((server) => { + this.serversSelectedStatus[server.name] = true; + }) + } + + /** + * Uncheck all ports in list + */ + deselectAllPorts() { + this.serversSelectedStatus = {}; + this.serversSelectedNumber = 0; + } + + addPort(port, protocol) { + let name = this.buildServerName(port); + this.servers[name] = {'port': port, 'protocol': protocol}; + + this.updateSelectedStatus(); + return this.serversOnChange().then(() => {this.buildServersList();}); + } + + updatePort(serverName, port, protocol) { + delete this.servers[serverName]; + delete this.serversSelectedStatus[serverName]; + this.updateSelectedStatus(); + + let newName = this.buildServerName(port); + this.servers[newName] = {'port': port, 'protocol': protocol}; + + return this.serversOnChange().then(() => {this.buildServersList();}); + } + + buildServerName(port) { + return port + '/tcp'; + } + + /** + * Show dialog to add new port + * @param $event + */ + showAddDialog($event) { + this.$mdDialog.show({ + targetEvent: $event, + controller: 'AddPortDialogController', + controllerAs: 'addPortDialogController', + bindToController: true, + clickOutsideToClose: true, + locals: { + servers: this.servers, + callbackController: this + }, + templateUrl: 'app/workspaces/workspace-details/environments/list-ports/add-port-dialog/add-port-dialog.html' + }); + } + + /** + * Show dialog to edit existing port + * @param $event + * @param serverName {string} + */ + showEditDialog($event, serverName) { + this.$mdDialog.show({ + targetEvent: $event, + controller: 'EditPortDialogController', + controllerAs: 'editPortDialogController', + bindToController: true, + clickOutsideToClose: true, + locals: { + serverName: serverName, + servers: this.servers, + callbackController: this + }, + templateUrl: 'app/workspaces/workspace-details/environments/list-ports/edit-port-dialog/edit-port-dialog.html' + }); + } + + /** + * Removes selected ports + */ + deleteSelectedPorts() { + this.showDeleteConfirmation(this.serversSelectedNumber).then(() => { + this.lodash.forEach(this.serversSelectedStatus, (server, name) => { + delete this.servers[name]; + }); + this.deselectAllPorts(); + this.isBulkChecked = false; + this.serversOnChange().then(() => {this.buildServersList();}); + }) + } + + /** + * Show confirmation popup before port to delete + * @param numberToDelete + * @returns {*} + */ + showDeleteConfirmation(numberToDelete) { + let confirmTitle = 'Would you like to delete '; + if (numberToDelete > 1) { + confirmTitle += 'these ' + numberToDelete + ' ports?'; + } + else { + confirmTitle += 'this selected port?'; + } + let confirm = this.$mdDialog.confirm() + .title(confirmTitle) + .ariaLabel('Remove port') + .ok('Delete!') + .cancel('Cancel') + .clickOutsideToClose(true); + + return this.$mdDialog.show(confirm); + } + +} diff --git a/dashboard/src/app/workspaces/workspace-details/environments/list-ports/list-ports.directive.js b/dashboard/src/app/workspaces/workspace-details/environments/list-ports/list-ports.directive.js new file mode 100644 index 00000000000..1c179d51490 --- /dev/null +++ b/dashboard/src/app/workspaces/workspace-details/environments/list-ports/list-ports.directive.js @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2015-2016 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + */ +'use strict'; + +/** + * @ngdoc directive + * @name workspaces.details.directive:listPorts + * @restrict E + * @element + * + * @description + * `` for displaying list of ports + * + * @usage + * + * + * @author Oleksii Kurinnyi + */ +export class ListPorts { + + /** + * Default constructor that is using resource + * @ngInject for Dependency injection + */ + constructor () { + this.restrict = 'E'; + this.templateUrl = 'app/workspaces/workspace-details/environments/list-ports/list-ports.html'; + + this.controller = 'ListPortsController'; + this.controllerAs = 'listPortsController'; + this.bindToController = true; + + // scope values + this.scope = { + servers: '=', + serversOnChange: '&' + }; + } +} diff --git a/dashboard/src/app/workspaces/workspace-details/environments/list-ports/list-ports.html b/dashboard/src/app/workspaces/workspace-details/environments/list-ports/list-ports.html new file mode 100644 index 00000000000..ad5b8a9a356 --- /dev/null +++ b/dashboard/src/app/workspaces/workspace-details/environments/list-ports/list-ports.html @@ -0,0 +1,100 @@ + +
+ +
+ + +
+
+
+ +
+
+
+ + + +
+
+
+ + + +
+
+ +
+
+
+ {{server.port}} +
+
+ YES +
+
+
+ +
+
+
+
+
+
+
+ + +
+
+ +
+
+ +
+
+
+ diff --git a/dashboard/src/app/workspaces/workspace-details/environments/list-ports/list-ports.styl b/dashboard/src/app/workspaces/workspace-details/environments/list-ports/list-ports.styl new file mode 100644 index 00000000000..f1a79ae77d0 --- /dev/null +++ b/dashboard/src/app/workspaces/workspace-details/environments/list-ports/list-ports.styl @@ -0,0 +1,25 @@ +.list-ports + * + outline none !important + + .che-list + margin-bottom 12px + background-color inherit + + .che-list-item-checkbox-main + padding-left 0 + + .md-button + margin-left 0 + margin-right 0 + margin-top 18px + filter none !important + + .server-item-row + min-height 33px + + .che-list-header-content > *, .che-list + margin 0 + + .che-list-header md-item + border-top none diff --git a/dashboard/src/app/workspaces/workspace-details/environments/machine-config/edit-machine-name-dialog/edit-machine-name-dialog.controller.js b/dashboard/src/app/workspaces/workspace-details/environments/machine-config/edit-machine-name-dialog/edit-machine-name-dialog.controller.js new file mode 100644 index 00000000000..e22dbd703ca --- /dev/null +++ b/dashboard/src/app/workspaces/workspace-details/environments/machine-config/edit-machine-name-dialog/edit-machine-name-dialog.controller.js @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2015-2016 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + */ +'use strict'; + +/** + * @ngdoc controller + * @name machine.config.controller:EditMachineNameDialogController + * @description This class is handling the controller for the dialog box about editing the machine name. + * @author Oleksii Kurinnyi + */ +export class EditMachineNameDialogController { + + /** + * Default constructor that is using resource + * @ngInject for Dependency injection + */ + constructor($mdDialog) { + this.$mdDialog = $mdDialog; + + this.updateInProgress = false; + } + + isUnique(name) { + return !this.machinesNames.includes(name); + } + + /** + * It will hide the dialog box. + */ + hide() { + this.$mdDialog.hide(); + } + + /** + * Update machine name + */ + updateMachineName() { + this.updateInProgress = true; + + this.callbackController.updateMachineName(this.name).finally(() => { + this.updateInProgress = false; + this.hide(); + }); + } +} diff --git a/dashboard/src/app/workspaces/workspace-details/environments/machine-config/edit-machine-name-dialog/edit-machine-name-dialog.html b/dashboard/src/app/workspaces/workspace-details/environments/machine-config/edit-machine-name-dialog/edit-machine-name-dialog.html new file mode 100644 index 00000000000..df77d30f035 --- /dev/null +++ b/dashboard/src/app/workspaces/workspace-details/environments/machine-config/edit-machine-name-dialog/edit-machine-name-dialog.html @@ -0,0 +1,32 @@ + + + +
+ +
A name is required.
+
This name is already used.
+
+
+
+ + +
+ + + +
+
+
+
diff --git a/dashboard/src/app/workspaces/workspace-details/environments/machine-config/edit-machine-name-dialog/edit-machine-name-dialog.styl b/dashboard/src/app/workspaces/workspace-details/environments/machine-config/edit-machine-name-dialog/edit-machine-name-dialog.styl new file mode 100644 index 00000000000..8f8805ad2eb --- /dev/null +++ b/dashboard/src/app/workspaces/workspace-details/environments/machine-config/edit-machine-name-dialog/edit-machine-name-dialog.styl @@ -0,0 +1,14 @@ +.edit-machine-name-dialog-content + padding 0 14px + + .edit-machine-name-dialog-input + margin -6px 0 + + .edit-machine-name-dialog-textarea + margin -8px 0 + + button + margin 0 30px 0 0 + + input, textarea + width 300px diff --git a/dashboard/src/app/workspaces/workspace-details/environments/machine-config/machine-config.controller.js b/dashboard/src/app/workspaces/workspace-details/environments/machine-config/machine-config.controller.js new file mode 100644 index 00000000000..004150ac944 --- /dev/null +++ b/dashboard/src/app/workspaces/workspace-details/environments/machine-config/machine-config.controller.js @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2015-2016 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + */ +'use strict'; + +/** + * @ngdoc controller + * @name workspace.details.controller:WorkspaceMachineConfigController + * @description This class is handling the controller for machine config + * @author Oleksii Kurinnyi + */ +export class WorkspaceMachineConfigController { + + /** + * Default constructor that is using resource injection + * @ngInject for Dependency injection + */ + constructor($mdDialog, $scope, $timeout, lodash) { + this.$mdDialog = $mdDialog; + this.$timeout = $timeout; + this.lodash = lodash; + + this.timeoutPromise; + $scope.$on('$destroy', () => { + if (this.timeoutPromise) { + $timeout.cancel(this.timeoutPromise); + } + }); + + this.init(); + } + + /** + * Sets initial values + */ + init() { + this.machine = this.lodash.find(this.machinesList, (machine) => { + return machine.name === this.machineName; + }); + + this.machineConfig = { + source: this.environmentManager.getSource(this.machine), + isDev: this.environmentManager.isDev(this.machine), + memoryLimitBytes: this.environmentManager.getMemoryLimit(this.machine), + servers: this.environmentManager.getServers(this.machine), + canEditEnvVariables: this.environmentManager.canEditEnvVariables(this.machine), + envVariables: this.environmentManager.getEnvVariables(this.machine), + canRenameMachine: this.environmentManager.canRenameMachine(this.machine), + canDeleteMachine: this.environmentManager.canDeleteMachine(this.machine) + }; + + this.newDev = this.machineConfig.isDev; + + this.newRam = this.machineConfig.memoryLimitBytes; + } + + /** + * Modifies agents list in order to add or remove 'ws-agent' + */ + enableDev() { + this.$timeout.cancel(this.timeoutPromise); + + if (this.machineConfig.isDev === this.newDev) { + return; + } + + this.timeoutPromise = this.$timeout(() => { + // remove ws-agent from machine which is the dev machine now + this.machinesList.forEach((machine) => { + if (this.environmentManager.isDev(machine)) { + this.environmentManager.setDev(machine, false); + } + }); + + // add ws-agent to current machine agents list + this.environmentManager.setDev(this.machine, this.newDev); + + this.doUpdateConfig().then(() => { + this.init(); + }); + }, 1000); + } + + /** + * Updates amount of RAM for machine after a delay + * @param isFormValid {boolean} + */ + updateRam(isFormValid) { + this.$timeout.cancel(this.timeoutPromise); + + if (!isFormValid || this.ram === this.newRam) { + return; + } + + this.timeoutPromise = this.$timeout(() => { + this.environmentManager.setMemoryLimit(this.machine, this.newRam); + + this.doUpdateConfig().then(() => { + this.init(); + }); + }, 1000); + } + + /** + * Callback which is called in order to update list of servers + * @returns {Promise} + */ + updateServers() { + this.environmentManager.setServers(this.machine, this.machineConfig.servers); + return this.doUpdateConfig(); + } + + /** + * Callback which is called in order to update list of environment variables + * @returns {Promise} + */ + updateEnvVariables() { + this.environmentManager.setEnvVariables(this.machine, this.machineConfig.envVariables); + + return this.doUpdateConfig().then(() => { + this.init(); + }); + } + + /** + * Calls parent controller's callback to update machine config + * @returns {IPromise|*|Promise.} + */ + doUpdateConfig() { + return this.machineConfigOnChange(); + } + + /** + * Show dialog to edit machine name + * @param $event + */ + showEditDialog($event) { + let machinesNames = Object.keys(this.machinesList); + + this.$mdDialog.show({ + targetEvent: $event, + controller: 'EditMachineNameDialogController', + controllerAs: 'editMachineNameDialogController', + bindToController: true, + clickOutsideToClose: true, + locals: { + name: this.machineName, + machinesNames: machinesNames, + callbackController: this + }, + templateUrl: 'app/workspaces/workspace-details/environments/machine-config/edit-machine-name-dialog/edit-machine-name-dialog.html' + }); + } + + /** + * Updates machine name + * @param newMachineName {string} new machine name + */ + updateMachineName(newMachineName) { + if (this.machineName === newMachineName) { + let defer = this.$q.defer(); + defer.resolve(); + return defer.promise; + } + + return this.machineNameOnChange({ + oldName: this.machineName, + newName: newMachineName + }).then(() => { + this.init(); + }); + } + + /** + * Deletes machine + */ + deleteMachine() { + this.showDeleteConfirmation().then(() => { + this.machineOnDelete({ + name: this.machineName + }).then(() => { + this.init(); + }); + }); + } + + /** + * Show confirmation popup before machine to delete + * @returns {*} + */ + showDeleteConfirmation() { + let confirmTitle = 'Would you like to delete this machine?'; + let confirm = this.$mdDialog.confirm() + .title(confirmTitle) + .ariaLabel('Remove machine') + .ok('Delete!') + .cancel('Cancel') + .clickOutsideToClose(true); + + return this.$mdDialog.show(confirm); + } +} diff --git a/dashboard/src/app/workspaces/workspace-details/environments/machine-config/machine-config.directive.js b/dashboard/src/app/workspaces/workspace-details/environments/machine-config/machine-config.directive.js new file mode 100644 index 00000000000..fcb3b779c14 --- /dev/null +++ b/dashboard/src/app/workspaces/workspace-details/environments/machine-config/machine-config.directive.js @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2015-2016 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + */ +'use strict'; + +/** + * @ngdoc directive + * @name workspaces.details.directive:workspaceMachineConfig + * @restrict E + * @element + * + * @description + * ` for displaying workspace config. + * + * @usage + * + * + * @author Oleksii Kurinnyi + */ +export class WorkspaceMachineConfig { + + /** + * Default constructor that is using resource + * @ngInject for Dependency injection + */ + constructor () { + this.restrict = 'E'; + this.templateUrl = 'app/workspaces/workspace-details/environments/machine-config/machine-config.html'; + + this.controller = 'WorkspaceMachineConfigController'; + this.controllerAs = 'workspaceMachineConfigController'; + this.bindToController = true; + + this.scope = { + machineName: '=', + machinesList: '=', + environmentManager: '=', + machineNameOnChange: '&', + machineConfigOnChange: '&', + machineOnDelete: '&', + machineIsOpened: '=' + }; + } +} + diff --git a/dashboard/src/app/workspaces/workspace-details/environments/machine-config/machine-config.html b/dashboard/src/app/workspaces/workspace-details/environments/machine-config/machine-config.html new file mode 100644 index 00000000000..5e00fa1c2ef --- /dev/null +++ b/dashboard/src/app/workspaces/workspace-details/environments/machine-config/machine-config.html @@ -0,0 +1,96 @@ + +
+ + +
+
+ +
+
{{workspaceMachineConfigController.machineName}}
+
+ +
+
+ + +
+ + + +
+

+ {{key}}:{{val}} +

+
+
+ + + +
+ + + +
+
+ + + +
+ + Injects terminal, SSH and IDE tooling. + +
+
+ + + +
+ +
+
+ + + +
+ +
+
+ + + + + +
+
diff --git a/dashboard/src/app/workspaces/workspace-details/environments/machine-config/machine-config.styl b/dashboard/src/app/workspaces/workspace-details/environments/machine-config/machine-config.styl new file mode 100644 index 00000000000..69058277be2 --- /dev/null +++ b/dashboard/src/app/workspaces/workspace-details/environments/machine-config/machine-config.styl @@ -0,0 +1,60 @@ +.workspace-machine-config + * + outline none !important + + button + margin 0 30px 0 0 + + .config-title-row + padding 15px 0 + border-bottom 1px solid $list-separator-color + cursor pointer + + .config-title + padding 0 17px + white-space nowrap + text-transform uppercase + font-size 12px + color #555c65 //TODO + + .config-title-action-show, .config-title-action-delete + color darken($stroke-color, 13%) + font-size 14px + cursor pointer + + .config-title-action-edit + color $primary-color + font-size 14px + cursor pointer + + .config-input + margin -6px 0 + + .config-paragraph + margin-bottom -10px + + .recipe-item-name + color $disabled-color + margin-right 5px + + .config-ram + margin-bottom -26px + + .config-params + padding 0 70px + background-color $light-gray-color + + .config-params-table + margin-top -25px + + .config-dev-machine-switch + margin -22px 0 + + .config-dev-machine-info + padding-left 13px + + .config-delete-label label + color $che-delete-label-color + + che-button-danger button + box-shadow none !important diff --git a/dashboard/src/app/workspaces/workspace-details/workspace-details.controller.js b/dashboard/src/app/workspaces/workspace-details/workspace-details.controller.js index f23637e9019..c88a4e99112 100644 --- a/dashboard/src/app/workspaces/workspace-details/workspace-details.controller.js +++ b/dashboard/src/app/workspaces/workspace-details/workspace-details.controller.js @@ -20,7 +20,7 @@ export class WorkspaceDetailsController { * Default constructor that is using resource injection * @ngInject for Dependency injection */ - constructor($scope, $rootScope, $route, $location, cheWorkspace, $mdDialog, cheNotification, ideSvc, $log, workspaceDetailsService, lodash, $timeout) { + constructor($scope, $rootScope, $route, $location, cheWorkspace, $mdDialog, cheNotification, ideSvc, $log, workspaceDetailsService, lodash, $q, $timeout) { this.$rootScope = $rootScope; this.cheNotification = cheNotification; this.cheWorkspace = cheWorkspace; @@ -30,9 +30,11 @@ export class WorkspaceDetailsController { this.$log = $log; this.workspaceDetailsService = workspaceDetailsService; this.lodash = lodash; + this.$q = $q; this.$timeout = $timeout; this.workspaceDetails = {}; + this.copyWorkspaceDetails = {}; this.namespace = $route.current.params.namespace; this.workspaceName = $route.current.params.workspaceName; this.workspaceKey = this.namespace + ":" + this.workspaceName; @@ -49,11 +51,9 @@ export class WorkspaceDetailsController { let promise = this.cheWorkspace.fetchWorkspaceDetails(this.workspaceKey); promise.then(() => { this.updateWorkspaceData(); - this.origRam = this.newRam; }, (error) => { if (error.status === 304) { this.updateWorkspaceData(); - this.origRam = this.newRam; } else { this.loading = false; this.invalidWorkspace = error.statusText; @@ -61,7 +61,6 @@ export class WorkspaceDetailsController { }); } else { this.updateWorkspaceData(); - this.origRam = this.newRam; } this.cheWorkspace.fetchWorkspaces(); @@ -102,26 +101,11 @@ export class WorkspaceDetailsController { if (this.loading) { this.loading = false; } - this.workspaceId = this.workspaceDetails.id; - this.newName = angular.copy(this.workspaceDetails.config.name); - this.newRam = this.getRam(); - } - /** - * Returns amount of RAM for dev machine in default environment. - * @returns {*} - */ - getRam() { - // get default environment - let defaultEnv = this.workspaceDetails.config.environments[this.workspaceDetails.config.defaultEnv]; - - // get dev machine config - let devMachineConfig = this.lodash.find(defaultEnv.machines, (machine) => { - return machine.agents.indexOf('org.eclipse.che.ws-agent') >= 0; - }); + angular.copy(this.workspaceDetails, this.copyWorkspaceDetails); - //TODO not implemented yet return angular.copy(devMachineConfig.limits.ram); - return ""; + this.workspaceId = this.workspaceDetails.id; + this.newName = this.workspaceDetails.config.name; } /** @@ -136,78 +120,57 @@ export class WorkspaceDetailsController { } /** - * Returns true if amount of RAM of workspace is changed - * @returns {boolean} + * Updates name of workspace + * @param isFormValid {boolean} true if workspaceNameForm is valid */ - isRamChanged() { - if (this.workspaceDetails) { - return this.getRam() !== this.newRam; + updateName(isFormValid) { + if (isFormValid === false || !this.isNameChanged()) { + return; } - return false; + + this.copyWorkspaceDetails.config.name = this.newName; + this.doUpdateWorkspace(); } /** - * Calls method to update workspace info after timeout. - * @param isFormValid {Boolean} true if form is valid + * Callback which is called in order to update workspace config + * @returns {Promise} */ - updateWorkspace(isFormValid) { - this.$timeout.cancel(this.timeoutPromise); - - if (isFormValid === false || !(this.isNameChanged() || this.isRamChanged())) { - return; + updateWorkspaceConfig() { + if (angular.equals(this.copyWorkspaceDetails.config, this.workspaceDetails.config)) { + let defer = this.$q.defer(); + defer.resolve(); + return defer.promise; } - this.timeoutPromise = this.$timeout(() => { - this.isLoading = true; - this.cheWorkspace.fetchWorkspaceDetails(this.workspaceId).then(() => { - this.doUpdateWorkspace(); - }, () => { - this.doUpdateWorkspace(); - }); - }, 500); + return this.doUpdateWorkspace(); } /** * Updates workspace info. */ doUpdateWorkspace() { - this.workspaceDetails = this.cheWorkspace.getWorkspacesById().get(this.workspaceId); - let workspaceNewDetails = angular.copy(this.workspaceDetails); - - workspaceNewDetails.config.name = this.newName; - - this.lodash.forEach(workspaceNewDetails.config.environments, (env) => { - if (env.name === workspaceNewDetails.config.defaultEnv) { - this.lodash.forEach(env.machines, (machine) => { - if (machine.agents.indexOf('org.eclipse.che.ws-agent') >= 0) { - /* TODO not implemented yet machine.limits.ram = this.newRam; - - if (this.getWorkspaceStatus() === 'STOPPED') { - this.origRam = this.newRam; - }*/ - } - }); - } - }); - - delete workspaceNewDetails.links; + this.isLoading = true; + delete this.copyWorkspaceDetails.links; - let promise = this.cheWorkspace.updateWorkspace(this.workspaceId, workspaceNewDetails); + let promise = this.cheWorkspace.updateWorkspace(this.workspaceId, this.copyWorkspaceDetails); promise.then((data) => { this.workspaceName = data.config.name; this.updateWorkspaceData(); - this.cheNotification.showInfo('Workspace is successfully updated.'); - this.$location.path('/workspace/' + this.namespace + '/' + this.workspaceName); + this.cheNotification.showInfo('Workspace updated.'); + return this.$location.path('/workspace/' + this.namespace + '/' + this.workspaceName); }, (error) => { this.isLoading = false; this.cheNotification.showError(error.data.message !== null ? error.data.message : 'Update workspace failed.'); this.$log.error(error); }); + + return promise; } //Perform workspace deletion. deleteWorkspace(event) { - var confirm = this.$mdDialog.confirm() + let confirm = this.$mdDialog.confirm() .title('Would you like to delete workspace \'' + this.workspaceDetails.config.name + '\'?') .ariaLabel('Delete workspace') .ok('Delete it!') @@ -274,9 +237,7 @@ export class WorkspaceDetailsController { stopWorkspace() { let promise = this.cheWorkspace.stopWorkspace(this.workspaceId); - promise.then(() => { - this.origRam = this.newRam; - }, (error) => { + promise.then(() => {}, (error) => { this.cheNotification.showError(error.data.message !== null ? error.data.message : 'Stop workspace failed.'); this.$log.error(error); }); diff --git a/dashboard/src/app/workspaces/workspace-details/workspace-details.html b/dashboard/src/app/workspaces/workspace-details/workspace-details.html index aad67dc2c13..6302fde0e4f 100644 --- a/dashboard/src/app/workspaces/workspace-details/workspace-details.html +++ b/dashboard/src/app/workspaces/workspace-details/workspace-details.html @@ -22,12 +22,14 @@
- - + - - - - - - - - -
@@ -98,6 +91,20 @@
+ + + + + Runtime + + + + + + + diff --git a/dashboard/src/app/workspaces/workspaces-config.js b/dashboard/src/app/workspaces/workspaces-config.js index 34f282ce4a8..36a56aff411 100644 --- a/dashboard/src/app/workspaces/workspaces-config.js +++ b/dashboard/src/app/workspaces/workspaces-config.js @@ -43,6 +43,22 @@ import {CheStackLibraryFilterController} from './create-workspace/select-stack/s import {CheStackLibraryFilter} from './create-workspace/select-stack/stack-library/stack-library-filter/che-stack-library-filter.directive'; import {CreateProjectStackLibrarySelectedStackFilter} from './create-workspace/select-stack/stack-library/create-project-stack-library-selected-stack.filter'; +import {WorkspaceEnvironmentsController} from './workspace-details/environments/environments.controller'; +import {WorkspaceEnvironments} from './workspace-details/environments/environments.directive'; +import {WorkspaceMachineConfigController} from './workspace-details/environments/machine-config/machine-config.controller'; +import {WorkspaceMachineConfig} from './workspace-details/environments/machine-config/machine-config.directive'; +import {EditMachineNameDialogController} from './workspace-details/environments/machine-config/edit-machine-name-dialog/edit-machine-name-dialog.controller'; + +import {ListEnvVariablesController} from './workspace-details/environments/list-env-variables/list-env-variables.controller'; +import {ListEnvVariables} from './workspace-details/environments/list-env-variables/list-env-variables.directive'; +import {AddVariableDialogController} from './workspace-details/environments/list-env-variables/add-variable-dialog/add-variable-dialog.controller'; +import {EditVariableDialogController} from './workspace-details/environments/list-env-variables/edit-variable-dialog/edit-variable-dialog.controller'; + +import {ListPortsController} from './workspace-details/environments/list-ports/list-ports.controller'; +import {ListPorts} from './workspace-details/environments/list-ports/list-ports.directive'; +import {AddPortDialogController} from './workspace-details/environments/list-ports/add-port-dialog/add-port-dialog.controller'; +import {EditPortDialogController} from './workspace-details/environments/list-ports/edit-port-dialog/edit-port-dialog.controller'; + /** * @ngdoc controller * @name workspaces:WorkspacesConfig @@ -98,12 +114,28 @@ export class WorkspacesConfig { register.controller('CheStackLibraryFilterController', CheStackLibraryFilterController); register.directive('cheStackLibraryFilter', CheStackLibraryFilter); + register.controller('WorkspaceEnvironmentsController', WorkspaceEnvironmentsController); + register.directive('workspaceEnvironments', WorkspaceEnvironments); + register.controller('WorkspaceMachineConfigController', WorkspaceMachineConfigController); + register.directive('workspaceMachineConfig', WorkspaceMachineConfig); + register.controller('EditMachineNameDialogController', EditMachineNameDialogController); + + register.controller('ListEnvVariablesController', ListEnvVariablesController); + register.directive('listEnvVariables', ListEnvVariables); + register.controller('AddVariableDialogController', AddVariableDialogController); + register.controller('EditVariableDialogController', EditVariableDialogController); + + register.controller('ListPortsController', ListPortsController); + register.directive('listPorts', ListPorts); + register.controller('AddPortDialogController', AddPortDialogController); + register.controller('EditPortDialogController', EditPortDialogController); + let locationProvider = { title: (params) => { return params.workspaceName;}, templateUrl: 'app/workspaces/workspace-details/workspace-details.html', controller: 'WorkspaceDetailsController', controllerAs: 'workspaceDetailsCtrl' - } + }; // config routes register.app.config(function ($routeProvider) { diff --git a/dashboard/src/components/api/che-api-config.js b/dashboard/src/components/api/che-api-config.js index 0a92d8ac59e..ddd65743a7b 100644 --- a/dashboard/src/components/api/che-api-config.js +++ b/dashboard/src/components/api/che-api-config.js @@ -28,6 +28,7 @@ import {CheAdminPlugins} from './che-admin-plugins.factory'; import {CheAdminService} from './che-admin-service.factory'; import {CheRemote} from './remote/che-remote.factory'; import {CheOAuthProvider} from './che-o-auth-provider.factory'; +import {CheEnvironmentRegistry} from './environment/che-environment-registry.factory'; export class ApiConfig { @@ -49,5 +50,6 @@ export class ApiConfig { register.factory('cheAPI', CheAPI); register.factory('cheRemote', CheRemote); register.factory('cheOAuthProvider', CheOAuthProvider); + register.factory('cheEnvironmentRegistry', CheEnvironmentRegistry); } } diff --git a/dashboard/src/components/api/che-workspace.factory.js b/dashboard/src/components/api/che-workspace.factory.js index f5536932c65..8c751a468c9 100644 --- a/dashboard/src/components/api/che-workspace.factory.js +++ b/dashboard/src/components/api/che-workspace.factory.js @@ -11,6 +11,9 @@ 'use strict'; import {CheWorkspaceAgent} from './che-workspace-agent'; +import {ComposeEnvironmentManager} from './environment/compose-environment-manager'; +import {DockerFileEnvironmentManager} from './environment/docker-file-environment-manager'; +import {DockerImageEnvironmentManager} from './environment/docker-image-environment-manager'; /** * This class is handling the workspace retrieval @@ -23,7 +26,7 @@ export class CheWorkspace { * Default constructor that is using resource * @ngInject for Dependency injection */ - constructor ($resource, $q, cheWebsocket, lodash) { + constructor ($resource, $q, cheWebsocket, lodash, cheEnvironmentRegistry, $log) { // keep resource this.$resource = $resource; this.$q = $q; @@ -60,6 +63,10 @@ export class CheWorkspace { createSnapshot: {method: 'POST', url: '/api/workspace/:workspaceId/snapshot'} } ); + + cheEnvironmentRegistry.addEnvironmentManager('compose', new ComposeEnvironmentManager($log)); + cheEnvironmentRegistry.addEnvironmentManager('dockerfile', new DockerFileEnvironmentManager($log)); + cheEnvironmentRegistry.addEnvironmentManager('dockerimage', new DockerImageEnvironmentManager($log)); } getWorkspaceAgent(workspaceId) { diff --git a/dashboard/src/components/api/environment/che-environment-registry.factory.js b/dashboard/src/components/api/environment/che-environment-registry.factory.js new file mode 100644 index 00000000000..b5430c3e3ca --- /dev/null +++ b/dashboard/src/components/api/environment/che-environment-registry.factory.js @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2015-2016 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + */ +'use strict'; + +/** + * Registry for maintaining environment managers. + * + * @author Ann Shumilova + */ +export class CheEnvironmentRegistry { + + constructor() { + this.managers = new Map(); + } + + /** + * Adds new environment manager to handle environments with specified type. + * + * @param type environment's type + * @param manager environment's manager + */ + addEnvironmentManager(type, manager) { + this.managers.set(type, manager); + } + + /** + * Returns the registered environment manager by type. + * + * @param type environment's type + * @returns {*} + */ + getEnvironmentManager(type) { + return this.managers.get(type); + } + + /** + * Returns the list of registered environments managers. + * + * @returns {lookup.managers|*|managers} + */ + getEnvironmentManagers() { + return this.managers; + } +} diff --git a/dashboard/src/components/api/environment/compose-environment-manager.js b/dashboard/src/components/api/environment/compose-environment-manager.js new file mode 100644 index 00000000000..382e8374195 --- /dev/null +++ b/dashboard/src/components/api/environment/compose-environment-manager.js @@ -0,0 +1,359 @@ +/* + * Copyright (c) 2015-2016 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + */ +'use strict'; + +import {EnvironmentManager} from './environment-manager'; + +/** + * This is the implementation of environment manager that handles the docker compose format. + * + * Format sample and specific description: + * + *services: + * devmachine: + * image: codenvy/ubuntu_jdk8 + * depends_on: + * - anotherMachine + * mem_limit: 2147483648 + * anotherMachine: + * image: codenvy/ubuntu_jdk8 + * depends_on: + * - thirdMachine + * mem_limit: 1073741824 + * thirdMachine: + * image: codenvy/ubuntu_jdk8 + * mem_limit: 512741824 + * labels: + * com.example.description: "Accounting webapp" + * com.example.department: "Finance" + * com.example.label-with-empty-value: "" + * environment: + * SOME_ENV: development + * SHOW: 'true' + * SESSION_SECRET: + * + * + * + * The recipe type is compose. + * Machines are described both in recipe and in machines attribute of the environment (machine configs). + * The machine configs contain memoryLimitBytes in attributes, servers and agent. + * Environment variables can be set only in recipe content. + * + * @author Ann Shumilova + */ + + +export class ComposeEnvironmentManager extends EnvironmentManager { + + constructor($log) { + super(); + + this.$log = $log; + } + + get editorMode() { + return 'text/x-yaml'; + } + + /** + * Parses recipe content + * + * @param content {string} recipe content + * @returns {object} recipe object + */ + _parseRecipe(content) { + let recipe = {}; + try { + recipe = jsyaml.load(content); + } catch (e) { + this.$log.error(e); + } + return recipe; + } + + /** + * Dumps recipe object + * + * @param recipe {object} recipe object + * @returns {string} recipe content + */ + + _stringifyRecipe(recipe) { + let content = ''; + try { + content = jsyaml.dump(recipe); + } catch (e) { + this.$log.error(e); + } + + return content; + } + + /** + * Retrieves the list of machines. + * + * @param environment environment's configuration + * @returns {Array} list of machines defined in environment + */ + getMachines(environment) { + let recipe = null, + machines = [], + machineNames; + + if (environment.recipe.content) { + recipe = this._parseRecipe(environment.recipe.content); + machineNames = Object.keys(recipe.services); + } else { + machineNames = Object.keys(environment.machines); + } + + machineNames.forEach((machineName) => { + let machine = angular.copy(environment.machines[machineName]) || {}; + machine.name = machineName; + machine.recipe = recipe ? recipe.services[machineName] : recipe; + + // memory + let memoryLimitBytes = this.getMemoryLimit(machine); + if (memoryLimitBytes === -1 && recipe) { + this.setMemoryLimit(machine, recipe.services[machineName].mem_limit); + } + machines.push(machine); + }); + + return machines; + } + + /** + * Provides the environment configuration based on machines format. + * + * @param environment origin of the environment to be edited + * @param machines the list of machines + * @returns environment's configuration + */ + getEnvironment(environment, machines) { + let newEnvironment = super.getEnvironment(environment, machines); + + if (newEnvironment.recipe.content) { + let recipe = this._parseRecipe(newEnvironment.recipe.content); + + machines.forEach((machine) => { + let machineName = machine.name; + if (machine.recipe.environment && Object.keys(machine.recipe.environment).length) { + recipe.services[machineName].environment = angular.copy(machine.recipe.environment); + } else { + delete recipe.services[machineName].environment; + } + }); + try { + newEnvironment.recipe.content = this._stringifyRecipe(recipe); + } catch (e) { + this.$log.error('Cannot retrieve environment\'s recipe, error: ', e); + } + } + + return newEnvironment; + } + + /** + * Returns object which contains docker image or link to docker file and build context. + * + * @param machine {object} + * @returns {*} + */ + getSource(machine) { + if (!machine.recipe) { + return null; + } + + if (machine.recipe.image) { + return {image: machine.recipe.image}; + } else if (machine.recipe.build) { + return machine.recipe.build; + } + } + + /** + * Returns true if environment recipe content is present. + * + * @param machine {object} + * @returns {boolean} + */ + canEditEnvVariables(machine) { + return !!machine.recipe; + } + + /** + * Returns object with environment variables. + * + * @param machine {object} + * @returns {*} + */ + getEnvVariables(machine) { + if (!machine.recipe) { + return null; + } + + return machine.recipe.environment || {}; + } + + /** + * Updates machine with new environment variables. + * + * @param machine {object} + * @param envVariables {object} + */ + setEnvVariables(machine, envVariables) { + if (!machine.recipe) { + return; + } + + if (Object.keys(envVariables).length) { + machine.recipe.environment = angular.copy(envVariables); + } else { + delete machine.recipe.environment; + } + } + + /** + * Returns true if machine can be renamed. + * + * @param machine {object} + * @returns {boolean} + */ + canRenameMachine(machine) { + return !!machine.recipe; + } + + /** + * Renames machine. + * + * @param environment {object} + * @param oldName {string} + * @param newName {string} + * @returns {*} new environment + */ + renameMachine(environment, oldName, newName) { + try { + let recipe = this._parseRecipe(environment.recipe.content); + + // fix relations to other machines in recipe + Object.keys(recipe.services).forEach((serviceName) => { + if (serviceName === oldName) { + return; + } + + // fix 'depends_on' + let dependsOn = recipe.services[serviceName].depends_on || [], + index = dependsOn.indexOf(oldName); + if (index > -1) { + dependsOn.splice(index, 1); + dependsOn.push(newName); + } + + // fix 'links' + let links = recipe.services[serviceName].links || [], + re = new RegExp('^' + oldName + '(?:$|:(.+))'); + for (let i = 0; i < links.length; i++) { + if (re.test(links[i])) { + let match = links[i].match(re), + alias = match[1] || '', + newLink = alias ? newName + ':' + alias : newName; + links.splice(i, 1); + links.push(newLink); + + break; + } + } + }); + + // rename machine in recipe + recipe.services[newName] = recipe.services[oldName]; + delete recipe.services[oldName]; + + // try to update recipe + environment.recipe.content = this._stringifyRecipe(recipe); + + // and then update config + environment.machines[newName] = environment[oldName]; + delete environment.machines[oldName]; + } catch (e) { + this.$log.error('Cannot rename machine, error: ', e); + } + + return environment; + } + + /** + * Returns true if machine can be deleted. + * + * @param machine {object} + * @returns {boolean} + */ + canDeleteMachine(machine) { + return !!machine.recipe; + } + + /** + * Removes machine. + * + * @param environment {object} + * @param name {string} name of machine + * @returns {*} new environment + */ + deleteMachine(environment, name) { + try { + let recipe = this._parseRecipe(environment.recipe.content); + + // fix relations to other machines in recipe + Object.keys(recipe.services).forEach((serviceName) => { + if (serviceName === name) { + return; + } + + // fix 'depends_on' + let dependsOn = recipe.services[serviceName].depends_on || [], + index = dependsOn.indexOf(name); + if (index > -1) { + dependsOn.splice(index, 1); + if (dependsOn.length === 0) { + delete recipe.services[serviceName].depends_on; + } + } + + // fix 'links' + let links = recipe.services[serviceName].links || [], + re = new RegExp('^' + name + '(?:$|:(.+))'); + for (let i = 0; i < links.length; i++) { + if (re.test(links[i])) { + links.splice(i, 1); + break; + } + } + if (links.length === 0) { + delete recipe.services[serviceName].links; + } + }); + + // delete machine from recipe + delete recipe.services[name]; + + // try to update recipe + environment.recipe.content = this._stringifyRecipe(recipe); + + // and then update config + delete environment.machines[name]; + } catch (e) { + this.$log.error('Cannot delete machine, error: ', e); + } + + return environment; + } +} diff --git a/dashboard/src/components/api/environment/compose-environment-manager.spec.js b/dashboard/src/components/api/environment/compose-environment-manager.spec.js new file mode 100644 index 00000000000..46308ead747 --- /dev/null +++ b/dashboard/src/components/api/environment/compose-environment-manager.spec.js @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2015-2016 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + */ +'use strict'; + +import {ComposeEnvironmentManager} from './compose-environment-manager'; + +/** + * Test the environment manager for compose based recipes + * @author Oleksii Kurinnyi + */ + +describe('ComposeEnvironmentManager', () => { + let envManager; + + describe('regardless of recipe location or content', () => { + let environment, machines; + + beforeEach(inject(function() { + envManager = new ComposeEnvironmentManager(); + + environment = {"machines":{"another-machine":{"attributes":{"memoryLimitBytes":"2147483648"},"servers":{},"agents":[]},"db":{"attributes":{},"servers":{},"agents":[]},"dev-machine":{"attributes":{"memoryLimitBytes":"5368709120"},"servers":{"1024/tcp":{"port":"1024","properties":{},"protocol":"http"},"1025/tcp":{"port":"1025","properties":{},"protocol":"http"}},"agents":["org.eclipse.che.ws-agent","org.eclipse.che.terminal","org.eclipse.che.ssh"]}},"recipe":{"type":"compose","content":"services:\n dev-machine:\n image: codenvy/ubuntu_jdk8\n mem_limit: 2147483648\n depends_on:\n - another-machine\n environment:\n myName: John Doe\n myDog: Rex The Dog\n myCat: fluffy\n another-machine:\n build:\n context: 'https://github.com/eclipse/che'\n dockerfile: dockerfiles/che-dev/Dockerfile\n command:\n - tail\n - '-f'\n - /dev/null\n entrypoint:\n - /bin/bash\n - '-c'\n environment:\n SOME_ENV: development\n SHOW: 'true'\n SESSION_SECRET: null\n expose:\n - '3000'\n - '8000'\n labels:\n com.example.description: Accounting webapp\n com.example.department: Finance\n com.example.label-with-empty-value: ''\n links:\n - 'db:database'\n mem_limit: 2147483648\n db:\n image: redis\n","contentType":"application/x-yaml"}}; + + machines = envManager.getMachines(environment); + })); + + it('should return servers', () => { + let machineName = 'dev-machine', + machine = machines.find((machine) => { + return machine.name === machineName; + }); + let servers = envManager.getServers(machine); + + let expectedServers = environment.machines[machineName].servers; + expect(servers).toEqual(expectedServers); + }); + + it('at least one machine should contain \'ws-agent\'', () => { + let devMachinesList = machines.filter((machine) => { + return envManager.isDev(machine); + }); + + expect(devMachinesList.length).toBeGreaterThan(0); + }); + + }); + + describe('for recipe from content', () => { + let environment, machines, testMachine; + + beforeEach(inject(function() { + envManager = new ComposeEnvironmentManager(); + + environment = {"machines":{"another-machine":{"attributes":{"memoryLimitBytes":"2147483648"},"servers":{},"agents":[]},"db":{"attributes":{},"servers":{},"agents":[]},"dev-machine":{"attributes":{"memoryLimitBytes":"5368709120"},"servers":{"1024/tcp":{"port":"1024","properties":{},"protocol":"http"},"1025/tcp":{"port":"1025","properties":{},"protocol":"http"}},"agents":["org.eclipse.che.ws-agent","org.eclipse.che.terminal","org.eclipse.che.ssh"]}},"recipe":{"type":"compose","content":"services:\n dev-machine:\n image: codenvy/ubuntu_jdk8\n mem_limit: 2147483648\n depends_on:\n - another-machine\n environment:\n myName: John Doe\n myDog: Rex The Dog\n myCat: fluffy\n another-machine:\n build:\n context: 'https://github.com/eclipse/che'\n dockerfile: dockerfiles/che-dev/Dockerfile\n command:\n - tail\n - '-f'\n - /dev/null\n entrypoint:\n - /bin/bash\n - '-c'\n environment:\n SOME_ENV: development\n SHOW: 'true'\n SESSION_SECRET: null\n expose:\n - '3000'\n - '8000'\n labels:\n com.example.description: Accounting webapp\n com.example.department: Finance\n com.example.label-with-empty-value: ''\n links:\n - 'db:database'\n mem_limit: 2147483648\n db:\n image: redis\n","contentType":"application/x-yaml"}}; + + machines = envManager.getMachines(environment); + + let testMachineName = 'dev-machine'; + testMachine = machines.find((machine) => { + return machine.name === testMachineName; + }); + })); + + it('should be allowed rename machine', () => { + let canRenameMachine = envManager.canRenameMachine(testMachine); + + expect(canRenameMachine).toBe(true); + }); + + it('should be allowed delete machine', () => { + let canDeleteMachine = envManager.canDeleteMachine(testMachine); + + expect(canDeleteMachine).toBe(true); + }); + + it('should be allowed edit environment variables', () => { + let canEditEnvVariables = envManager.canEditEnvVariables(testMachine); + + expect(canEditEnvVariables).toBe(true); + }); + + it('should return source', () => { + let source = envManager.getSource(testMachine); + + let expectedSource = {image: 'codenvy/ubuntu_jdk8'}; + expect(source).toEqual(expectedSource); + }); + + it('from machine\'s attributes', () => { + let memoryLimit = envManager.getMemoryLimit(testMachine); + + // machine's attributes are more preferable than recipe to get memory limit + let expectedMemoryLimit = environment.machines[testMachine.name].attributes.memoryLimitBytes; + expect(memoryLimit).toEqual(expectedMemoryLimit); + }); + + it('from recipe', () => { + let someMachineName = 'dev-machine'; + delete environment.machines[someMachineName].attributes.memoryLimitBytes; + + let machines = envManager.getMachines(environment), + someMachine = machines.find((machine) => { + return machine.name = someMachineName; + }); + + let memoryLimit = envManager.getMemoryLimit(someMachine); + + // if there is no 'memoryLimitBytes' in attributes then mem_limit is returned + delete environment.machines[someMachineName].attributes.memoryLimitBytes; + expect(memoryLimit).toEqual(2147483648); + }); + + }); + + describe('for recipe from location', () => { + let environment, machines; + + beforeEach(inject(function() { + envManager = new ComposeEnvironmentManager(); + + environment = {"machines":{"dev-machine":{"servers":{},"agents":["org.eclipse.che.ws-agent","org.eclipse.che.terminal","org.eclipse.che.ssh"],"attributes":{"memoryLimitBytes":"2147483648"}}},"recipe":{"contentType":"text/x-dockerfile","location":"https://gist.githubusercontent.com/garagatyi/14c3d1587a4c5b630d789f85340426c7/raw/8db09677766b82ec8b034698a046f8fdf53ebcb1/script","type":"dockerfile"}}; + + machines = envManager.getMachines(environment); + })); + + it('shouldn\'t be allowed rename machine', () => { + let canRenameMachine = envManager.canRenameMachine(machines[0]); + + expect(canRenameMachine).toBe(false); + }); + + it('shouldn\'t be allowed edit environment variables', () => { + let canDeleteMachine = envManager.canDeleteMachine(machines[0]); + + expect(canDeleteMachine).toBe(false); + }); + + it('shouldn\'t be allowed delete machine', () => { + let canEditEnvVariables = envManager.canDeleteMachine(machines[0]); + + expect(canEditEnvVariables).toBe(false); + }); + + it('shouldn\'t return any source', () => { + let source = envManager.getSource(machines[0]); + + expect(source).toEqual(null); + }); + + it('should return memory limit from machine\'s attributes', () => { + let memoryLimit = envManager.getMemoryLimit(machines[0]); + + let expectedMemoryLimit = environment.machines['dev-machine'].attributes.memoryLimitBytes; + expect(memoryLimit).toEqual(expectedMemoryLimit); + }); + + }); + +}); + diff --git a/dashboard/src/components/api/environment/docker-file-environment-manager.js b/dashboard/src/components/api/environment/docker-file-environment-manager.js new file mode 100644 index 00000000000..8b1ee7ed704 --- /dev/null +++ b/dashboard/src/components/api/environment/docker-file-environment-manager.js @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2015-2016 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + */ +'use strict'; + +import {EnvironmentManager} from './environment-manager'; +import {DockerfileParser} from './docker-file-parser'; + +/** + * This is the implementation of environment manager that handles the docker file format of environment. + * + * Format sample and specific description: + * + * FROM ubuntu + * RUN mkdir /var/www + * ADD app.js /var/www/app.js + * CMD ["/usr/bin/node", "/var/www/app.js"] + * + * + * The recipe type is dockerfile. This environment can contain only one machine. + * Machine is described both in recipe (content or location to recipe) and in machines attribute of the environment (machine configs). + * The machine configs contain memoryLimitBytes in attributes, servers and agent. + * Environment variables can be set only in recipe content. + * + * @author Ann Shumilova + */ +export class DockerFileEnvironmentManager extends EnvironmentManager { + + constructor($log) { + super(); + + this.$log = $log; + + this.ENV_INSTRUCTION = 'ENV'; + + this.parser = new DockerfileParser(); + } + + get editorMode() { + return 'text/x-dockerfile'; + } + + /** + * Parses a dockerfile and returns an array of objects + * + * @param content {string} content of dockerfile + * @returns {Array} a list of instructions and arguments + * @private + */ + _parseRecipe(content) { + let recipe = []; + try { + recipe = this.parser.parse(content); + } catch (e) { + this.$log.error(e); + } + return recipe; + } + + /** + * Dumps a list of instructions and arguments into dockerfile + * + * @param instructions {array} array of objects + * @returns {string} dockerfile + * @private + */ + _stringifyRecipe(instructions) { + let content = ''; + + try { + content = this.parser.dump(instructions); + } catch (e) { + this.$log.log(e); + } + + return content; + } + + /** + * Provides the environment configuration based on machines format. + * + * @param environment origin of the environment to be edited + * @param machines the list of machines + * @returns environment's configuration + */ + getEnvironment(environment, machines) { + let newEnvironment = super.getEnvironment(environment, machines); + + // machines should contain one machine only + if (machines[0].recipe) { + try { + newEnvironment.recipe.content = this._stringifyRecipe(machines[0].recipe); + } catch (e) { + this.$log.error('Cannot retrieve environment\'s recipe, error: ', e); + } + } + + return newEnvironment; + } + + /** + * Retrieves the list of machines. + * + * @param environment environment's configuration + * @returns {Array} list of machines defined in environment + */ + getMachines(environment) { + let recipe = null, + machines = []; + + if (environment.recipe.content) { + recipe = this._parseRecipe(environment.recipe.content); + } + + Object.keys(environment.machines).forEach((machineName) => { + let machine = angular.copy(environment.machines[machineName]); + machine.name = machineName; + machine.recipe = recipe; + + machines.push(machine); + }); + + return machines; + } + + /** + * Returns a docker image from the recipe. + * + * @param machine + * @returns {*} + */ + getSource(machine) { + if (!machine.recipe) { + return null; + } + + let from = machine.recipe.find((line) => { + return line.instruction === 'FROM'; + }); + + return {image: from.argument}; + } + + /** + * Returns true if environment recipe content is present. + * + * @param machine {object} + * @returns {boolean} + */ + canEditEnvVariables(machine) { + return !!machine.recipe; + } + + /** + * Returns environment variables from recipe + * + * @param machine {object} + * @returns {*} + */ + getEnvVariables(machine) { + if (!machine.recipe) { + return null; + } + + let envVariables = {}; + + let envList = machine.recipe.filter((line) => { + return line.instruction === this.ENV_INSTRUCTION; + }) || []; + + envList.forEach((line) => { + envVariables[line.argument[0]] = line.argument[1]; + }); + + return envVariables; + } + + /** + * Updates machine with new environment variables. + * + * @param machine {object} + * @param envVariables {object} + */ + setEnvVariables(machine, envVariables) { + if (!machine.recipe) { + return; + } + + let newRecipe = []; + + // new recipe without any 'ENV' instruction + newRecipe = machine.recipe.filter((line) => { + return line.instruction !== this.ENV_INSTRUCTION; + }); + + // add environments if any + if (Object.keys(envVariables).length) { + Object.keys(envVariables).forEach((name) => { + let line = { + instruction: this.ENV_INSTRUCTION, + argument: [name, envVariables[name]] + }; + newRecipe.splice(1,0,line); + }) + } + + machine.recipe = newRecipe; + } +} diff --git a/dashboard/src/components/api/environment/docker-file-environment-manager.spec.js b/dashboard/src/components/api/environment/docker-file-environment-manager.spec.js new file mode 100644 index 00000000000..7a5a848f2c9 --- /dev/null +++ b/dashboard/src/components/api/environment/docker-file-environment-manager.spec.js @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2015-2016 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + */ +'use strict'; + +import {DockerFileEnvironmentManager} from './docker-file-environment-manager'; + +/** + * Test the environment manager for docker file based recipes + * @author Oleksii Kurinnyi + */ + +describe('If recipe has content', () => { + let envManager, environment, machines; + + beforeEach(() => { + envManager = new DockerFileEnvironmentManager(); + + environment = { + "machines": { + "dev-machine": { + "attributes": {"memoryLimitBytes": "2147483648"}, + "servers": {}, + "agents": ["org.eclipse.che.ws-agent", "org.eclipse.che.terminal", "org.eclipse.che.ssh"] + } + }, + "recipe": { + "type": "dockerfile", + "content": "FROM codenvy/ubuntu_jdk8\nENV myName=\"John Doe\" myDog=Rex\\ The\\ Dog \\\n myCat=fluffy", + "contentType": "text/x-dockerfile" + } + }; + + machines = envManager.getMachines(environment); + }); + + describe('DockerFileEnvironmentManager', () => { + + it('cannot rename machine', () => { + let canRenameMachine = envManager.canRenameMachine(machines[0]); + + expect(canRenameMachine).toBe(false); + }); + + it('cannot delete machine', () => { + let canDeleteMachine = envManager.canDeleteMachine(machines[0]); + + expect(canDeleteMachine).toBe(false); + }); + + it('can edit environment variables', () => { + let canEditEnvVariables = envManager.canEditEnvVariables(machines[0]); + + expect(canEditEnvVariables).toBe(true); + }); + + it('should return servers', () => { + let servers = envManager.getServers(machines[0]); + + let expectedServers = environment.machines['dev-machine'].servers; + expect(servers).toEqual(expectedServers); + }); + + it('should return memory limit', () => { + let memoryLimit = envManager.getMemoryLimit(machines[0]); + + let expectedMemoryLimit = environment.machines['dev-machine'].attributes.memoryLimitBytes; + expect(memoryLimit).toEqual(expectedMemoryLimit); + }); + + it('should return source', () => { + let source = envManager.getSource(machines[0]); + + let expectedSource = {image: 'codenvy/ubuntu_jdk8'}; + expect(source).toEqual(expectedSource); + }); + + it('the machine should be a dev machine', () => { + let isDev = envManager.isDev(machines[0]); + + expect(isDev).toBe(true); + }); + + }); + +}); + +describe('If recipe has location', () => { + let envManager, environment, machines; + + beforeEach(() => { + envManager = new DockerFileEnvironmentManager(); + + environment = {"machines":{"dev-machine":{"servers":{},"agents":["org.eclipse.che.ws-agent","org.eclipse.che.terminal","org.eclipse.che.ssh"],"attributes":{"memoryLimitBytes":"2147483648"}}},"recipe":{"contentType":"text/x-dockerfile","location":"https://gist.githubusercontent.com/garagatyi/14c3d1587a4c5b630d789f85340426c7/raw/8db09677766b82ec8b034698a046f8fdf53ebcb1/script","type":"dockerfile"}}; + + machines = envManager.getMachines(environment); + }); + + describe('DockerFileEnvironmentManager', () => { + + it('cannot rename machine', () => { + let canRenameMachine = envManager.canRenameMachine(machines[0]); + + expect(canRenameMachine).toBe(false); + }); + + it('cannot delete machine', () => { + let canDeleteMachine = envManager.canDeleteMachine(machines[0]); + + expect(canDeleteMachine).toBe(false); + }); + + it('cannot edit environment variables', () => { + let canEditEnvVariables = envManager.canEditEnvVariables(machines[0]); + + expect(canEditEnvVariables).toBe(false); + }); + + it('should return servers', () => { + let servers = envManager.getServers(machines[0]); + + let expectedServers = environment.machines['dev-machine'].servers; + expect(servers).toEqual(expectedServers); + }); + + it('should return memory limit', () => { + let memoryLimit = envManager.getMemoryLimit(machines[0]); + + let expectedMemoryLimit = environment.machines['dev-machine'].attributes.memoryLimitBytes; + expect(memoryLimit).toEqual(expectedMemoryLimit); + }); + + it('cannot return the source', () => { + let source = envManager.getSource(machines[0]); + + expect(source).toEqual(null); + }); + + it('the machine should be a dev machine', () => { + let isDev = envManager.isDev(machines[0]); + + expect(isDev).toBe(true); + }); + + }); + +}); + diff --git a/dashboard/src/components/api/environment/docker-file-parser.js b/dashboard/src/components/api/environment/docker-file-parser.js new file mode 100644 index 00000000000..73fb0db933e --- /dev/null +++ b/dashboard/src/components/api/environment/docker-file-parser.js @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2015-2016 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + */ +'use strict'; + +/** + * Simple parser and simple dumper of dockerfiles. + * @author Oleksii Kurinnyi + */ +export class DockerfileParser { + + constructor() { + this.backslashLineBreakRE = /\\\r?\n(\s+)?/; + this.lineBreakRE = /\r?\n/; + this.instructionRE = /(\w+)\s+?(.+)/; + this.envVariablesRE = /(?:^|(?:\s+))([^\s=]+?)=([^=]+?)(?:(?=\s+\w+=)|$)/g; + // | | | | + // | | | \- start of next variable name or end of line + // | | \- variable value + // | \- variable name + // \- start of line or spaces before variable name + this.quotesRE = /^["]|["]$/g; + this.backslashSpaceRE = /\\\s/g; + } + + /** + * Parses a dockerfile into array of pairs of instructions and arguments + * + * @param content + * @returns {Array} + */ + parse(content) { + // join multiline instructions + content = this._joinMultilineInstructions(content); + + // split dockerfile into separate instruction lines + let instructionLines = content.split(this.lineBreakRE); + + // split instruction line into instruction and argument + let instructions = []; + instructionLines.forEach((line) => { + let m = line.match(this.instructionRE); + if (m) { + let instruction = m[1], + argument = m[2]; + + // parse argument + let results = this._parseArgument(instruction, argument); + + results.forEach((result) => { + instructions.push(result); + }); + } + }); + + return instructions; + } + + /** + * Remove line breaks from lines which end with backslash + * + * @param content {string} + * @returns {*} + * @private + */ + _joinMultilineInstructions(content) { + return content.replace(this.backslashLineBreakRE, ''); + } + + /** + * Parses an argument string depending on instruction + * + * @param instruction {string} + * @param argumentStr {string} + * @returns {Array} + * @private + */ + _parseArgument(instruction, argumentStr) { + let results = []; + + switch (instruction) { + case 'ENV': + if (argumentStr.includes('=')) { + // this argument string contains one or more environment variables + let match; + while(match = this.envVariablesRE.exec(argumentStr)) { + let name = match[1], + value = match[2]; + if (this.quotesRE.test(value)) { + value = value.replace(this.quotesRE, ''); + } + if (this.backslashSpaceRE.test(value)) { + value = value.replace(this.backslashSpaceRE, ' '); + } + + results.push({ + instruction: instruction, + argument: [name, value] + }); + } + } else { + // this argument string contains only one environment variable + let firstSpaceIndex = argumentStr.indexOf(' '); + results.push({ + instruction: instruction, + argument: [argumentStr.slice(0, firstSpaceIndex), argumentStr.slice(firstSpaceIndex+1)] + }); + } + break; + default: + results.push({ + instruction: instruction, + argument: argumentStr + }); + } + + return results; + } + + /** + * Dumps an array into a dockerfile + * + * @param instructions {array} + * @returns {string} + */ + dump(instructions) { + let content = ''; + + instructions.forEach((line) => { + content += line.instruction + ' ' + this._stringifyArgument(line.instruction, line.argument) + '\n'; + }); + + return content; + } + + /** + * Dumps argument object depending on instruction. + * + * @param instruction {string} + * @param argument {*|string} + * @returns {string} + * @private + */ + _stringifyArgument(instruction, argument) { + switch (instruction) { + case 'ENV': + return argument.join(' '); + default: + return argument; + } + } +} diff --git a/dashboard/src/components/api/environment/docker-file-parser.spec.js b/dashboard/src/components/api/environment/docker-file-parser.spec.js new file mode 100644 index 00000000000..164dd75b63c --- /dev/null +++ b/dashboard/src/components/api/environment/docker-file-parser.spec.js @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2015-2016 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + */ +'use strict'; + +import {DockerfileParser} from './docker-file-parser'; + +/** + * Test the simple dokerfile parser and dumper + * @author Oleksii Kurinnyi + */ + +describe('Simper dockerfile parser', () => { + let parser; + + beforeEach(() => { + parser = new DockerfileParser(); + }); + + describe('method _parseArgument()', () => { + it('should parse ENV argument with single environment variable', () => { + let instruction = 'ENV', + argument = 'name environment variable value'; + + let result = parser._parseArgument(instruction, argument); + + let expectedResult = [{ + instruction:'ENV', + argument: ['name', 'environment variable value'] + }]; + expect(result).toEqual(expectedResult); + }); + + it('should parse ENV argument with several environment variables', () => { + let instruction = 'ENV', + argument = 'myName="John Doe" myDog=Rex\ The\ Dog myCat=fluffy'; + + let result = parser._parseArgument(instruction, argument); + + let expectedResult = [{ + instruction:'ENV', + argument: ['myName', 'John Doe'] + },{ + instruction:'ENV', + argument: ['myDog', 'Rex The Dog'] + }, { + instruction:'ENV', + argument: ['myCat', 'fluffy'] + }]; + expect(result).toEqual(expectedResult); + }); + }); + + it('should parse a dockerfile', () => { + let dockerfile = 'FROM codenvy/ubuntu_jdk8ENV' + +'\nENV myCat fluffy' + +'\nENV myDog Rex The Dog' + +'\nENV myName John Doe'; + + let result = parser.parse(dockerfile); + + let expectedResult = [{ + instruction:'FROM', + argument: 'codenvy/ubuntu_jdk8ENV' + },{ + instruction:'ENV', + argument: ['myCat', 'fluffy'] + },{ + instruction:'ENV', + argument: ['myDog', 'Rex The Dog'] + },{ + instruction:'ENV', + argument: ['myName', 'John Doe'] + }]; + expect(result).toEqual(expectedResult); + }); + + it('should stringify an object into a dockerfile', () => { + let instructions = [{ + instruction:'FROM', + argument: 'codenvy/ubuntu_jdk8ENV' + },{ + instruction:'ENV', + argument: ['myCat', 'fluffy'] + },{ + instruction:'ENV', + argument: ['myDog', 'Rex The Dog'] + },{ + instruction:'ENV', + argument: ['myName', 'John Doe'] + }]; + + let result = parser.dump(instructions); + + let expectedResult = 'FROM codenvy/ubuntu_jdk8ENV' + +'\nENV myCat fluffy' + +'\nENV myDog Rex The Dog' + +'\nENV myName John Doe'; + expect(result.trim()).toEqual(expectedResult); + }); + +}); + diff --git a/dashboard/src/components/api/environment/docker-image-environment-manager.js b/dashboard/src/components/api/environment/docker-image-environment-manager.js new file mode 100644 index 00000000000..fbfab0fdee2 --- /dev/null +++ b/dashboard/src/components/api/environment/docker-image-environment-manager.js @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2015-2016 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + */ +'use strict'; + +import {EnvironmentManager} from './environment-manager'; + +/** + * This is the implementation of environment manager that handles the docker image format of environment. + * + * Format sample and specific description: + * + * condevy/ubuntu_jdk8 + * + * + * The recipe type is dockerimage. This environment can contain only one machine. + * Machine is described by image and in machines attribute of the environment (machine configs). + * The machine configs contain memoryLimitBytes in attributes, servers and agent. + * Environment variables can not be set. + * + * @author Ann Shumilova + */ +export class DockerImageEnvironmentManager extends EnvironmentManager { + + constructor() { + super(); + } + + /** + * Retrieves the list of machines. + * + * @param environment environment's configuration + * @returns {Array} list of machines defined in environment + */ + getMachines(environment) { + let machines = []; + + Object.keys(environment.machines).forEach((machineName) => { + let machine = angular.copy(environment.machines[machineName]); + machine.name = machineName; + machine.recipe = environment.recipe; + + machines.push(machine); + }); + + return machines; + } + + /** + * Provides the environment configuration based on machines format. + * + * @param environment origin of the environment to be edited + * @param machines the list of machines + * @returns environment's configuration + */ + getEnvironment(environment, machines) { + return super.getEnvironment(environment, machines); + } + + /** + * Returns a dockerimage. + * + * @param machine {object} + * @returns {{image: string}} + */ + getSource(machine) { + return {image: machine.recipe.location}; + }} diff --git a/dashboard/src/components/api/environment/docker-image-environment-manager.spec.js b/dashboard/src/components/api/environment/docker-image-environment-manager.spec.js new file mode 100644 index 00000000000..01d97a9adb3 --- /dev/null +++ b/dashboard/src/components/api/environment/docker-image-environment-manager.spec.js @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2015-2016 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + */ +'use strict'; + +import {DockerImageEnvironmentManager} from './docker-image-environment-manager'; + +/** + * Test the environment manager for docker image based recipes + * @author Oleksii Kurinnyi + */ + +describe('DockerImageEnvironmentManager', () => { + let envManager, environment, machines; + + beforeEach(() => { + envManager = new DockerImageEnvironmentManager(); + + environment = {'machines':{'dev-machine':{'servers':{'10240/tcp':{'properties':{},'protocol':'http','port':'10240'}},'agents':['ws-agent','org.eclipse.che.ws-agent'],'attributes':{'memoryLimitBytes':'16642998272'}}},'recipe':{'location':'codenvy/ubuntu_jdk8','type':'dockerimage'}}; + + machines = envManager.getMachines(environment); + }); + + it('cannot rename machine', () => { + let canRenameMachine = envManager.canRenameMachine(machines[0]); + + expect(canRenameMachine).toBe(false); + }); + + it('cannot delete machine', () => { + let canDeleteMachine = envManager.canDeleteMachine(machines[0]); + + expect(canDeleteMachine).toBe(false); + }); + + it('cannot edit environment variables', () => { + let canEditEnvVariables = envManager.canEditEnvVariables(machines[0]); + + expect(canEditEnvVariables).toBe(false); + }); + + it('should return source', () => { + let source = envManager.getSource(machines[0]); + + let expectedSource = {image: environment.recipe.location}; + expect(source).toEqual(expectedSource); + }); + + it('should return servers', () => { + let servers = envManager.getServers(machines[0]); + + let expectedServers = environment.machines['dev-machine'].servers; + expect(servers).toEqual(expectedServers); + }); + + it('should return memory limit', () => { + let memoryLimit = envManager.getMemoryLimit(machines[0]); + + let expectedMemoryLimit = environment.machines['dev-machine'].attributes.memoryLimitBytes; + expect(memoryLimit).toEqual(expectedMemoryLimit); + }); + + it('the machine should be a dev machine', () => { + let isDev = envManager.isDev(machines[0]); + + expect(isDev).toBe(true); + }) +}); + diff --git a/dashboard/src/components/api/environment/environment-manager.js b/dashboard/src/components/api/environment/environment-manager.js new file mode 100644 index 00000000000..e404f42bd58 --- /dev/null +++ b/dashboard/src/components/api/environment/environment-manager.js @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2015-2016 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + */ +'use strict'; + +/** + * This is base class, which describes the environment manager. + * It's aim is to handle machines retrieval and editing, based on the type of environment. + */ +export class EnvironmentManager { + + constructor() { + this.WS_AGENT_NAME = 'org.eclipse.che.ws-agent'; + } + + canRenameMachine() { + return false; + } + + canDeleteMachine() { + return false; + } + + canEditEnvVariables() { + return false; + } + + /** + * Retrieves the list of machines. + * + * @returns {Array} list of machines defined in environment + */ + getMachines() { + return []; + } + + /** + * Provides the environment configuration based on machines format. + * + * @param environment origin of the environment to be edited + * @param machines the list of machines + * @returns environment's configuration + */ + getEnvironment(environment, machines) { + let newEnvironment = angular.copy(environment); + + machines.forEach((machine) => { + let machineName = machine.name; + + if (angular.isUndefined(newEnvironment.machines[machineName])) { + newEnvironment.machines[machineName] = {'attributes': {}}; + } + newEnvironment.machines[machineName].attributes.memoryLimitBytes = machine.attributes.memoryLimitBytes; + newEnvironment.machines[machineName].agents = angular.copy(machine.agents); + newEnvironment.machines[machineName].servers = angular.copy(machine.servers); + }); + + return newEnvironment; + } + + /** + * Returns whether machine is developer or not. + * + * @param machine + * @returns {boolean} + */ + isDev(machine) { + return machine.agents && machine.agents.includes(this.WS_AGENT_NAME); + } + + /** + * Set machine as developer one - contains 'ws-agent' agent. + * + * @param machine machine to edit + * @param isDev defined whether machine is developer or not + */ + setDev(machine, isDev) { + let hasWsAgent = this.isDev(machine); + if (isDev && !hasWsAgent) { + machine.agents = machine.agents ? machine.agents : []; + machine.agents.push(this.WS_AGENT_NAME); + return; + } + + if (!isDev && hasWsAgent) { + machine.agents.splice(machine.agents.indexOf(this.WS_AGENT_NAME), 1); + } + } + + getServers(machine) { + return machine.servers || {}; + } + + setServers(machine, servers) { + machine.servers = angular.copy(servers); + } + + /** + * Returns memory limit from machine's attributes + * + * @param machine + * @returns {*} memory limit in bytes + */ + getMemoryLimit(machine) { + if (machine && machine.attributes && machine.attributes.memoryLimitBytes) { + return machine.attributes.memoryLimitBytes; + } + + return -1; + } + + /** + * Sets the memory limit of the pointed machine. + * Value in attributes has the highest priority, + * + * @param machine machine to change memory limit + * @param limit memory limit + */ + setMemoryLimit(machine, limit) { + machine.attributes = machine.attributes ? machine.attributes : {}; + machine.attributes.memoryLimitBytes = limit; + } + + getEnvVariables() { + return null; + } +} diff --git a/dashboard/src/components/validator/custom-validator.directive.js b/dashboard/src/components/validator/custom-validator.directive.js new file mode 100644 index 00000000000..cb9e9176939 --- /dev/null +++ b/dashboard/src/components/validator/custom-validator.directive.js @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2015-2016 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + */ +'use strict'; + +/** + * Defines a directive for custom validation + * @author Oleksii Kurinnyi + */ +export class CustomValidator { + + /** + * Default constructor that is using resource + * @ngInject for Dependency injection + */ + constructor () { + + this.restrict = 'A'; + this.require = 'ngModel'; + } + + link($scope, element, attrs, ctrl) { + // validate only input element + if ('input' === element[0].localName) { + + let $testScope = $scope.$parent ? $scope.$parent : $scope; + + ctrl.$validators.customValidator = (modelValue) => { + return $testScope.$eval(attrs.customValidator, {$value: modelValue}); + } + } + } +} + diff --git a/dashboard/src/components/validator/custom-validator.spec.js b/dashboard/src/components/validator/custom-validator.spec.js new file mode 100644 index 00000000000..a89d8260d0a --- /dev/null +++ b/dashboard/src/components/validator/custom-validator.spec.js @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2015-2016 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + */ +'use strict'; + +/** + * Test the custom validation directive + * @author Oleksii Kurinnyi + */ + +describe('custom-validator', () => { + let $scope, form, $compile; + + /** + * Backend for handling http operations + */ + let httpBackend; + + beforeEach(angular.mock.module('userDashboard')); + + beforeEach(inject((_$compile_, $rootScope, cheHttpBackend) => { + $scope = $rootScope; + $compile = _$compile_; + + httpBackend = cheHttpBackend.getHttpBackend(); + httpBackend.whenGET(/.*/).respond(200, ''); + + $scope.validateFn = (value) => { + return value % 2 === 0; + }; + })); + + it('should make form invalid if value isn\'t valid', () => { + let nonValidValue = 5; + $scope.model = {value: ''}; + + let element = angular.element( + '
' + + '' + + '
' + ); + $compile(element)($scope); + + form = $scope.form; + form.value.$setViewValue(nonValidValue); + + // check form (expect invalid) + expect(form.value.$invalid).toBe(true); + expect(form.value.$valid).toBe(false); + }); + + it('should leave form valid for valid value', () => { + let newValidValue = 10; + $scope.model = {value: ''}; + + let element = angular.element( + '
' + + '' + + '
' + ); + $compile(element)($scope); + + form = $scope.form; + form.value.$setViewValue(newValidValue); + + // check form (expect valid) + expect(form.value.$invalid).toBe(false); + expect(form.value.$valid).toBe(true); + }); +}); diff --git a/dashboard/src/components/validator/validator-config.js b/dashboard/src/components/validator/validator-config.js index bfe40991a75..42082edee81 100644 --- a/dashboard/src/components/validator/validator-config.js +++ b/dashboard/src/components/validator/validator-config.js @@ -13,6 +13,7 @@ import {GitUrlValidator} from './git-url-validator.directive'; import {UniqueProjectNameValidator} from './unique-project-name-validator.directive'; import {UniqueWorkspaceNameValidator} from './unique-workspace-name-validator.directive'; +import {CustomValidator} from './custom-validator.directive'; import {UniqueStackNameValidator} from './unique-stack-name-validator.directive'; @@ -23,6 +24,7 @@ export class ValidatorConfig { register.directive('gitUrl', GitUrlValidator) .directive('uniqueProjectName', UniqueProjectNameValidator) .directive('uniqueWorkspaceName', UniqueWorkspaceNameValidator) + .directive('customValidator', CustomValidator) .directive('uniqueStackName', UniqueStackNameValidator); } } diff --git a/dashboard/src/components/widget/input/che-input.directive.js b/dashboard/src/components/widget/input/che-input.directive.js index ac2212024e6..108df52faba 100644 --- a/dashboard/src/components/widget/input/che-input.directive.js +++ b/dashboard/src/components/widget/input/che-input.directive.js @@ -79,14 +79,14 @@ export class CheInput { if (attrs.chePattern) { template = template + ' pattern="' + pattern + '"'; } - template = template + ' data-ng-model="valueModel">'; + template = template + ' data-ng-model="valueModel">' + + ''; if (attrs.cheWidth === 'auto') { template = template + '
{{valueModel ? valueModel : placeHolder}}
'; } - template = template + '' - + '' + template = template + '' + '
' + '
' + '' diff --git a/dashboard/src/components/widget/input/che-input.styl b/dashboard/src/components/widget/input/che-input.styl index 0756d4e2d59..58a044989dd 100644 --- a/dashboard/src/components/widget/input/che-input.styl +++ b/dashboard/src/components/widget/input/che-input.styl @@ -43,12 +43,17 @@ label.che-input-desktop-label position relative max-width 100% + textarea + min-width 200px + padding-right 30px + input min-width 200px padding-right 30px overflow hidden text-overflow ellipsis white-space nowrap + background-color inherit .che-input-desktop-hidden-text min-width 170px @@ -67,11 +72,11 @@ label.che-input-desktop-label top 13px input[disabled], input[disabled] + .che-input-icon - background-color inherit color $disabled-color input[readonly] box-shadow none + background-color inherit - & + .che-input-icon - display none + input[readonly] + .che-input-icon + display none diff --git a/dashboard/src/components/widget/input/che-textarea.directive.js b/dashboard/src/components/widget/input/che-textarea.directive.js new file mode 100644 index 00000000000..da0e52bbe99 --- /dev/null +++ b/dashboard/src/components/widget/input/che-textarea.directive.js @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2015-2016 Codenvy, S.A. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Codenvy, S.A. - initial API and implementation + */ +'use strict'; + +import { CheInput } from './che-input.directive'; + +/** + * Defines a directive for creating textarea that are working either on desktop or on mobile devices. + * It will change upon width of the screen + * @author Oleksii Kurinnyi + */ +export class CheTextarea extends CheInput { + + /** + * Default constructor that is using resource + * @ngInject for Dependency injection + */ + constructor() { + super(); + } + + /** + * Template for the current toolbar + * @param element + * @param attrs + * @returns {string} the template + */ + template(element, attrs) { + + var textareaName = attrs.cheName; + var labelName = attrs.cheLabelName || ''; + var placeHolder = attrs.chePlaceHolder; + var pattern = attrs.chePattern; + + var template = '
' + + '' + + '' + + '' + + '' + + '' + + '
' + + '
' + + '' + + '
' + + '
' + + '' + + '' + + '
' + + '' + + '' + + '' + + '
' + + '
' + + '
' + + '
' + + '
'; + + return template; + } + +} diff --git a/dashboard/src/components/widget/widget-config.js b/dashboard/src/components/widget/widget-config.js index 5e1a4b791b3..847698c1b01 100644 --- a/dashboard/src/components/widget/widget-config.js +++ b/dashboard/src/components/widget/widget-config.js @@ -29,6 +29,7 @@ import {CheFrame} from './frame/che-frame.directive'; import {CheFooter} from './footer/che-footer.directive'; import {CheHtmlSource} from './html-source/che-html-source.directive'; import {CheInput} from './input/che-input.directive'; +import {CheTextarea} from './input/che-textarea.directive'; import {CheLabel} from './label/che-label.directive'; import {CheLabelContainer} from './label-container/che-label-container.directive'; import {CheLearnMoreCtrl} from './learn-more/che-learn-more.controller'; @@ -90,6 +91,7 @@ export class WidgetConfig { .directive('cheFooter', CheFooter) .directive('cheHtmlSource', CheHtmlSource) .directive('cheInput', CheInput) + .directive('cheTextarea', CheTextarea) .directive('cheLabel', CheLabel) .directive('cheLabelContainer', CheLabelContainer)