summaryrefslogtreecommitdiffstats
path: root/catalog-ui/app/scripts/view-models
diff options
context:
space:
mode:
authorMichael Lando <ml636r@att.com>2017-02-19 10:28:42 +0200
committerMichael Lando <ml636r@att.com>2017-02-19 10:51:01 +0200
commit451a3400b76511393c62a444f588a4ed15f4a549 (patch)
treee4f5873a863d1d3e55618eab48b83262f874719d /catalog-ui/app/scripts/view-models
parent5abfe4e1fb5fae4bbd5fbc340519f52075aff3ff (diff)
Initial OpenECOMP SDC commit
Change-Id: I0924d5a6ae9cdc161ae17c68d3689a30d10f407b Signed-off-by: Michael Lando <ml636r@att.com>
Diffstat (limited to 'catalog-ui/app/scripts/view-models')
-rw-r--r--catalog-ui/app/scripts/view-models/admin-dashboard/add-category-modal/add-category-modal-view-model.ts94
-rw-r--r--catalog-ui/app/scripts/view-models/admin-dashboard/add-category-modal/add-category-modal-view.html41
-rw-r--r--catalog-ui/app/scripts/view-models/admin-dashboard/add-category-modal/add-category-modal-view.less3
-rw-r--r--catalog-ui/app/scripts/view-models/admin-dashboard/admin-dashboard-view-model.ts82
-rw-r--r--catalog-ui/app/scripts/view-models/admin-dashboard/admin-dashboard-view.html24
-rw-r--r--catalog-ui/app/scripts/view-models/admin-dashboard/admin-dashboard.less49
-rw-r--r--catalog-ui/app/scripts/view-models/admin-dashboard/category-management/category-management-view-model.ts197
-rw-r--r--catalog-ui/app/scripts/view-models/admin-dashboard/category-management/category-management-view.html53
-rw-r--r--catalog-ui/app/scripts/view-models/admin-dashboard/category-management/category-management.less118
-rw-r--r--catalog-ui/app/scripts/view-models/admin-dashboard/ecomp/ecomp-view.html1
-rw-r--r--catalog-ui/app/scripts/view-models/admin-dashboard/user-management/user-management-view-model.ts220
-rw-r--r--catalog-ui/app/scripts/view-models/admin-dashboard/user-management/user-management-view.html102
-rw-r--r--catalog-ui/app/scripts/view-models/admin-dashboard/user-management/user-management.less251
-rw-r--r--catalog-ui/app/scripts/view-models/catalog/catalog-view-model.ts312
-rw-r--r--catalog-ui/app/scripts/view-models/catalog/catalog-view-tests.ts309
-rw-r--r--catalog-ui/app/scripts/view-models/catalog/catalog-view.html190
-rw-r--r--catalog-ui/app/scripts/view-models/catalog/catalog.less304
-rw-r--r--catalog-ui/app/scripts/view-models/component-viewer/activity-log/activity-log-view.html16
-rw-r--r--catalog-ui/app/scripts/view-models/component-viewer/activity-log/activity-log-view.less18
-rw-r--r--catalog-ui/app/scripts/view-models/component-viewer/component-viewer-view-model.ts211
-rw-r--r--catalog-ui/app/scripts/view-models/component-viewer/component-viewer.html55
-rw-r--r--catalog-ui/app/scripts/view-models/component-viewer/component-viewer.less148
-rw-r--r--catalog-ui/app/scripts/view-models/component-viewer/properties/product-properties-view.html76
-rw-r--r--catalog-ui/app/scripts/view-models/component-viewer/properties/properties-view.less128
-rw-r--r--catalog-ui/app/scripts/view-models/component-viewer/properties/resource-properties-view.html169
-rw-r--r--catalog-ui/app/scripts/view-models/component-viewer/properties/service-properties-view.html167
-rw-r--r--catalog-ui/app/scripts/view-models/dashboard/cover/dashboard-cover-view-model.ts91
-rw-r--r--catalog-ui/app/scripts/view-models/dashboard/cover/dashboard-cover-view.html1
-rw-r--r--catalog-ui/app/scripts/view-models/dashboard/dashboard-view-model-tests.ts276
-rw-r--r--catalog-ui/app/scripts/view-models/dashboard/dashboard-view-model.ts415
-rw-r--r--catalog-ui/app/scripts/view-models/dashboard/dashboard-view.html106
-rw-r--r--catalog-ui/app/scripts/view-models/dashboard/dashboard.less420
-rw-r--r--catalog-ui/app/scripts/view-models/forms/artifact-form/artifact-form-view-model.ts354
-rw-r--r--catalog-ui/app/scripts/view-models/forms/artifact-form/artifact-form-view.html169
-rw-r--r--catalog-ui/app/scripts/view-models/forms/artifact-form/artifact-form.less44
-rw-r--r--catalog-ui/app/scripts/view-models/forms/attribute-form/attribute-form-view.html153
-rw-r--r--catalog-ui/app/scripts/view-models/forms/attribute-form/attribute-from-view-model.ts255
-rw-r--r--catalog-ui/app/scripts/view-models/forms/env-parameters-form/env-parameters-form.html39
-rw-r--r--catalog-ui/app/scripts/view-models/forms/env-parameters-form/env-parameters-form.less74
-rw-r--r--catalog-ui/app/scripts/view-models/forms/env-parameters-form/env-parameters-form.ts149
-rw-r--r--catalog-ui/app/scripts/view-models/forms/property-form/property-form-view-model.ts330
-rw-r--r--catalog-ui/app/scripts/view-models/forms/property-form/property-form-view.html219
-rw-r--r--catalog-ui/app/scripts/view-models/forms/property-form/property-form.less63
-rw-r--r--catalog-ui/app/scripts/view-models/forms/resource-instance-name-form/resource-instance-name-model.ts105
-rw-r--r--catalog-ui/app/scripts/view-models/forms/resource-instance-name-form/resource-instance-name-view.html72
-rw-r--r--catalog-ui/app/scripts/view-models/forms/resource-instance-name-form/resource-instance-name.less29
-rw-r--r--catalog-ui/app/scripts/view-models/modals/confirmation-modal/confirmation-modal-view-model.ts96
-rw-r--r--catalog-ui/app/scripts/view-models/modals/confirmation-modal/confirmation-modal-view.html29
-rw-r--r--catalog-ui/app/scripts/view-models/modals/confirmation-modal/confirmation-modal.less30
-rw-r--r--catalog-ui/app/scripts/view-models/modals/email-modal/email-modal-view-model.ts117
-rw-r--r--catalog-ui/app/scripts/view-models/modals/email-modal/email-modal-view.html79
-rw-r--r--catalog-ui/app/scripts/view-models/modals/email-modal/email-modal.less56
-rw-r--r--catalog-ui/app/scripts/view-models/modals/error-modal/error-403-view.html4
-rw-r--r--catalog-ui/app/scripts/view-models/modals/error-modal/error-view-model.ts43
-rw-r--r--catalog-ui/app/scripts/view-models/modals/error-modal/error.less13
-rw-r--r--catalog-ui/app/scripts/view-models/modals/message-modal/message-base-modal-model.ts64
-rw-r--r--catalog-ui/app/scripts/view-models/modals/message-modal/message-client-modal/client-message-modal-view-model.ts43
-rw-r--r--catalog-ui/app/scripts/view-models/modals/message-modal/message-client-modal/client-message-modal-view.html16
-rw-r--r--catalog-ui/app/scripts/view-models/modals/message-modal/message-client-modal/client-message-modal.less0
-rw-r--r--catalog-ui/app/scripts/view-models/modals/message-modal/message-server-modal/server-message-modal-view-model.ts45
-rw-r--r--catalog-ui/app/scripts/view-models/modals/message-modal/message-server-modal/server-message-modal-view.html17
-rw-r--r--catalog-ui/app/scripts/view-models/modals/message-modal/message-server-modal/server-message-modal.less0
-rw-r--r--catalog-ui/app/scripts/view-models/modals/onboarding-modal/onboarding-modal-view-model.ts249
-rw-r--r--catalog-ui/app/scripts/view-models/modals/onboarding-modal/onboarding-modal-view.html142
-rw-r--r--catalog-ui/app/scripts/view-models/modals/onboarding-modal/onboarding-modal.less148
-rw-r--r--catalog-ui/app/scripts/view-models/onboard-vendor/onboard-vendor-view-model.ts148
-rw-r--r--catalog-ui/app/scripts/view-models/onboard-vendor/onboard-vendor-view.html14
-rw-r--r--catalog-ui/app/scripts/view-models/onboard-vendor/onboard-vendor.less303
-rw-r--r--catalog-ui/app/scripts/view-models/preloading/preloading-view.html9
-rw-r--r--catalog-ui/app/scripts/view-models/preloading/preloading-view.less107
-rw-r--r--catalog-ui/app/scripts/view-models/preloading/preloading-view.ts47
-rw-r--r--catalog-ui/app/scripts/view-models/support/support-view-model.ts38
-rw-r--r--catalog-ui/app/scripts/view-models/support/support-view.html33
-rw-r--r--catalog-ui/app/scripts/view-models/support/support.less8
-rw-r--r--catalog-ui/app/scripts/view-models/tabs/general-tab.less131
-rw-r--r--catalog-ui/app/scripts/view-models/tabs/hierarchy/hierarchy-view-model.ts99
-rw-r--r--catalog-ui/app/scripts/view-models/tabs/hierarchy/hierarchy-view.html57
-rw-r--r--catalog-ui/app/scripts/view-models/tabs/hierarchy/hierarchy.less71
-rw-r--r--catalog-ui/app/scripts/view-models/tutorial-end/tutorial-end.html10
-rw-r--r--catalog-ui/app/scripts/view-models/tutorial-end/tutorial-end.less41
-rw-r--r--catalog-ui/app/scripts/view-models/tutorial-end/tutorial-end.ts42
-rw-r--r--catalog-ui/app/scripts/view-models/welcome/slide0.html50
-rw-r--r--catalog-ui/app/scripts/view-models/welcome/slide1.html34
-rw-r--r--catalog-ui/app/scripts/view-models/welcome/slide2.html26
-rw-r--r--catalog-ui/app/scripts/view-models/welcome/slide3.html27
-rw-r--r--catalog-ui/app/scripts/view-models/welcome/slide4.html29
-rw-r--r--catalog-ui/app/scripts/view-models/welcome/slide5.html27
-rw-r--r--catalog-ui/app/scripts/view-models/welcome/slide6.html26
-rw-r--r--catalog-ui/app/scripts/view-models/welcome/welcome-steps-controller.ts74
-rw-r--r--catalog-ui/app/scripts/view-models/welcome/welcome-view.html22
-rw-r--r--catalog-ui/app/scripts/view-models/welcome/welcome-view.ts267
-rw-r--r--catalog-ui/app/scripts/view-models/wizard/ReadMe.txt54
-rw-r--r--catalog-ui/app/scripts/view-models/wizard/artifact-deployment-step/artifact-deployment-step.html137
-rw-r--r--catalog-ui/app/scripts/view-models/wizard/artifact-deployment-step/artifact-deployment-step.less107
-rw-r--r--catalog-ui/app/scripts/view-models/wizard/artifact-deployment-step/artifact-deployment-step.ts228
-rw-r--r--catalog-ui/app/scripts/view-models/wizard/artifact-form-step/artifact-form-step-view-model.ts304
-rw-r--r--catalog-ui/app/scripts/view-models/wizard/artifact-form-step/artifact-form-step-view.html147
-rw-r--r--catalog-ui/app/scripts/view-models/wizard/artifact-form-step/artifact-form-step.less45
-rw-r--r--catalog-ui/app/scripts/view-models/wizard/artifact-information-step/artifact-information-step.html48
-rw-r--r--catalog-ui/app/scripts/view-models/wizard/artifact-information-step/artifact-information-step.less50
-rw-r--r--catalog-ui/app/scripts/view-models/wizard/artifact-information-step/artifact-information-step.ts168
-rw-r--r--catalog-ui/app/scripts/view-models/wizard/general-step/general-step.html270
-rw-r--r--catalog-ui/app/scripts/view-models/wizard/general-step/general-step.less34
-rw-r--r--catalog-ui/app/scripts/view-models/wizard/general-step/general-step.ts381
-rw-r--r--catalog-ui/app/scripts/view-models/wizard/hierarchy-step/hierarchy-step.html40
-rw-r--r--catalog-ui/app/scripts/view-models/wizard/hierarchy-step/hierarchy-step.less125
-rw-r--r--catalog-ui/app/scripts/view-models/wizard/hierarchy-step/hierarchy-step.ts149
-rw-r--r--catalog-ui/app/scripts/view-models/wizard/icons-step/icons-step.html26
-rw-r--r--catalog-ui/app/scripts/view-models/wizard/icons-step/icons-step.less55
-rw-r--r--catalog-ui/app/scripts/view-models/wizard/icons-step/icons-step.ts150
-rw-r--r--catalog-ui/app/scripts/view-models/wizard/properties-step/properties-step.html57
-rw-r--r--catalog-ui/app/scripts/view-models/wizard/properties-step/properties-step.less55
-rw-r--r--catalog-ui/app/scripts/view-models/wizard/properties-step/properties-step.ts123
-rw-r--r--catalog-ui/app/scripts/view-models/wizard/property-form/property-form-view-model-tests.ts163
-rw-r--r--catalog-ui/app/scripts/view-models/wizard/property-form/property-form-view-model.ts250
-rw-r--r--catalog-ui/app/scripts/view-models/wizard/property-form/property-form.html133
-rw-r--r--catalog-ui/app/scripts/view-models/wizard/property-form/property-form.less7
-rw-r--r--catalog-ui/app/scripts/view-models/wizard/wizard-creation-base.html12
-rw-r--r--catalog-ui/app/scripts/view-models/wizard/wizard-creation-base.less60
-rw-r--r--catalog-ui/app/scripts/view-models/wizard/wizard-creation-base.ts399
-rw-r--r--catalog-ui/app/scripts/view-models/wizard/wizard-state/create-wizard.ts114
-rw-r--r--catalog-ui/app/scripts/view-models/wizard/wizard-state/edit-wizard.ts164
-rw-r--r--catalog-ui/app/scripts/view-models/wizard/wizard-state/import-wizard.ts64
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/activity-log/activity-log.html85
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/activity-log/activity-log.less83
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/activity-log/activity-log.ts122
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/attributes/attributes-view-model.ts107
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/attributes/attributes-view.html52
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/attributes/attributes.less54
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/composition/composition-view-model.ts232
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/composition/composition-view.html99
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/composition/composition.less864
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/artifacts/artifacts-view-model.ts255
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/artifacts/artifacts-view.html55
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/artifacts/artifacts.less172
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/details/details-view-model.ts132
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/details/details-view.html129
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/details/details.less68
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/properties-and-attributes/properties-view-model.ts228
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/properties-and-attributes/properties-view.html81
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/properties-and-attributes/properties.less16
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/relations/relations-view-model.ts81
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/relations/relations-view.html57
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/relations/relations.less116
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/structure/structure-view.html13
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/structure/structure-view.ts34
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/deployment-artifacts/deployment-artifacts-view-model.ts253
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/deployment-artifacts/deployment-artifacts-view.html149
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/deployment-artifacts/deployment-artifacts.less102
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/deployment/deployment-view-model.ts127
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/deployment/deployment-view.html10
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/deployment/deployment.less33
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/distribution/disribution-status-modal/disribution-status-modal-view-model.ts67
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/distribution/disribution-status-modal/disribution-status-modal-view.html126
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/distribution/disribution-status-modal/disribution-status-modal.less33
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/distribution/distribution-view-model.ts135
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/distribution/distribution-view.html171
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/distribution/distribution.less361
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/general/general-view-model.ts379
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/general/general-view.html307
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/general/general.less64
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/icons/icons-view-model.ts131
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/icons/icons-view.html26
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/icons/icons.less65
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/information-artifacts/information-artifacts-view-model.ts150
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/information-artifacts/information-artifacts-view.html57
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/information-artifacts/information-artifacts.less47
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/inputs/inputs.less286
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/inputs/resource-input/resource-inputs-view-model.ts145
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/inputs/resource-input/resource-inputs-view.html136
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/inputs/resource-input/resource-inputs.less9
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/inputs/service-input/service-inputs-view-model.ts246
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/inputs/service-input/service-inputs-view.html205
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/inputs/service-input/service-inputs.less54
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/management-workflow/management-workflow-view-model.ts128
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/management-workflow/management-workflow-view.html3
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/network-call-flow/network-call-flow-view-model.ts80
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/network-call-flow/network-call-flow-view.html3
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/product-hierarchy/product-hierarchy-view-model.ts134
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/product-hierarchy/product-hierarchy-view.html40
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/product-hierarchy/product-hierarchy.less130
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/properties/properties-view-model.ts114
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/properties/properties-view.html62
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/properties/properties.less115
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities-view-model.ts165
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities-view.html144
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities.less196
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/tosca-artifacts/tosca-artifacts-view-model.ts87
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/tosca-artifacts/tosca-artifacts-view.html45
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/tosca-artifacts/tosca-artifacts.less74
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/workspace-view-model.ts703
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/workspace-view.html86
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/workspace.less144
193 files changed, 23214 insertions, 0 deletions
diff --git a/catalog-ui/app/scripts/view-models/admin-dashboard/add-category-modal/add-category-modal-view-model.ts b/catalog-ui/app/scripts/view-models/admin-dashboard/add-category-modal/add-category-modal-view-model.ts
new file mode 100644
index 0000000000..93c1dac174
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/admin-dashboard/add-category-modal/add-category-modal-view-model.ts
@@ -0,0 +1,94 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../../references"/>
+module Sdc.ViewModels {
+ 'use strict';
+
+ interface IAddCategoryModalViewModelScope extends ng.IScope {
+ category:Sdc.Services.ICategoryResource;
+ modelType:string;
+ footerButtons: Array<any>;
+ forms:any;
+
+ save():void;
+ close():void;
+ }
+
+ export class AddCategoryModalViewModel {
+
+ static '$inject' = [
+ '$scope',
+ 'Sdc.Services.CategoryResourceService',
+ '$modalInstance',
+ 'parentCategory',
+ 'type'
+ ];
+
+ constructor(
+ private $scope:IAddCategoryModalViewModelScope,
+ private categoryResourceService:Sdc.Services.ICategoryResourceClass,
+ private $modalInstance:ng.ui.bootstrap.IModalServiceInstance,
+ private parentCategory:Sdc.Services.ICategoryResource,
+ private type:string
+ ){
+ this.initScope();
+ }
+
+ private initScope = ():void => {
+ this.$scope.forms = {};
+ this.$scope.modelType = this.parentCategory ? 'sub category' : 'category';
+ this.$scope.category = new this.categoryResourceService();
+
+ this.$scope.close = ():void => {
+ this.$modalInstance.dismiss();
+ };
+
+ this.$scope.save = ():void => {
+
+ let onOk = (newCategory :Sdc.Services.ICategoryResource):void => {
+ this.$modalInstance.close(newCategory);
+ };
+
+ let onCancel = ():void => {
+ //error
+ };
+
+ if(!this.parentCategory) {
+ this.$scope.category.$save({types: this.type+"s"}, onOk, onCancel);
+ }else{
+ this.$scope.category.$saveSubCategory({types: this.type+"s", categoryId: this.parentCategory.uniqueId}, onOk, onCancel);
+ }
+
+ };
+
+ this.$scope.footerButtons = [
+ {'name': 'OK', 'css': 'blue', 'callback': this.$scope.save, 'disabled': true},
+ {'name': 'Cancel', 'css': 'grey', 'callback': this.$scope.close}
+ ];
+
+ this.$scope.$watch("forms.editForm.$invalid", (newVal, oldVal) => {
+ this.$scope.footerButtons[0].disabled = this.$scope.forms.editForm.$invalid;
+ });
+
+ }
+
+
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/admin-dashboard/add-category-modal/add-category-modal-view.html b/catalog-ui/app/scripts/view-models/admin-dashboard/add-category-modal/add-category-modal-view.html
new file mode 100644
index 0000000000..5718982661
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/admin-dashboard/add-category-modal/add-category-modal-view.html
@@ -0,0 +1,41 @@
+<sdc-modal modal="modalInstance"
+ type="classic"
+ class="i-sdc-admin-add-category-modal modal-type-confirmation"
+ header-translate="CREATE_CATEGORY_MODAL_HEADER"
+ buttons="footerButtons"
+ header-translate-values="{'modelType': '{{modelType}}' }"
+ show-close-button="true"
+ hide-background="false"
+>
+
+ <form novalidate class="w-sdc-form" name="forms.editForm">
+
+ <div class="w-sdc-form-column">
+ <div class="i-sdc-form-item"
+ data-ng-class="{error:(editForm.categoryName.$dirty && editForm.categoryName.$invalid)}">
+ <label class="i-sdc-form-label required" translate="CREATE_CATEGORY_MODAL_CATEGORY_NAME"
+ translate-values="{'modelType': '{{modelType}}' }"></label>
+ <input class="i-sdc-form-input"
+ data-ng-model="category.name"
+ data-ng-model-options="{ debounce: 200 }"
+ type="text"
+ name="categoryName"
+ required="required"
+ data-ng-minlength="4"
+ data-ng-pattern="namePattern"
+ maxlength="25"
+ autofocus />
+
+ <div class="input-error" data-ng-show="editForm.categoryName.$dirty && editForm.categoryName.$invalid">
+ <span ng-show="editForm.categoryName.$error.required" translate="CREATE_CATEGORY_MODAL_REQUIRED" translate-values="{'modelType': '{{modelType}}' }"></span>
+ <span ng-show="editForm.categoryName.$error.minlength" translate="CREATE_CATEGORY_MODAL_MINLENGTH" translate-values="{'minlength': '4', 'modelType': '{{modelType}}' }"></span>
+ <span ng-show="editForm.categoryName.$error.pattern" translate="CREATE_CATEGORY_MODAL_PATTERN" translate-values="{'modelType': '{{modelType}}' }"></span>
+ </div>
+
+ </div>
+
+ </div>
+
+ </form>
+
+</sdc-modal>
diff --git a/catalog-ui/app/scripts/view-models/admin-dashboard/add-category-modal/add-category-modal-view.less b/catalog-ui/app/scripts/view-models/admin-dashboard/add-category-modal/add-category-modal-view.less
new file mode 100644
index 0000000000..39d84aab23
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/admin-dashboard/add-category-modal/add-category-modal-view.less
@@ -0,0 +1,3 @@
+.i-sdc-admin-add-category-modal {
+
+}
diff --git a/catalog-ui/app/scripts/view-models/admin-dashboard/admin-dashboard-view-model.ts b/catalog-ui/app/scripts/view-models/admin-dashboard/admin-dashboard-view-model.ts
new file mode 100644
index 0000000000..d7cbbcc68d
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/admin-dashboard/admin-dashboard-view-model.ts
@@ -0,0 +1,82 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../references"/>
+module Sdc.ViewModels {
+
+ 'use strict';
+
+ interface IAdminDashboardViewModelScope extends ng.IScope {
+ version:string;
+ sdcConfig:Models.IAppConfigurtaion;
+ isLoading: boolean;
+ currentTab: string;
+ templateUrl:string;
+ monitorUrl:string;
+ moveToTab(tab:string):void;
+ isSelected(tab:string):boolean;
+ }
+
+
+ export class AdminDashboardViewModel {
+ static '$inject' = [
+ '$scope',
+ 'Sdc.Services.CacheService',
+ 'sdcConfig'
+ ];
+
+ constructor(private $scope:IAdminDashboardViewModelScope,
+ private cacheService:Services.CacheService,
+ private sdcConfig:Models.IAppConfigurtaion) {
+
+ this.initScope();
+ }
+
+
+ private initScope = ():void => {
+
+ this.$scope.version = this.cacheService.get('version');
+ this.$scope.sdcConfig = this.sdcConfig;
+ this.$scope.monitorUrl = this.$scope.sdcConfig.api.kibana;
+ this.$scope.isSelected=(tab:string):boolean => {
+ return tab===this.$scope.currentTab;
+ }
+
+ this.$scope.moveToTab=(tab:string):void => {
+ if (tab===this.$scope.currentTab){
+ return;
+ }
+ else if(tab === 'USER_MANAGEMENT'){
+ this.$scope.templateUrl = '/app/scripts/view-models/admin-dashboard/user-management/user-management-view.html';
+ }
+ else if(tab ==='CATEGORY_MANAGEMENT'){
+ this.$scope.templateUrl = '/app/scripts/view-models/admin-dashboard/category-management/category-management-view.html';
+ }
+ /* else if(tab ==='ECOMP'){
+ this.$scope.templateUrl = '/app/scripts/view-models/admin-dashboard/ecomp/ecomp-view.html';
+ }*/
+ this.$scope.currentTab = tab;
+ };
+
+ this.$scope.moveToTab('USER_MANAGEMENT');
+
+
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/admin-dashboard/admin-dashboard-view.html b/catalog-ui/app/scripts/view-models/admin-dashboard/admin-dashboard-view.html
new file mode 100644
index 0000000000..063525a4bf
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/admin-dashboard/admin-dashboard-view.html
@@ -0,0 +1,24 @@
+<div class="sdc-admin-container">
+
+ <!--<ecomp-header menu-data="menuItems" version="{{version}}" clickable-logo="false"></ecomp-header>-->
+
+ <nav class="sdc-admin-top-bar-menu">
+ <button class="sdc-admin-top-bar-menu-tab" data-tests-id="usermanagmenttab"
+ data-ng-class="{'selected': isSelected('USER_MANAGEMENT')}"
+ data-ng-click="moveToTab('USER_MANAGEMENT')"
+ translate="USER_MANAGEMENT">
+ </button>
+ <button class="sdc-admin-top-bar-menu-tab" data-tests-id="categorymanagmenttab"
+ data-ng-class="{'selected': isSelected('CATEGORY_MANAGEMENT')}"
+ data-ng-click="moveToTab('CATEGORY_MANAGEMENT')"
+ translate="CATEGORY_MANAGEMENT">
+ </button>
+ <a href={{monitorUrl}} target="_blank" ng-show="monitorUrl!=''" >
+ <button class="sdc-admin-top-bar-menu-monitor-btn" translate="MONITOR" data-tests-id="monitor"></button>
+ </a>
+ </nav>
+
+ <div class="sdc-admin-body">
+ <ng-include src="templateUrl" ng-if="true"></ng-include>
+ </div>
+</div>
diff --git a/catalog-ui/app/scripts/view-models/admin-dashboard/admin-dashboard.less b/catalog-ui/app/scripts/view-models/admin-dashboard/admin-dashboard.less
new file mode 100644
index 0000000000..874a02c431
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/admin-dashboard/admin-dashboard.less
@@ -0,0 +1,49 @@
+.sdc-admin-container{
+ height: 100%;
+
+ .sdc-admin-top-bar-menu{
+ .bg_k;
+ height: @top_nav_admin_height;
+ padding-left:260px;
+ .box-shadow(-1px 0px 3px 0px rgba(0, 0, 0, 0.33));
+ position: absolute;
+ top: @header_height;
+ left: 0;
+ right: 0;
+ z-index: 2;
+
+ .sdc-admin-top-bar-menu-tab{
+ .b_17;
+ .hand;
+ height: 44px;
+ background-color: transparent;
+ position: relative;
+ padding: 0px 10px 0px 10px;
+ border: none;
+ outline: none;
+ margin-right: 15px;
+ &.selected {
+ outline: none;
+ border-bottom: solid 4px @color_t;
+ }
+ }
+ .sdc-admin-top-bar-menu-monitor-btn{
+ .bg_a;
+ .c_6;
+ float: right;
+ border: none;
+ position: relative;
+ padding: 11px 24px;
+ }
+ }
+
+ .sdc-admin-body{
+ .bg_n;
+ padding: 40px 260px 60px 260px;
+ position: absolute;
+ top: @top_nav_admin_height;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/admin-dashboard/category-management/category-management-view-model.ts b/catalog-ui/app/scripts/view-models/admin-dashboard/category-management/category-management-view-model.ts
new file mode 100644
index 0000000000..a3ad7a2714
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/admin-dashboard/category-management/category-management-view-model.ts
@@ -0,0 +1,197 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../../references"/>
+module Sdc.ViewModels {
+ 'use strict';
+
+ interface ICategoryManagementViewModelScope extends ng.IScope {
+ SERVICE:string;
+ RESOURCE:string;
+ categoriesToShow: Array<Sdc.Services.ICategoryResource>;
+ serviceCategories: Array<Sdc.Services.ICategoryResource>;
+ resourceCategories: Array<Sdc.Services.ICategoryResource>;
+ selectedCategory: Sdc.Services.ICategoryResource;
+ selectedSubCategory: Sdc.Services.ICategoryResource;
+ modalInstance:ng.ui.bootstrap.IModalServiceInstance;
+ isLoading:boolean;
+ type:string;
+ namePattern:RegExp;
+
+ selectCategory(category:Sdc.Services.ICategoryResource) :void;
+ selectSubCategory(subcategory:Sdc.Services.ICategoryResource) :void;
+ selectType(type:string) :void;
+ deleteCategory(category:Sdc.Services.ICategoryResource, subCategory:Sdc.Services.ICategoryResource) :void;
+ createCategoryModal(parentCategory:Sdc.Services.ICategoryResource) :void;
+ }
+
+ export class CategoryManagementViewModel {
+ static '$inject' = [
+ '$scope',
+ 'sdcConfig',
+ 'Sdc.Services.CacheService',
+ '$templateCache',
+ '$modal',
+ '$filter',
+ 'ValidationUtils',
+ 'ModalsHandler'
+ ];
+
+ constructor(private $scope:ICategoryManagementViewModelScope,
+ private sdcConfig:Models.IAppConfigurtaion,
+ private cacheService:Services.CacheService,
+ private $templateCache:ng.ITemplateCacheService,
+ private $modal:ng.ui.bootstrap.IModalService,
+ private $filter:ng.IFilterService,
+ private ValidationUtils: Sdc.Utils.ValidationUtils,
+ private ModalsHandler: Utils.ModalsHandler
+ ) {
+
+ this.initScope();
+ this.$scope.selectType(Sdc.Utils.Constants.ComponentType.SERVICE.toLocaleLowerCase());
+
+ }
+
+ private initScope = ():void => {
+ let scope:ICategoryManagementViewModelScope = this.$scope;
+ scope.SERVICE = Sdc.Utils.Constants.ComponentType.SERVICE.toLocaleLowerCase();
+ scope.RESOURCE = Sdc.Utils.Constants.ComponentType.RESOURCE.toLocaleLowerCase();
+
+ scope.namePattern = this.ValidationUtils.getValidationPattern('cssClasses');
+
+ scope.selectCategory = (category :Sdc.Services.ICategoryResource) => {
+ if(scope.selectedCategory !== category) {
+ scope.selectedSubCategory = null;
+ }
+ scope.selectedCategory = category;
+ };
+ scope.selectSubCategory = (subcategory :Sdc.Services.ICategoryResource) => {
+ scope.selectedSubCategory = subcategory;
+ };
+ scope.selectType = (type:string):void => {
+ if (scope.type !== type) {
+ scope.selectedCategory = null;
+ scope.selectedSubCategory = null;
+ }
+
+ scope.type = type;
+ scope.categoriesToShow = scope[type + 'Categories'];
+ };
+
+ scope.createCategoryModal = (parentCategory:Sdc.Services.ICategoryResource):void => {
+ //can't create a sub category for service
+ if(parentCategory && scope.type === Sdc.Utils.Constants.ComponentType.SERVICE.toLowerCase()) {
+ return;
+ }
+
+ let type:string = scope.type;
+
+ let onOk = (newCategory :Sdc.Services.ICategoryResource):void => {
+ if(!parentCategory) {
+ scope[type + 'Categories'].push(newCategory);
+ }else{
+ if(!parentCategory.subcategories) {
+ parentCategory.subcategories = [];
+ }
+ parentCategory.subcategories.push(newCategory);
+ }
+ };
+
+ let onCancel = ():void => {
+
+ };
+
+ let modalOptions:ng.ui.bootstrap.IModalSettings = {
+ template: this.$templateCache.get('/app/scripts/view-models/admin-dashboard/add-category-modal/add-category-modal-view.html'),
+ controller: 'Sdc.ViewModels.AddCategoryModalViewModel',
+ size: 'sdc-xsm',
+ backdrop: 'static',
+ scope: scope,
+ resolve: {
+ parentCategory: function () {
+ return parentCategory;
+ },
+ type: function () {
+ return type;
+ }
+ }
+ };
+
+ scope.modalInstance = this.$modal.open(modalOptions);
+ scope.modalInstance.result.then(onOk, onCancel);
+
+ };
+
+ scope.deleteCategory = (category: Sdc.Services.ICategoryResource, subCategory: Sdc.Services.ICategoryResource): void => {
+
+ let onOk = ():void => {
+
+ scope.isLoading = true;
+ let type:string = scope.type;
+
+ let onError = (response):void => {
+ scope.isLoading = false;
+ console.info('onFaild', response);
+ };
+
+ let onSuccess = (response: any) :void => {
+ let arr:Array<Sdc.Services.ICategoryResource>;
+
+ if(!subCategory) {
+ arr = this.$scope[type + 'Categories'];
+ arr.splice(arr.indexOf(category), 1);
+ if(category === scope.selectedCategory) {
+ scope.selectedCategory = null;
+ scope.selectedSubCategory = null;
+ }
+ } else {
+ arr = category.subcategories;
+ arr.splice(arr.indexOf(subCategory), 1);
+ }
+
+ scope.isLoading = false;
+ };
+
+ if(!subCategory) {
+ category.$delete({
+ types: type+"s",
+ categoryId: category.uniqueId
+ }
+ , onSuccess, onError);
+ } else {
+ category.$deleteSubCategory({
+ types: type+"s",
+ categoryId: category.uniqueId,
+ subCategoryId: subCategory.uniqueId,
+ }
+ , onSuccess, onError);
+ }
+ };
+ let modelType:string = subCategory ? 'sub category' : 'cssClasses';
+ let title:string = this.$filter('translate')("DELETE_CATEGORY_MODAL_HEADER", "{'modelType': '" + modelType +"' }");
+ let message:string = this.$filter('translate')("DELETE_CATEGORY_MODAL_CATEGORY_NAME", "{'modelType': '" + modelType +"' }");
+
+ this.ModalsHandler.openConfirmationModal(title, message, false, 'sdc-xsm').then(onOk);
+ };
+
+ this.$scope.serviceCategories = this.cacheService.get('serviceCategories');
+ this.$scope.resourceCategories = this.cacheService.get('resourceCategories');
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/admin-dashboard/category-management/category-management-view.html b/catalog-ui/app/scripts/view-models/admin-dashboard/category-management/category-management-view.html
new file mode 100644
index 0000000000..95a002d3d7
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/admin-dashboard/category-management/category-management-view.html
@@ -0,0 +1,53 @@
+<div data-ng-controller="Sdc.ViewModels.CategoryManagementViewModel" class="category-management">
+
+ <loader data-display="isLoading"></loader>
+
+ <div class="row">
+
+ <div class="col-sm-6">
+
+ <h4>
+ <span class="hand selected" data-ng-click="selectType(SERVICE)" data-tests-id="servicecategoryheader"
+ data-ng-class="{'selected': type === SERVICE}" translate="SERVICE_CATEGORY_HEADER"></span>
+ <span class="hand" data-ng-click="selectType(RESOURCE)" data-tests-id="resourcecategoryheader"
+ data-ng-class="{'selected': type === RESOURCE}" translate="RESOURCE_CATEGORY_HEADER"></span>
+ </h4>
+ <span data-ng-click="createCategoryModal(null)" translate="ADD_CATEGORY" data-tests-id="newcategory"></span>
+
+ <perfect-scrollbar class="perfect-scrollbar">
+ <ul>
+ <li data-ng-repeat="category in categoriesToShow"
+ data-ng-class="{'selected': selectedCategory === category, 'gray': selectedSubCategory}"
+ data-ng-click="selectCategory(category)"
+ data-tests-id="{{ type === SERVICE ? 'servicecategory' : 'resourcecategory' }}">
+ {{category.name}}
+
+ <!--<button class="sprite e-sdc-small-icons-delete" data-ng-click="deleteCategory(category, null)" type="button"></button>-->
+ <!--button class="sprite e-sdc-small-icons-pad" data-ng-click="" type="button"></button-->
+ </li>
+ </ul>
+ </perfect-scrollbar>
+ </div>
+
+ <div class="col-sm-6">
+
+ <h4><span translate="SUBCATEGORY_HEADER" data-tests-id="subcategoryheader"></span></h4>
+ <span data-ng-if="type === RESOURCE && selectedCategory" data-ng-click="selectedCategory ? createCategoryModal(selectedCategory) : ''" translate="ADD_SUBCATEGORY" data-tests-id="newsubcategory"></span>
+
+ <perfect-scrollbar class="perfect-scrollbar">
+ <ul>
+ <li data-ng-repeat="subcategory in selectedCategory.subcategories"
+ data-ng-class="{'selected': selectedSubCategory === subcategory}"
+ data-ng-click="selectSubCategory(subcategory)"
+ data-tests-id="subcategory">
+ {{subcategory.name}}
+
+ <!--<button class="sprite e-sdc-small-icons-delete" data-ng-click="deleteCategory(selectedCategory, subcategory)" type="button"></button>-->
+ <!--button class="sprite e-sdc-small-icon-pad" data-ng-click="" type="button"></button-->
+ </li>
+ </ul>
+ </perfect-scrollbar>
+ </div>
+
+ </div>
+</div>
diff --git a/catalog-ui/app/scripts/view-models/admin-dashboard/category-management/category-management.less b/catalog-ui/app/scripts/view-models/admin-dashboard/category-management/category-management.less
new file mode 100644
index 0000000000..011122c9e8
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/admin-dashboard/category-management/category-management.less
@@ -0,0 +1,118 @@
+
+.category-management {
+
+ .row {
+ display: table;
+ width: 70%;
+ min-width: 800px;
+ margin: auto;
+
+ [class*="col-"] {
+ float: none;
+ display: table-cell;
+ vertical-align: top;
+ background-color: white;
+ border: 1px solid #c1c1c1;
+ padding: 0;
+
+ &:not(:last-child) {
+ border-right: none;
+ }
+
+ h4 {
+ float:left;
+ color:white;
+ background-color: rgb(155,168,176);
+ margin: 0;
+ padding: 0px 0px 0px 16px;
+ width: 100%;
+ height: 31px;
+ font-size: 15px;
+
+ span{
+ display: inline-block;
+ line-height: 30px;
+ margin-right: 5px;
+ padding: 0 7px;
+
+ &.selected {
+ border-bottom: 2px #067ab4 solid;
+ }
+ }
+ }
+ h4+span{
+ .hand;
+ float:right;
+ display: inline-block;
+ background-color: rgb(59,124,156);
+ text-align: center;
+ padding: 5.5px 0px;
+ width: 60px;
+ color: white;
+ position: absolute;
+ top: 0;
+ right: 0;
+ z-index: 1;
+ color: white;
+ }
+
+
+ .perfect-scrollbar {
+ width: 100%;
+ height: 500px;
+ margin-top: 40px;
+ margin-bottom: 15px;
+ }
+
+ ul {
+ clear: both;
+ margin: 5px 0 10px 0;
+ list-style-type: none;
+ padding: 0;
+ position: relative;
+
+ li{
+ .hand;
+ padding: 0 8px;
+ text-indent: 9px;
+ font-size: 13px;
+ line-height: 25px;
+ border: 1px solid white;
+ border-right: none;
+ border-left: none;
+ margin-right: 5px;
+
+ button {
+ background-color: transparent;
+ border: none;
+ float: right;
+ margin: 5px 3px;
+ display: none;
+ }
+
+ &:hover {
+ background-color: #d9e6ec;
+ color: #3b7b9b;
+ border-color: #d9e6ec;
+
+ button {
+ display: inline-block;
+ }
+ }
+ &.selected {
+ background-color: rgb(219,230,236);
+ color: #3b7b9b;
+ border-color: rgba(59, 123, 155, 0.42);
+
+ &.gray {
+ background-color: rgba(155, 168, 176, 0.09);
+ border-color: white;
+ }
+ }
+
+ }
+ }
+ }
+ }
+
+}
diff --git a/catalog-ui/app/scripts/view-models/admin-dashboard/ecomp/ecomp-view.html b/catalog-ui/app/scripts/view-models/admin-dashboard/ecomp/ecomp-view.html
new file mode 100644
index 0000000000..7c89b545c5
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/admin-dashboard/ecomp/ecomp-view.html
@@ -0,0 +1 @@
+<div></div>
diff --git a/catalog-ui/app/scripts/view-models/admin-dashboard/user-management/user-management-view-model.ts b/catalog-ui/app/scripts/view-models/admin-dashboard/user-management/user-management-view-model.ts
new file mode 100644
index 0000000000..3921d0cf8f
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/admin-dashboard/user-management/user-management-view-model.ts
@@ -0,0 +1,220 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../../references"/>
+module Sdc.ViewModels {
+
+ import IUserProperties = Sdc.Models.IUserProperties;
+ 'use strict';
+
+ interface IUserManagementViewModelScope extends ng.IScope {
+ sdcConfig:Models.IAppConfigurtaion;
+ usersList: Array<Models.IUserProperties>;
+ isLoading: boolean;
+ isNewUser: boolean;
+ sortBy:string;
+ reverse:boolean;
+ tableHeadersList:any;
+ roles:Array<string>;
+ newUser: Models.IUser;
+ currentUser: Sdc.Services.IUserResource;
+ userIdValidationPattern: RegExp;
+ editForm:ng.IFormController;
+ getAllUsers():void;
+ editUserRole(user:IUserProperties);
+ sort(sortBy:string): void;
+ createUser(): void;
+ deleteUser(userId:string) : void;
+ onEditUserPressed(user:IUserProperties): void;
+ saveUserChanges(user:IUserProperties) :void;
+ getTitle(role:string): string;
+ clearForm():void;
+
+ }
+
+
+ export class UserManagementViewModel {
+ static '$inject' = [
+ '$scope',
+ 'sdcConfig',
+ 'Sdc.Services.UserResourceService',
+ '$templateCache',
+ '$modal',
+ 'UserIdValidationPattern',
+ '$filter',
+ 'ModalsHandler'
+ ];
+
+ constructor(private $scope:IUserManagementViewModelScope,
+ private sdcConfig:Models.IAppConfigurtaion,
+ private userResourceService:Sdc.Services.IUserResourceClass,
+ private $templateCache:ng.ITemplateCacheService,
+ private $modal:ng.ui.bootstrap.IModalService,
+ private UserIdValidationPattern:RegExp,
+ private $filter:ng.IFilterService,
+ private ModalsHandler: Utils.ModalsHandler
+ ) {
+
+ this.initScope();
+
+ }
+
+
+
+ private getAllUsers = ():void => {
+ this.$scope.isLoading = true;
+
+ let onError = (response) => {
+ this.$scope.isLoading = false;
+ console.info('onFaild', response);
+ };
+ let onSuccess = (response: Array<Models.IUserProperties>) => {
+ this.$scope.usersList = response;
+ _.forEach(this.$scope.usersList,(user:any,i:number)=>{
+ user.index = i;
+ });
+ this.$scope.isLoading = false;
+ };
+ this.userResourceService.getAllUsers(onSuccess, onError);
+ };
+
+ private updateUserFilterTerm = (user: IUserProperties): void =>{
+ user.filterTerm = user.firstName + ' ' + user.lastName + ' ' + user.userId + ' ' + user.email + ' ' + user.role + ' ' + this.$filter('date')(user.lastLoginTime, "MM/dd/yyyy");
+ };
+
+ private initScope = ():void => {
+ let self=this;
+
+ this.$scope.tableHeadersList = [
+ {title: "First Name", property: 'firstName'},
+ {title: "Last Name", property: 'lastName'},
+ {title: this.$filter('translate')("USER_MANAGEMENT_TABLE_HEADER_USER_ID"), property: 'userId'},
+ {title: "Email", property: 'email'},
+ {title: "Role", property: 'role'},
+ {title: "Last Active", property: 'lastLoginTime'}
+ ];
+ this.$scope.userIdValidationPattern = this.UserIdValidationPattern;
+ this.$scope.sortBy = 'lastLoginTime';
+ this.$scope.reverse = false;
+ this.$scope.roles = this.sdcConfig.roles;
+ this.$scope.isNewUser = false;
+ this.$scope.currentUser = this.userResourceService.getLoggedinUser();
+ this.getAllUsers();
+
+ let resource : Services.IUserResource = <Services.IUserResource>{};
+ this.$scope.newUser = new Sdc.Models.User(resource);
+
+ this.$scope.sort = (sortBy:string):void => {//default sort by descending last update. default for alphabetical = ascending
+ this.$scope.isNewUser = false;
+ this.$scope.reverse = (this.$scope.sortBy === sortBy) ? ( !this.$scope.reverse) : this.$scope.reverse = false;
+ this.$scope.sortBy = sortBy;
+ };
+
+ this.$scope.createUser = () : void => {
+
+ let onError = (response) => {
+ this.$scope.isLoading = false;
+ console.info('onFaild', response);
+ };
+
+ let onSuccess = (response: Models.IUserProperties) => {
+ this.$scope.newUser.resource['index'] = this.$scope.usersList.length;
+ this.$scope.newUser.resource.lastLoginTime = "0";
+ this.$scope.newUser.resource.status = response.status;
+ this.updateUserFilterTerm(this.$scope.newUser.resource);
+ this.$scope.usersList.unshift(this.$scope.newUser.resource);
+ this.$scope.isNewUser = true;
+ this.$scope.sortBy = 'index';
+ this.$scope.reverse = true;
+ this.$scope.isLoading = false;
+ this.$scope.newUser = new Sdc.Models.User(null);
+ this.$scope.editForm.$setPristine();
+ let _self = this;
+ setTimeout(function () {
+ _self.$scope.isNewUser = false;
+ }, 7000);
+ };
+ this.userResourceService.createUser({ userId: this.$scope.newUser.resource.userId, role: this.$scope.newUser.resource.role}, onSuccess, onError);
+ };
+
+
+ this.$scope.onEditUserPressed = (user:IUserProperties): void => {
+ user.isInEditMode = true;
+ user.tempRole = user.role;
+ };
+
+ this.$scope.editUserRole = (user:IUserProperties): void => {
+ let roleBeforeUpdate: string = user.role;
+ user.role= user.tempRole;
+
+ let onError = (response) => {
+ this.$scope.isLoading = false;
+ user.role = roleBeforeUpdate;
+ console.info('onFaild', response);
+ };
+ let onSuccess = (response: any) => {
+ this.$scope.isLoading = false;
+ user.tempRole = user.role;
+ this.updateUserFilterTerm(user);
+ };
+
+ this.userResourceService.editUserRole({ id: user.userId, role: user.role}, onSuccess, onError);
+ };
+
+ this.$scope.saveUserChanges = (user:IUserProperties): void => {
+ if(user.tempRole != user.role){
+ this.$scope.editUserRole(user)
+ }
+ user.isInEditMode = false;
+ };
+
+ this.$scope.deleteUser = (userId:string): void => {
+
+ let onOk = ():void => {
+ this.$scope.isLoading = true;
+
+ let onError = (response):void => {
+ this.$scope.isLoading = false;
+ console.info('onFaild', response);
+ };
+
+ let onSuccess = (response: any) :void => {
+ _.remove(this.$scope.usersList, {userId: userId });
+ this.$scope.isLoading = false;
+ };
+ this.userResourceService.deleteUser({ id: userId}, onSuccess, onError);
+ };
+
+ let title:string = this.$filter('translate')("USER_MANAGEMENT_VIEW_DELETE_MODAL_TITLE");
+ let message:string = this.$filter('translate')("USER_MANAGEMENT_VIEW_DELETE_MODAL_TEXT");
+ this.ModalsHandler.openConfirmationModal(title, message, false).then(onOk);
+ };
+
+ this.$scope.getTitle = (role:string):string =>{
+ return role.toLowerCase().replace('governor','governance_Rep').replace('_',' ');
+ };
+
+ this.$scope.clearForm =():void =>{
+ if(!this.$scope.editForm['contactId'].$viewValue && !this.$scope.editForm['role'].$viewValue){
+ this.$scope.editForm.$setPristine();
+ }
+ };
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/admin-dashboard/user-management/user-management-view.html b/catalog-ui/app/scripts/view-models/admin-dashboard/user-management/user-management-view.html
new file mode 100644
index 0000000000..26e720b044
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/admin-dashboard/user-management/user-management-view.html
@@ -0,0 +1,102 @@
+<div ng-controller="Sdc.ViewModels.UserManagementViewModel">
+ <loader data-display="isLoading"></loader>
+ <div class="sdc-user-management-top-bar">
+ <div class="sdc-user-management-top-bar-search-container">
+ <label class="sdc-user-management-top-bar-search-text">Search User</label>
+ <input type="text" class="sdc-user-management-top-bar-form-input" placeholder="{{ USER_MANAGEMENT_SEARCH_LABEL | translate }}" data-ng-model="search.filterTerm" ng-model-options="{ debounce: 500 }" data-tests-id="searchbox" />
+ <span class="w-sdc-search-icon" data-ng-class="{'cancel':search.filterTerm, 'magnification':!search.filterTerm}" data-ng-click="search.filterTerm=''" ></span>
+ </div>
+ <div class="vertical-border-container">
+ <div class="vertical-border"></div>
+ </div>
+ <form class="sdc-user-management-top-bar-create-user-container w-sdc-form" name="editForm">
+ <label class="sdc-user-management-top-bar-title">Create New User</label>
+ <div class="sdc-user-management-top-bar-wrapper">
+ <div class="i-sdc-form-item sdc-user-management-top-bar-form-container" data-ng-class="{error:(editForm.contactId.$dirty && editForm.contactId.$invalid)}">
+ <input ng-focus="search.filterTerm=''" type="text"
+ data-ng-model="newUser.resource.userId"
+ class="i-sdc-form-input"
+ placeholder="{{ USER_MANAGEMENT_SEARCH_TEXT | translate}}"
+ data-ng-model-options="{ updateOn: 'default blur', debounce: { 'default': 750, 'blur': 0 } }"
+ name="contactId"
+ data-ng-pattern="userIdValidationPattern"
+ data-ng-change="clearForm()"
+ data-ng-blur="clearForm()"
+ data-required
+ data-tests-id="newuserid" />
+
+ <div class="input-error" data-ng-show="editForm.contactId.$dirty && editForm.contactId.$invalid">
+ <span ng-show="editForm.contactId.$error.required" translate="NEW_USER_ERROR_USER_ID_REQUIRED"></span>
+ <span ng-show="editForm.contactId.$error.pattern" translate="NEW_USER_ERROR_USER_ID_NOT_VALID"></span>
+ </div>
+ </div>
+ <div class="i-sdc-form-item sdc-user-management-top-bar-form-container" data-ng-class="{error:(editForm.role.$dirty && editForm.role.$invalid
+ && editForm.contactId.$viewValue)}">
+ <select class="i-sdc-form-select capitalize"
+ data-required
+ name="role"
+ data-tests-id="selectrole"
+ data-ng-model = "newUser.resource.role"
+ data-ng-options="role as (getTitle(role)) for role in roles | orderBy:'role'"
+ ng-focus="search.filterTerm=''">
+ <option value="">Select Role</option>
+ </select>
+ <div class="input-error" data-ng-show="editForm.role.$dirty && editForm.role.$invalid && editForm.contactId.$viewValue">
+ <span ng-show="editForm.role.$error.required" translate="NEW_USER_ERROR_ROLE_REQUIRED"></span>
+ </div>
+ </div>
+ <button data-tests-id="creategreen" data-ng-disabled="editForm.$invalid" class="sdc-user-management-top-bar-create-btn" ng-click="search.filterTerm = '' ; createUser()">Create</button>
+ </div>
+ </form>
+ </div>
+
+
+ <div class="sdc-user-management-table-container-flex">
+
+ <div class="sdc-user-management-table">
+ <div class="head sdc-user-management-flex-container">
+ <div class="sdc-user-management-table-header head-row hand sdc-user-management-flex-item" data-tests-id="th{{header.title}}" ng-repeat="header in tableHeadersList" ng-click="sort(header.property)">{{header.title}}
+ <span ng-if="sortBy === header.property" class="sdc-user-management-table-header-sort-arrow" data-ng-class="{'down': reverse, 'up':!reverse}"> </span>
+ </div>
+ <div class="sdc-user-management-table-no-text-header head-row sdc-user-management-flex-item"></div>
+ <div class="sdc-user-management-table-no-text-header head-row sdc-user-management-flex-item"></div>
+ </div>
+
+ <div class="body">
+ <perfect-scrollbar scroll-y-margin-offset="0" include-padding="true" class="scrollbar-container">
+ <div ng-init="user.filterTerm = user.firstName + ' ' + user.lastName + ' ' + user.userId + ' ' + user.email + ' ' + user.role + ' ' + (user.lastLoginTime | date: 'MM/dd/yyyy')"
+ ng-repeat="user in usersList | filter: search | orderBy:sortBy:reverse"
+ data-ng-class="{'sdc-user-management-table-new-user-row': (isNewUser && $first), 'sdc-user-management-table-row-edit-mode': user.isInEditMode}"
+ class="sdc-user-management-flex-container data-row" data-tests-id="tdRow">
+
+ <div sdc-smart-tooltip class="sdc-user-management-table-col-general sdc-user-management-flex-item" data-tests-id="td{{tableHeadersList[0].title}}">{{user.firstName || '---'}}</div>
+ <div sdc-smart-tooltip class="sdc-user-management-table-col-general sdc-user-management-flex-item" data-tests-id="td{{tableHeadersList[1].title}}">{{user.lastName || '---' }}</div>
+ <div class="sdc-user-management-table-col-userid sdc-user-management-flex-item" data-tests-id="td{{tableHeadersList[2].title}}">{{user.userId || '---'}}</div>
+ <div sdc-smart-tooltip class="sdc-user-management-table-col-general sdc-user-management-flex-item" data-tests-id="td{{tableHeadersList[3].title}}">{{user.email || '---'}}</div>
+ <div class="sdc-user-management-table-col-general sdc-user-management-flex-item" data-tests-id="td{{tableHeadersList[4].title}}">
+ <div class="sdc-user-management-table-role-select capitalize sdc-user-management-table-role-label"
+ data-ng-if="!user.isInEditMode"
+ data-ng-bind="getTitle(user.role)"></div>
+ <select class="sdc-user-management-table-role-select capitalize"
+ data-tests-id="tdselectrole"
+ data-ng-if="user.isInEditMode"
+ data-ng-model="user.tempRole"
+ data-ng-options="role as (getTitle(role)) for role in roles | orderBy:'role'">
+ </select>
+ </div>
+ <div class="sdc-user-management-table-col-general sdc-user-management-flex-item" data-tests-id="td{{tableHeadersList[5].title}}">{{user.lastLoginTime == 0 ? 'Waiting' : (user.lastLoginTime | date:'MM/dd/yyyy')}}</div>
+ <div class="sdc-user-management-table-btn-col sdc-user-management-flex-item">
+ <button data-ng-disabled="user.isInEditMode" data-ng-hide="user.isInEditMode || currentUser.userId === user.userId" class="sdc-user-management-table-edit-btn" ng-click="onEditUserPressed(user)" data-tests-id="updateuser{{user.userId}}"> </button>
+ <button data-ng-show="user.isInEditMode" class="sdc-user-management-table-save-btn" ng-click="saveUserChanges(user)" data-tests-id="tdsave"> </button>
+ </div>
+ <div class="sdc-user-management-table-btn-col sdc-user-management-flex-item">
+ <button data-ng-hide="currentUser.userId === user.userId" class="sdc-user-management-table-delete-btn" ng-click="deleteUser(user.userId)" data-tests-id="delete{{user.userId}}"> </button>
+ </div>
+
+ </div>
+ </perfect-scrollbar>
+ </div>
+
+ </div>
+ </div>
+</div>
diff --git a/catalog-ui/app/scripts/view-models/admin-dashboard/user-management/user-management.less b/catalog-ui/app/scripts/view-models/admin-dashboard/user-management/user-management.less
new file mode 100644
index 0000000000..934faab9e7
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/admin-dashboard/user-management/user-management.less
@@ -0,0 +1,251 @@
+.sdc-user-management-top-bar {
+ display: flex;
+ width: 100%;
+ label {
+ .i_17;
+ }
+ .sdc-user-management-top-bar-form-input,
+ .sdc-user-management-top-bar-form-select {
+ .b_9;
+ color: @color_b;
+ height: 28px;
+ padding-left: 10px;
+ border-radius: 2px;
+ border: 1px solid @border_color_f;
+ }
+
+ .sdc-user-management-top-bar-search-container {
+ display: flex;
+ flex-direction: column;
+ position: relative;
+ width: 400px;
+
+ label {
+ margin-bottom: 20px;
+ }
+
+ .w-sdc-search-icon {
+ right: 11px;
+ top: 49px;
+ }
+ }
+ .vertical-border-container {
+ min-width: 50px;
+ margin: 0px auto;
+
+ .vertical-border {
+
+ width: 1px;
+ height: 70px;
+ background-color: @color_e;
+ display: table;
+ margin: 0 auto;
+ }
+ }
+
+ .sdc-user-management-top-bar-wrapper {
+ display: flex;
+ }
+
+ .sdc-user-management-top-bar-title {
+ .i_17;
+ font-weight: bold;
+ }
+
+ .sdc-user-management-top-bar-create-user-container {
+
+ display: flex;
+ flex-direction: column;
+ position: relative;
+ float: right;
+ padding-top: 0px;
+ text-align: left;
+ width: 650px;
+
+ label {
+ margin-bottom: 20px;
+ }
+
+ .sdc-user-management-top-bar-form-container {
+ width: 233px;
+ margin-right: 35px;
+ }
+
+ .sdc-user-management-top-bar-create-btn {
+ .w-sdc-btn-light-green;
+ height: 30px;
+ width: 100px;
+ line-height: 0px;
+ padding-bottom: 3px;
+ margin-right: 0px;
+ }
+ }
+}
+
+
+.sdc-user-management-table-container-flex {
+ height: 650px;
+ margin-top: 35px;
+ .sdc-user-management-table {
+ width: 100%;
+ border: 1px solid @color_m;
+ .head {
+ .bg_m;
+ .head-row {
+ .c_18;
+ font-weight: bold;
+
+ border-right: 1px solid @border_color_d;
+
+ .sdc-user-management-table-header-sort-arrow {
+ display: inline-block;
+ background-color: transparent;
+ border: none;
+ .c_9;
+ width: 0;
+ height: 0;
+ float: right;
+ margin: 8px 8px 0px 0px;
+ &.up {
+ border-left: 5px solid transparent;
+ border-right: 5px solid transparent;
+ border-bottom: 5px solid;
+ }
+ &.down {
+ border-left: 5px solid transparent;
+ border-right: 5px solid transparent;
+ border-top: 5px solid;
+ }
+ }
+ }
+ .sdc-user-management-table-header:hover {
+ .bg_j;
+ }
+
+ }
+ .body {
+ .scrollbar-container {
+ max-height: 421px;
+ .perfect-scrollbar;
+ }
+ .b_9;
+
+ .data-row {
+ &:nth-of-type(odd) {
+ .bg_c;
+ }
+ &.sdc-user-management-table-new-user-row {
+
+ animation: change 7s step-end both;
+
+ @keyframes change {
+ from {
+ color: @color_z
+ }
+ to {
+ color: @color_b
+ }
+ }
+ }
+ &.sdc-user-management-table-row-edit-mode {
+ .bg_j;
+ }
+ div {
+
+ border-right: 1px solid @border_color_d;
+
+ &:last-child {
+ border-right: none;
+ }
+
+ .sdc-user-management-table-role-select {
+ background-color: transparent;
+ border: 0;
+ width: 100%;
+
+ }
+ .sdc-user-management-table-role-label {
+ margin-left:4px;
+ }
+
+ }
+
+ }
+ .data-row:hover {
+ .bg_j;
+ }
+
+ }
+
+ .sdc-user-management-table-btn-col {
+
+ line-height: 0px;
+ text-align: center;
+ .sdc-user-management-table-delete-btn {
+ background-color: transparent;
+ border: none;
+ .sprite;
+ .sprite.e-sdc-small-icon-delete;
+ opacity: 0.7;
+ }
+ .sdc-user-management-table-edit-btn {
+ background-color: transparent;
+ border: none;
+ .sprite;
+ .e-sdc-small-icon-pencil;
+ opacity: 0.7;
+ }
+ .sdc-user-management-table-save-btn {
+ background-color: transparent;
+ border: none;
+ .sprite;
+ .sprite.e-sdc-green-save;
+ }
+ }
+
+ }
+
+ .sdc-user-management-flex-container {
+ display: flex;
+ }
+
+ .sdc-user-management-flex-item {
+ width:10px;
+ padding: 5px;
+ text-align: center;
+ }
+
+ .sdc-user-management-flex-item:nth-child(1) {
+ flex-grow: 5;
+ }
+
+ .sdc-user-management-flex-item:nth-child(2) {
+ flex-grow: 7;
+ }
+
+ .sdc-user-management-flex-item:nth-child(3) {
+ flex-grow: 4;
+ }
+
+ .sdc-user-management-flex-item:nth-child(4) {
+ flex-grow: 8;
+ }
+
+ .sdc-user-management-flex-item:nth-child(5) {
+ flex-grow: 8;
+ }
+
+ .sdc-user-management-flex-item:nth-child(6) {
+ flex-grow: 8;
+ }
+
+ .sdc-user-management-flex-item:nth-child(7) {
+ flex-grow: 1;
+ }
+
+ .sdc-user-management-flex-item:nth-child(8) {
+ flex-grow: 1;
+ }
+
+}
+
diff --git a/catalog-ui/app/scripts/view-models/catalog/catalog-view-model.ts b/catalog-ui/app/scripts/view-models/catalog/catalog-view-model.ts
new file mode 100644
index 0000000000..bf37e92e56
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/catalog/catalog-view-model.ts
@@ -0,0 +1,312 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../references"/>
+module Sdc.ViewModels {
+
+ 'use strict';
+
+ interface Checkboxes {
+ componentTypes:Array<string>;
+ resourceSubTypes:Array<string>;
+ }
+
+ interface CheckboxesFilter {
+ // Types
+ selectedComponentTypes:Array<string>;
+ selectedResourceSubTypes:Array<string>;
+ // Categories
+ selectedCategoriesModel:Array<string>;
+ // Statuses
+ selectedStatuses:Array<string>;
+ }
+
+ interface Gui {
+ isLoading: boolean;
+ onResourceSubTypesClick:Function;
+ onComponentTypeClick:Function;
+ onCategoryClick:Function;
+ onSubcategoryClick:Function;
+ onGroupClick:Function;
+ }
+
+ export interface ICatalogViewModelScope extends ng.IScope {
+ checkboxes:Checkboxes;
+ checkboxesFilter:CheckboxesFilter;
+ gui:Gui;
+
+ categories: Array<Models.IMainCategory>;
+ confStatus: Models.IConfigStatuses;
+ sdcMenu:Models.IAppMenu;
+ catalogFilterdItems: Array<Models.Components.Component>;
+ expandedSection: Array<string>;
+ actionStrategy: any;
+ user: Models.IUserProperties;
+ catalogMenuItem: any;
+ version:string;
+ sortBy:string;
+ reverse:boolean;
+
+ //this is for UI paging
+ numberOfItemToDisplay:number;
+ isAllItemDisplay: boolean;
+
+ openViewerModal(isResource: boolean, uniqueId: string): void;
+ changeLifecycleState(entity:any,state:string): void;
+ sectionClick (section:string):void;
+ order(sortBy:string): void;
+ getNumOfElements(num:number): string;
+ goToComponent(component:Models.Components.Component):void;
+ raiseNumberOfElementToDisplay():void;
+ }
+
+ export class CatalogViewModel {
+ static '$inject' = [
+ '$scope',
+ '$filter',
+ 'Sdc.Services.EntityService',
+ 'sdcConfig',
+ 'sdcMenu',
+ '$state',
+ '$q',
+ 'Sdc.Services.UserResourceService',
+ '$modal',
+ '$templateCache',
+ 'Sdc.Services.CacheService',
+ 'ComponentFactory',
+ 'ChangeLifecycleStateHandler',
+ 'ModalsHandler',
+ 'MenuHandler'
+ ];
+
+ constructor(private $scope:ICatalogViewModelScope,
+ private $filter:ng.IFilterService,
+ private EntityService:Services.EntityService,
+ private sdcConfig:Models.IAppConfigurtaion,
+ private sdcMenu:Models.IAppMenu,
+ private $state:any,
+ private $q:any,
+ private userResourceService:Sdc.Services.IUserResourceClass,
+ private $modal:ng.ui.bootstrap.IModalService,
+ private $templateCache:ng.ITemplateCacheService,
+ private cacheService:Services.CacheService,
+ private ComponentFactory: Sdc.Utils.ComponentFactory,
+ private ChangeLifecycleStateHandler: Sdc.Utils.ChangeLifecycleStateHandler,
+ private OpenViewModalHandler: Utils.ModalsHandler,
+ private MenuHandler: Utils.MenuHandler
+ ) {
+
+ this.initScopeMembers();
+ this.initCatalogData(); // Async task to get catalog from server.
+ this.initScopeMethods();
+ }
+
+ private initCatalogData = ():void => {
+ let onSuccess = (followedResponse:Array<Models.Components.Component>):void => {
+ this.$scope.catalogFilterdItems = followedResponse;
+ this.$scope.isAllItemDisplay = this.$scope.numberOfItemToDisplay >= this.$scope.catalogFilterdItems.length;
+ this.$scope.categories = this.cacheService.get('serviceCategories').concat(this.cacheService.get('resourceCategories')).concat(this.cacheService.get('productCategories'));
+ this.$scope.gui.isLoading = false;
+ };
+
+ let onError = ():void => {
+ console.info('Failed to load catalog CatalogViewModel::initCatalog');
+ this.$scope.gui.isLoading = false;
+ };
+ this.EntityService.getCatalog().then(onSuccess, onError);
+ };
+
+
+
+ private initScopeMembers = ():void => {
+ // Gui init
+ this.$scope.gui = <Gui>{};
+ this.$scope.gui.isLoading = true;
+ this.$scope.numberOfItemToDisplay = 0;
+ //this.$scope.categories = this.cacheService.get('categoriesMap');
+ this.$scope.sdcMenu = this.sdcMenu;
+ this.$scope.confStatus = this.sdcMenu.statuses;
+ this.$scope.expandedSection = ["type", "cssClasses", "product-category", "status"];
+ this.$scope.user = this.userResourceService.getLoggedinUser();
+ this.$scope.catalogMenuItem = this.sdcMenu.catalogMenuItem;
+ this.$scope.version = this.cacheService.get('version');
+ this.$scope.sortBy = 'lastUpdateDate';
+ this.$scope.reverse = true;
+
+
+ // Checklist init
+ this.$scope.checkboxes = <Checkboxes>{};
+ this.$scope.checkboxes.componentTypes = ['Resource', 'Service', 'Product'];
+ this.$scope.checkboxes.resourceSubTypes = ['VF', 'VFC', 'CP', 'VL'];
+
+ // Checkboxes filter init
+ this.$scope.checkboxesFilter = <CheckboxesFilter>{};
+ this.$scope.checkboxesFilter.selectedComponentTypes = [];
+ this.$scope.checkboxesFilter.selectedResourceSubTypes = [];
+ this.$scope.checkboxesFilter.selectedCategoriesModel = [];
+ this.$scope.checkboxesFilter.selectedStatuses = [];
+
+ // this.$scope.isAllItemDisplay = this.$scope.numberOfItemToDisplay >= this.$scope.catalogFilterdItems.length;
+ };
+
+ private initScopeMethods = ():void => {
+ this.$scope.sectionClick = (section:string):void => {
+ let index:number = this.$scope.expandedSection.indexOf(section);
+ if (index!==-1) {
+ this.$scope.expandedSection.splice(index,1);
+ } else {
+ this.$scope.expandedSection.push(section);
+ }
+ };
+
+
+ this.$scope.order = (sortBy:string):void => {//default sort by descending last update. default for alphabetical = ascending
+ this.$scope.reverse = (this.$scope.sortBy === sortBy) ? !this.$scope.reverse : (sortBy === 'lastUpdateDate') ? true: false;
+ this.$scope.sortBy = sortBy;
+ };
+
+
+ this.$scope.goToComponent = (component:Models.Components.Component):void => {
+ this.$scope.gui.isLoading = true;
+ this.$state.go('workspace.general', {id: component.uniqueId, type:component.componentType.toLowerCase()});
+ };
+
+
+ // Will print the number of elements found in catalog
+ this.$scope.getNumOfElements = (num:number) : string => {
+ if (!num || num===0){
+ return "No Elements found";
+ } else if (num===1){
+ return "1 Element found";
+ }else {
+ return num + " Elements found";
+ }
+ };
+
+ /**
+ * Select | unselect sub resource when resource is clicked | unclicked.
+ * @param type
+ */
+ this.$scope.gui.onComponentTypeClick = (type:string): void => {
+ if (type==='Resource'){
+ if (this.$scope.checkboxesFilter.selectedComponentTypes.indexOf('Resource')===-1){
+ // If the resource was not selected, unselect all childs.
+ this.$scope.checkboxesFilter.selectedResourceSubTypes = [];
+ } else {
+ // If the resource was selected, select all childs
+ this.$scope.checkboxesFilter.selectedResourceSubTypes = angular.copy(this.$scope.checkboxes.resourceSubTypes);
+ }
+ }
+ };
+
+ /**
+ * Selecting | unselect resources when sub resource is clicked | unclicked.
+ */
+ this.$scope.gui.onResourceSubTypesClick = ():void => {
+ if (this.$scope.checkboxesFilter.selectedResourceSubTypes && this.$scope.checkboxesFilter.selectedResourceSubTypes.length===this.$scope.checkboxes.resourceSubTypes.length){
+ this.$scope.checkboxesFilter.selectedComponentTypes.push('Resource');
+ } else {
+ this.$scope.checkboxesFilter.selectedComponentTypes = _.without(this.$scope.checkboxesFilter.selectedComponentTypes,'Resource');
+ }
+ };
+
+ this.$scope.gui.onCategoryClick = (category:Models.IMainCategory): void => {
+ // Select | Unselect all childs
+ if (this.isCategorySelected(category.uniqueId)){
+ this.$scope.checkboxesFilter.selectedCategoriesModel = this.$scope.checkboxesFilter.selectedCategoriesModel.concat(angular.copy(_.map(category.subcategories, (item) => { return item.uniqueId; })));
+ if (category.subcategories) {
+ category.subcategories.forEach((sub:Models.ISubCategory)=> { // Loop on all selected subcategories and mark the childrens
+ this.$scope.checkboxesFilter.selectedCategoriesModel = this.$scope.checkboxesFilter.selectedCategoriesModel.concat(angular.copy(_.map(sub.groupings, (item) => {
+ return item.uniqueId;
+ })));
+ });
+ }
+ } else {
+ this.$scope.checkboxesFilter.selectedCategoriesModel = _.difference(this.$scope.checkboxesFilter.selectedCategoriesModel, _.map(category.subcategories, (item) => { return item.uniqueId; }));
+ if (category.subcategories) {
+ category.subcategories.forEach((sub:Models.ISubCategory)=> { // Loop on all selected subcategories and un mark the childrens
+ this.$scope.checkboxesFilter.selectedCategoriesModel = _.difference(this.$scope.checkboxesFilter.selectedCategoriesModel, _.map(sub.groupings, (item) => {
+ return item.uniqueId;
+ }));
+ });
+ }
+ }
+ };
+
+ this.$scope.gui.onSubcategoryClick = (category:Models.IMainCategory, subCategory:Models.ISubCategory) : void => {
+ // Select | Unselect all childs
+ if (this.isCategorySelected(subCategory.uniqueId)){
+ this.$scope.checkboxesFilter.selectedCategoriesModel = this.$scope.checkboxesFilter.selectedCategoriesModel.concat(angular.copy(_.map(subCategory.groupings, (item) => { return item.uniqueId; })));
+ } else {
+ this.$scope.checkboxesFilter.selectedCategoriesModel = _.difference(this.$scope.checkboxesFilter.selectedCategoriesModel, _.map(subCategory.groupings, (item) => { return item.uniqueId; }));
+ }
+
+ // Mark | Un mark the parent when all childs selected.
+ if (this.areAllCategoryChildsSelected(category)){
+ // Add the category to checkboxesFilter.selectedCategoriesModel
+ this.$scope.checkboxesFilter.selectedCategoriesModel.push(category.uniqueId);
+ } else {
+ this.$scope.checkboxesFilter.selectedCategoriesModel = _.without(this.$scope.checkboxesFilter.selectedCategoriesModel, category.uniqueId);
+ }
+
+ };
+
+ this.$scope.raiseNumberOfElementToDisplay = () : void => {
+ this.$scope.numberOfItemToDisplay = this.$scope.numberOfItemToDisplay +35;
+ if(this.$scope.catalogFilterdItems) {
+ this.$scope.isAllItemDisplay = this.$scope.numberOfItemToDisplay >= this.$scope.catalogFilterdItems.length;
+ }
+ };
+
+ this.$scope.gui.onGroupClick = (subCategory:Models.ISubCategory) : void => {
+ // Mark | Un mark the parent when all childs selected.
+ if (this.areAllSubCategoryChildsSelected(subCategory)){
+ // Add the category to checkboxesFilter.selectedCategoriesModel
+ this.$scope.checkboxesFilter.selectedCategoriesModel.push(subCategory.uniqueId);
+ } else {
+ this.$scope.checkboxesFilter.selectedCategoriesModel = _.without(this.$scope.checkboxesFilter.selectedCategoriesModel, subCategory.uniqueId);
+ }
+ };
+
+
+ };
+
+ private areAllCategoryChildsSelected = (category:Models.IMainCategory):boolean => {
+ if (!category.subcategories){return false;}
+ let allIds = _.map(category.subcategories, (sub:Models.ISubCategory)=>{return sub.uniqueId;});
+ let total = _.intersection(this.$scope.checkboxesFilter.selectedCategoriesModel, allIds);
+ return total.length === category.subcategories.length?true:false;
+ };
+
+ private areAllSubCategoryChildsSelected = (subCategory:Models.ISubCategory):boolean => {
+ if (!subCategory.groupings){return false;}
+ let allIds = _.map(subCategory.groupings, (group:Models.IGroup)=>{return group.uniqueId;});
+ let total = _.intersection(this.$scope.checkboxesFilter.selectedCategoriesModel, allIds);
+ return total.length === subCategory.groupings.length?true:false;
+ };
+
+ private isCategorySelected = (uniqueId:string):boolean => {
+ if (this.$scope.checkboxesFilter.selectedCategoriesModel.indexOf(uniqueId)!==-1){
+ return true;
+ }
+ return false;
+ };
+
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/catalog/catalog-view-tests.ts b/catalog-ui/app/scripts/view-models/catalog/catalog-view-tests.ts
new file mode 100644
index 0000000000..3e21835233
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/catalog/catalog-view-tests.ts
@@ -0,0 +1,309 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../references"/>
+describe("test catalog-view", () => {
+
+ let $controllerMock:ng.IControllerService;
+ let $qMock:ng.IQService;
+ let $httpBackendMock:ng.IHttpBackendService;
+ let $scopeMock:Sdc.ViewModels.ICatalogViewModelScope;
+ let $stateMock:ng.ui.IStateService;
+ let $stateParams:any;
+ let entityServiceMock;
+ let cacheServiceMock;
+
+ beforeEach(angular.mock.module('sdcApp'));
+
+ let getAllEntitiesResponseMock = [
+ {
+ "uniqueId": "855acdc7-7976-4913-9fa6-25220bd5a069",
+ "uuid": "8bc54f94-082c-42fa-9049-84767df3ff05",
+ "contactId": "qa1234",
+ "category": "VoIP Call Control",
+ "creationDate": 1447234712398,
+ "description": "ddddd",
+ "highestVersion": true,
+ "icon": "mobility",
+ "lastUpdateDate": 1447234712398,
+ "lastUpdaterUserId": "cs0008",
+ "lastUpdaterFullName": "Carlos Santana",
+ "lifecycleState": "NOT_CERTIFIED_CHECKOUT",
+ "distributionStatus": "DISTRIBUTION_NOT_APPROVED",
+ "projectCode": "233233",
+ "name": "mas mas mas mas mas mas mas mas mas mas mas mas ma",
+ "version": "0.1",
+ "type": 0,
+ "tags": [
+ "mas mas mas mas mas mas mas mas mas mas mas mas ma"
+ ],
+ "systemName": "MasMasMasMasMasMasMasMasMasMasMasMasMa",
+ "vnf": true,
+ "$$hashKey": "object:30"
+ },
+ {
+ "uniqueId": "4bb577ce-cb2c-4cb7-bb39-58644b5e73cb",
+ "uuid": "e27f4723-c9ec-4160-89da-dbf84d19a7e3",
+ "contactId": "qa1111",
+ "category": "Mobility",
+ "creationDate": 1447238503181,
+ "description": "aqa",
+ "highestVersion": true,
+ "icon": "call_controll",
+ "lastUpdateDate": 1447248991388,
+ "lastUpdaterUserId": "jm0007",
+ "lastUpdaterFullName": "Joni Mitchell",
+ "lifecycleState": "CERTIFIED",
+ "distributionStatus": "DISTRIBUTION_REJECTED",
+ "projectCode": "111111",
+ "name": "martin18",
+ "version": "1.0",
+ "type": 0,
+ "tags": [
+ "martin18"
+ ],
+ "systemName": "Martin18",
+ "vnf": true
+ },
+ {
+ "uniqueId": "f192f4a6-7fbf-42e4-a546-37509df28dc1",
+ "uuid": "0b77dc0d-222e-4d10-85cd-e420c9481417",
+ "contactId": "fd1212",
+ "category": "Application Layer 4+/Web Server",
+ "creationDate": 1447233679778,
+ "description": "geefw",
+ "highestVersion": true,
+ "icon": "database",
+ "lastUpdateDate": 1447233681582,
+ "lastUpdaterUserId": "cs0008",
+ "lastUpdaterFullName": "Carlos Santana",
+ "lifecycleState": "NOT_CERTIFIED_CHECKOUT",
+ "name": "ger",
+ "version": "0.1",
+ "type": 1,
+ "tags": [
+ "ger"
+ ],
+ "vendorName": "fewwfe",
+ "vendorRelease": "fewew",
+ "systemName": "Ger",
+ "$$hashKey": "object:31"
+ },
+ {
+ "uniqueId": "78392d08-1859-47c2-b1f2-1a35b7f8c30e",
+ "uuid": "8cdd63b2-6a62-4376-9012-624f424f71d4",
+ "contactId": "qw1234",
+ "category": "Application Layer 4+/Application Servers",
+ "creationDate": 1447234046114,
+ "description": "test",
+ "highestVersion": true,
+ "icon": "router",
+ "lastUpdateDate": 1447234050545,
+ "lastUpdaterUserId": "cs0008",
+ "lastUpdaterFullName": "Carlos Santana",
+ "lifecycleState": "NOT_CERTIFIED_CHECKOUT",
+ "name": "test",
+ "version": "0.1",
+ "type": 1,
+ "tags": [
+ "test"
+ ],
+ "vendorName": "test",
+ "vendorRelease": "test",
+ "systemName": "Test",
+ "$$hashKey": "object:32"
+ },
+ {
+ "uniqueId": "939e153d-2236-410f-b4a9-3b4bf8c79c9e",
+ "uuid": "84862547-4f56-4058-b78e-40df5f374d7e",
+ "contactId": "qw1234",
+ "category": "Application Layer 4+/Application Servers",
+ "creationDate": 1447235242560,
+ "description": "jlk",
+ "highestVersion": true,
+ "icon": "database",
+ "lastUpdateDate": 1447235328062,
+ "lastUpdaterUserId": "cs0008",
+ "lastUpdaterFullName": "Carlos Santana",
+ "lifecycleState": "NOT_CERTIFIED_CHECKIN",
+ "name": "new",
+ "version": "0.1",
+ "type": 1,
+ "tags": [
+ "new"
+ ],
+ "vendorName": "e",
+ "vendorRelease": "e",
+ "systemName": "New",
+ "$$hashKey": "object:33"
+ },
+ {
+ "uniqueId": "ece818e0-fd59-477a-baf6-e27461a7ce23",
+ "uuid": "8db823c2-6a9c-4636-8676-f5e713270dd7",
+ "contactId": "uf2345",
+ "category": "Network Layer 2-3/Router",
+ "creationDate": 1447235352429,
+ "description": "u",
+ "highestVersion": true,
+ "icon": "network",
+ "lastUpdateDate": 1447235370064,
+ "lastUpdaterUserId": "cs0008",
+ "lastUpdaterFullName": "Carlos Santana",
+ "lifecycleState": "NOT_CERTIFIED_CHECKOUT",
+ "name": "u",
+ "version": "0.1",
+ "type": 1,
+ "tags": [
+ "u"
+ ],
+ "vendorName": "u",
+ "vendorRelease": "u",
+ "systemName": "U",
+ "$$hashKey": "object:34"
+ }
+ ];
+
+ let resourceCategoriesResponseMock = [{"name":"Network L2-3","normalizedName":"network l2-3","uniqueId":"resourceNewCategory.network l2-3","subcategories":[{"name":"Gateway","normalizedName":"gateway","uniqueId":"resourceNewCategory.network l2-3.gateway","icons":["gateway"]},{"name":"Infrastructure","normalizedName":"infrastructure","uniqueId":"resourceNewCategory.network l2-3.infrastructure","icons":["ucpe"]},{"name":"WAN Connectors","normalizedName":"wan connectors","uniqueId":"resourceNewCategory.network l2-3.wan connectors","icons":["network","connector","port"]},{"name":"LAN Connectors","normalizedName":"lan connectors","uniqueId":"resourceNewCategory.network l2-3.lan connectors","icons":["network","connector","port"]},{"name":"Router","normalizedName":"router","uniqueId":"resourceNewCategory.network l2-3.router","icons":["router","vRouter"]}]},{"name":"Network L4+","normalizedName":"network l4+","uniqueId":"resourceNewCategory.network l4+","subcategories":[{"name":"Common Network Resources","normalizedName":"common network resources","uniqueId":"resourceNewCategory.network l4+.common network resources","icons":["network"]}]},{"name":"Application L4+","normalizedName":"application l4+","uniqueId":"resourceNewCategory.application l4+","subcategories":[{"name":"Load Balancer","normalizedName":"load balancer","uniqueId":"resourceNewCategory.application l4+.load balancer","icons":["loadBalancer"]},{"name":"Media Servers","normalizedName":"media servers","uniqueId":"resourceNewCategory.application l4+.media servers","icons":["applicationServer"]},{"name":"Application Server","normalizedName":"application server","uniqueId":"resourceNewCategory.application l4+.application server","icons":["applicationServer"]},{"name":"Database","normalizedName":"database","uniqueId":"resourceNewCategory.application l4+.database","icons":["database"]},{"name":"Call Control","normalizedName":"call control","uniqueId":"resourceNewCategory.application l4+.call control","icons":["call_controll"]},{"name":"Border Element","normalizedName":"border element","uniqueId":"resourceNewCategory.application l4+.border element","icons":["borderElement"]},{"name":"Web Server","normalizedName":"web server","uniqueId":"resourceNewCategory.application l4+.web server","icons":["applicationServer"]},{"name":"Firewall","normalizedName":"firewall","uniqueId":"resourceNewCategory.application l4+.firewall","icons":["firewall"]}]},{"name":"Generic","normalizedName":"generic","uniqueId":"resourceNewCategory.generic","subcategories":[{"name":"Database","normalizedName":"database","uniqueId":"resourceNewCategory.generic.database","icons":["database"]},{"name":"Abstract","normalizedName":"abstract","uniqueId":"resourceNewCategory.generic.abstract","icons":["objectStorage","compute"]},{"name":"Network Elements","normalizedName":"network elements","uniqueId":"resourceNewCategory.generic.network elements","icons":["network","connector"]},{"name":"Infrastructure","normalizedName":"infrastructure","uniqueId":"resourceNewCategory.generic.infrastructure","icons":["connector"]}]},{"name":"NewCategory","normalizedName":"newcategory","uniqueId":"resourceNewCategory.newcategory","subcategories":[{"name":"MyNewSubCategory","normalizedName":"mynewsubcategory","uniqueId":"resourceNewCategory.newcategory.mynewsubcategory"}]}];
+
+ let getAllEntitiesDefered:ng.IDeferred<any> = null;
+
+ beforeEach(angular.mock.inject((_$controller_:ng.IControllerService,
+ _$httpBackend_:ng.IHttpBackendService,
+ _$rootScope_,
+ _$q_:ng.IQService,
+ _$state_:ng.ui.IStateService,
+ _$stateParams_:any) => {
+
+ $controllerMock = _$controller_;
+ $httpBackendMock = _$httpBackend_
+ $scopeMock = _$rootScope_.$new();
+ $qMock = _$q_;
+ $stateMock = _$state_;
+ $stateParams = _$stateParams_;
+
+
+ //handle all http request thet not relevant to the tests
+ $httpBackendMock.expectGET(/.*languages\/en_US.json.*/).respond(200, JSON.stringify({}));
+ $httpBackendMock.expectGET(/.*rest\/version.*/).respond(200, JSON.stringify({}));
+ $httpBackendMock.expectGET(/.*configuration\/ui.*/).respond(200, JSON.stringify({}));
+ $httpBackendMock.expectGET(/.*user\/authorize.*/).respond(200, JSON.stringify({}));
+ $httpBackendMock.expectGET(/.*categories\/services.*/).respond(200, JSON.stringify({}));
+ $httpBackendMock.expectGET(/.*categories\/resources.*/).respond(200, JSON.stringify({}));
+ $httpBackendMock.expectGET(/.*categories\/products.*/).respond(200, JSON.stringify({}));
+ $httpBackendMock.expectGET('http://feHost:8181/sdc1/feProxy/rest/version').respond(200, JSON.stringify({}));
+
+ /**
+ * Mock the service
+ * @type {any}
+ */
+ getAllEntitiesDefered = $qMock.defer();
+ getAllEntitiesDefered.resolve(getAllEntitiesResponseMock);
+
+ cacheServiceMock = jasmine.createSpyObj('cacheServiceMock', ['get']);
+ cacheServiceMock.get.and.callFake(function(string){return resourceCategoriesResponseMock;});
+ /*
+ cacheServiceMock.get.and.callFake(function(value:string){
+ switch(value){
+ case 'serviceCategories':
+ console.log('serviceCategories');
+ break;
+ case 'resourceCategories':
+ console.log('resourceCategories');
+ break;
+ case 'productCategories':
+ console.log('productCategories');
+ break;
+ default :
+ console.log('default');
+ break;
+ }
+ });
+ */
+
+ entityServiceMock = jasmine.createSpyObj('entityServiceMock', ['getCatalog']);
+ entityServiceMock.getCatalog.and.returnValue(getAllEntitiesDefered.promise);
+
+ // $stateParams['show'] = '';
+
+ /**
+ * Need to inject into the controller only the objects that we want to MOCK
+ * those that we need to change theirs behaviors
+ */
+ $controllerMock(Sdc.ViewModels.CatalogViewModel, {
+ '$scope': $scopeMock,
+ '$stateParams': $stateParams,
+ 'Sdc.Services.EntityService': entityServiceMock,
+ 'Sdc.Services.CacheService': cacheServiceMock
+ });
+
+ }));
+
+
+ beforeEach(function () {
+ });
+
+ describe("test GUI events on checkbox type resource click", function () {
+
+ /**
+ * The function checks only for resource type.
+ * Select the Resource and verify that the sub resources are selected.
+ *
+ */
+ it('test onComponentTypeClick (check select checkbox of Resource type)', function () {
+ $scopeMock.$apply();
+ $scopeMock.checkboxesFilter.selectedComponentTypes = ['Resource'];
+ $scopeMock.gui.onComponentTypeClick('Resource');
+ expect($scopeMock.checkboxesFilter.selectedResourceSubTypes.length === 4).toBeTruthy();
+ });
+
+ /**
+ * The function checks only for resource type.
+ * Un select the Resource and verify that the sub resources are selected.
+ *
+ */
+ it('test onComponentTypeClick (check un select checkbox of Resource type)', function () {
+ $scopeMock.$apply();
+ $scopeMock.gui.onComponentTypeClick('Resource');
+ expect($scopeMock.checkboxesFilter.selectedResourceSubTypes.length === 0).toBeTruthy();
+ });
+
+ });
+
+ describe("test GUI events on checkbox main category click -> sub categories are selected", function () {
+
+ /**
+ * The function checks that after selecting 2 main categories, the subcategories are selected also.
+ *
+ */
+ it('test onComponentTypeClick (check select checkbox of Resource type)', function () {
+ let category1 = resourceCategoriesResponseMock[0];
+ let category2 = resourceCategoriesResponseMock[1];
+
+ $scopeMock.$apply();
+ $scopeMock.checkboxesFilter.selectedCategoriesModel = [category1.uniqueId, category2.uniqueId];
+ $scopeMock.gui.onCategoryClick(category1);
+ $scopeMock.gui.onCategoryClick(category2);
+
+ expect($scopeMock.checkboxesFilter.selectedCategoriesModel.length===8).toBeTruthy();
+ });
+
+ });
+
+
+});
diff --git a/catalog-ui/app/scripts/view-models/catalog/catalog-view.html b/catalog-ui/app/scripts/view-models/catalog/catalog-view.html
new file mode 100644
index 0000000000..0d46dc2a24
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/catalog/catalog-view.html
@@ -0,0 +1,190 @@
+<div class="sdc-catalog-container">
+
+ <loader data-display="gui.isLoading"></loader>
+<!--
+ <ecomp-header menu-data="menuItems" version="{{version}}"></ecomp-header>
+-->
+
+ <div class="w-sdc-main-container">
+
+ <!-- LEFT SIDE -->
+ <perfect-scrollbar scroll-y-margin-offset="0" class="sdc-catalog-body-container w-sdc-left-sidebar" include-padding="true">
+ <div class="sdc-catalog-leftbar-container">
+
+ <div class="sdc-catalog-type-filter-container">
+ <div
+ class="i-sdc-designer-leftbar-section-title pointer"
+ data-ng-click="sectionClick('type')"
+ data-ng-class="{'expanded': expandedSection.indexOf('type') !== -1}">
+ <span class="i-sdc-designer-leftbar-section-title-icon"></span>
+ <span class="i-sdc-designer-leftbar-section-title-text" data-tests-id="typeFilterTitle">Type</span>
+ </div>
+ <div class="i-sdc-designer-leftbar-section-content">
+ <ul class="list-unstyled i-sdc-designer-leftbar-section-content-ul">
+ <li class="i-sdc-designer-leftbar-section-content-ul-li" data-ng-repeat="type in checkboxes.componentTypes">
+
+ <sdc-checkbox elem-id="checkbox-{{type | lowercase | clearWhiteSpaces}}"
+ sdc-checklist-model="checkboxesFilter.selectedComponentTypes"
+ sdc-checklist-value="type"
+ data-ng-click="gui.onComponentTypeClick(type)"
+ text="{{type}}"></sdc-checkbox>
+
+ <ul class="list-unstyled i-sdc-catalog-subcategories-checkbox" data-ng-if="type==='Resource'">
+ <li data-ng-repeat="subType in checkboxes.resourceSubTypes">
+
+ <sdc-checkbox elem-id="checkbox-{{subType | lowercase | clearWhiteSpaces}}"
+ sdc-checklist-model="checkboxesFilter.selectedResourceSubTypes"
+ sdc-checklist-value="subType"
+ data-ng-click="gui.onResourceSubTypesClick()"
+ text="{{subType}}"></sdc-checkbox>
+
+ </li>
+ </ul>
+ </li>
+ </ul>
+ </div>
+ </div>
+
+ <div class="sdc-catalog-categories-filter-container">
+ <div
+ class="i-sdc-designer-leftbar-section-title pointer"
+ data-ng-click="sectionClick('category')"
+ data-ng-class="{'expanded': expandedSection.indexOf('category') !== -1}">
+ <span class="i-sdc-designer-leftbar-section-title-icon"></span>
+ <span class="i-sdc-designer-leftbar-section-title-text" data-tests-id="categoriesFilterTitle">Categories</span>
+ </div>
+ <div class="i-sdc-designer-leftbar-section-content">
+
+ <!-- CATEGORY CHECKBOX -->
+ <ul class="list-unstyled i-sdc-designer-leftbar-section-content-ul">
+ <li class="i-sdc-designer-leftbar-section-content-ul-li"
+ data-ng-repeat="category in categories track by category.uniqueId | categoryTypeFilter:checkboxesFilter.selectedComponentTypes | orderBy: category">
+
+ <sdc-checkbox elem-id="checkbox-{{category.uniqueId | lowercase | clearWhiteSpaces}}"
+ sdc-checklist-model="checkboxesFilter.selectedCategoriesModel"
+ sdc-checklist-value="category.uniqueId"
+ data-tests-id="{{category.uniqueId}}"
+ data-ng-click="gui.onCategoryClick(category)"
+ text="{{category.name}}"></sdc-checkbox>
+
+ <!-- SUB CATEGORY CHECKBOX -->
+ <ul class="list-unstyled i-sdc-catalog-subcategories-checkbox" data-ng-if="category.subcategories && category.subcategories.length>0">
+ <li ng-repeat="subcategory in category.subcategories track by subcategory.uniqueId | orderBy:'name'">
+
+ <sdc-checkbox elem-id="checkbox-{{subcategory.uniqueId | lowercase | clearWhiteSpaces}}"
+ sdc-checklist-model="checkboxesFilter.selectedCategoriesModel"
+ sdc-checklist-value="subcategory.uniqueId"
+ data-tests-id="{{subcategory.uniqueId}}"
+ data-ng-click="gui.onSubcategoryClick($parent.category, subcategory)"
+ text="{{subcategory.name}}"></sdc-checkbox>
+
+ <!-- GROUPING CHECKBOX -->
+ <ul class=" list-unstyled i-sdc-catalog-grouping-checkbox" data-ng-if="subcategory.groupings && subcategory.groupings.length>0">
+ <li ng-repeat="grouping in subcategory.groupings track by grouping.uniqueId | orderBy:'name'">
+
+ <sdc-checkbox elem-id="checkbox-{{grouping.uniqueId | lowercase | clearWhiteSpaces}}"
+ sdc-checklist-model="checkboxesFilter.selectedCategoriesModel"
+ sdc-checklist-value="grouping.uniqueId"
+ data-ng-click="gui.onGroupClick($parent.subcategory)"
+ text="{{grouping.name}}"></sdc-checkbox>
+
+ </li>
+ </ul>
+ </li><!-- Close subcategory -->
+ </ul><!-- Close subcategories -->
+ </li><!-- Close main category -->
+ </ul><!-- Close main categories -->
+
+ </div>
+ </div>
+
+ <!-- STATUS -->
+ <div class="sdc-catalog-status-filter-container">
+ <div
+ class="i-sdc-designer-leftbar-section-title pointer"
+ data-ng-click="sectionClick('status')"
+ data-ng-class="{'expanded': expandedSection.indexOf('status') !== -1}">
+ <span class="i-sdc-designer-leftbar-section-title-icon"></span>
+ <span class="i-sdc-designer-leftbar-section-title-text" data-tests-id="statusFilterTitle">Status</span>
+ </div>
+
+ <div class="i-sdc-designer-leftbar-section-content">
+ <ul class="list-unstyled i-sdc-designer-leftbar-section-content-ul">
+ <!--li data-ng-repeat="(key, value) in confStatus" -->
+
+ <li class="i-sdc-designer-leftbar-section-content-ul-li"
+ data-ng-repeat="(key, state) in confStatus | catalogStatusFilter">
+
+ <sdc-checkbox elem-id="checkbox-{{key | lowercase | clearWhiteSpaces}}"
+ sdc-checklist-model="checkboxesFilter.selectedStatuses"
+ sdc-checklist-value="state.values"
+ text="{{state.name}}"></sdc-checkbox>
+
+ <div class="i-sdc-categories-list-item-icon"></div>
+ </label>
+ </li>
+ </ul>
+ </div>
+ </div>
+
+ </div>
+ </perfect-scrollbar>
+
+ <!-- RIGHT SIDE -->
+ <perfect-scrollbar id="catalog-main-scroll" include-padding="true" class="w-sdc-main-right-container w-sdc-catalog-main">
+
+ <!-- HEADER -->
+ <div>
+ <div class="w-sdc-dashboard-catalog-header">
+ {{getNumOfElements((catalogFilterdItems | entityFilter:checkboxesFilter | filter:search).length)}}
+ </div>
+ <div class="w-sdc-dashboard-catalog-header-right">
+ <span class="w-sdc-dashboard-catalog-header-order" translate="SORT_CAPTION"></span>&nbsp;&nbsp;
+ <a class="w-sdc-dashboard-catalog-sort" data-tests-id="sort-by-last-update" data-ng-class="{'blue' : sortBy==='lastUpdateDate'}"
+ ng-click="order('lastUpdateDate')" translate="SORT_BY_UPDATE_DATE"></a>&nbsp;
+ <span data-ng-show="sortBy === 'lastUpdateDate'" class="w-sdc-catalog-sort-arrow" data-ng-class="{'down': reverse, 'up':!reverse}"></span>
+ &nbsp;|&nbsp;
+ <a class="w-sdc-dashboard-catalog-sort" data-tests-id="sort-by-alphabetical" data-ng-class="{'blue' : sortBy!=='lastUpdateDate'}"
+ ng-click="order('name | resourceName')" translate="SORT_ALPHABETICAL"></a>&nbsp;
+ <span data-ng-show="sortBy !== 'lastUpdateDate'" class="w-sdc-catalog-sort-arrow" data-ng-class="{'down': reverse, 'up':!reverse}"></span>
+ </div>
+ </div>
+
+ <div infinite-scroll-disabled='isAllItemDisplay' infinite-scroll="raiseNumberOfElementToDisplay()" infinite-scroll-container="'#catalog-main-scroll'" infinite-scroll-parent>
+ <!-- CARDS -->
+ <div data-ng-class="{'sdc-hide-popover': hidePopover}"
+ data-ng-init="component.filterTerm = component.name + ' ' + component.description + ' ' + component.tags.toString() + ' ' + component.version"
+ class="w-sdc-dashboard-card"
+ data-ng-repeat="component in catalogFilterdItems | entityFilter:checkboxesFilter | filter:search | orderBy:sortBy:reverse | limitTo:numberOfItemToDisplay"
+ data-ng-class="{'resource' : component.isResource(), 'service' : component.isService(), 'product' : component.isProduct()}">
+
+ <div class="w-sdc-dashboard-card-body" data-ng-click="gui.isLoading || goToComponent(component)">
+ <div class="w-sdc-dashboard-card-avatar"><span data-tests-id="asset-type" class="{{component.getComponentSubType()}}"></span></div>
+ <!--<div class="w-sdc-dashboard-card-edit " data-ng-class="component.lifecycleState" data-tests-id="assetlifecycleState {{getStatus()}}"></div>-->
+ <div class="w-sdc-dashboard-card-schema-image {{component.icon}}" data-tests-id="{{component.categories[0].subcategories[0].uniqueId}}" data-ng-class="{'sprite-resource-icons':component.isResource(), 'sprite-services-icons':component.isService(), 'sprite-product-icons':component.isProduct()}"></div>
+ <!--<div class="w-sdc-dashboard-card-description">{{component.description}}</div>-->
+ <div class="w-sdc-dashboard-card-info-name-container">
+ <span class="w-sdc-dashboard-card-info-name" tooltips
+ tooltip-content="{{component.name | resourceName}}"> {{component.name | resourceName}}</span>
+ </div>
+ </div>
+
+ <div class="w-sdc-dashboard-card-footer">
+ <div class="w-sdc-dashboard-card-info">
+ <div class="w-sdc-dashboard-card-info-lifecycleState">
+ <span class="w-sdc-dashboard-card-info-lifecycleState" tooltips
+ tooltip-content="{{component.getStatus(sdcMenu)}}"> {{component.getStatus(sdcMenu)}}</span>
+ </div>
+ <div class="w-sdc-dashboard-card-info-user">V {{component.version}}</div>
+ </div>
+ <!--<div class="w-sdc-dashboard-card-info-lifecycleState-icon sprite-new {{sdcMenu.LifeCycleStatuses[component.lifecycleState].icon}}"></div>-->
+ </div>
+ </div>
+ </div>
+ </perfect-scrollbar>
+
+ </div>
+
+ <top-nav top-lvl-selected-index="1" search-bind="search.filterTerm" version="{{version}}"></top-nav>
+
+</div>
diff --git a/catalog-ui/app/scripts/view-models/catalog/catalog.less b/catalog-ui/app/scripts/view-models/catalog/catalog.less
new file mode 100644
index 0000000000..8be90a6a59
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/catalog/catalog.less
@@ -0,0 +1,304 @@
+.sdc-catalog-container {
+
+ .i-sdc-categories-list-item {
+ font-weight: normal;
+ }
+
+ // Checkboxes
+ .i-sdc-designer-leftbar-section-content-ul {
+ padding: 0;
+ margin: 0;
+
+ .i-sdc-catalog-subcategories-checkbox {
+ padding: 0 0 0 20px;
+ margin: 0;
+
+ .i-sdc-catalog-grouping-checkbox {
+ padding: 0 0 0 20px;
+ margin: 0;
+ }
+
+ }
+
+ }
+
+ .i-sdc-designer-leftbar-section-content-li {
+ &:last-child {
+ .i-sdc-categories-list-item {
+ margin: 0;
+ }
+ }
+ }
+
+ .i-sdc-categories-list-item {
+ display: block;
+ //margin-bottom: 5px;
+ //padding-left: 15px;
+ //text-indent: -24px;
+ vertical-align: top;
+ font-weight: bold;
+ }
+
+ .i-sdc-subcategories-list-item {
+ display: block;
+ //padding-left: 20px;
+ vertical-align: top;
+ font-weight: normal;
+ margin: 0;
+ //text-indent: -10px;
+ }
+
+ /*Added by - Ikram */
+ .i-sdc-product-input,
+ .i-sdc-product-select {
+ border: 1px solid @border_color_f;
+ min-height: 30px;
+ padding: 0;
+ width: 100%;
+ margin: 1px 0;
+ background-color: #F2F2F2;
+ outline: none;
+
+ &:disabled {
+ .disabled;
+ }
+ optgroup{
+ color: @color_u;
+ option{
+ color: @color_b;
+ }
+ }
+ }
+
+ .i-sdc-categories-list-item-icon {
+ display: inline-block;
+ float: right;
+ position: relative;
+ right: -8px;
+ top: 6px;
+ }
+
+ .i-sdc-categories-list-item {
+ margin-top: 7px;
+ &.NOT_CERTIFIED_CHECKOUT,
+ &.NOT_CERTIFIED_CHECKIN {
+ .i-sdc-categories-list-item-icon {
+ background: url('../../../styles/images/sprites/sprite-global-old.png') no-repeat -53px -2889px;
+ width: 14px;
+ height: 14px;
+
+ }
+ }
+
+ &.CERTIFIED {
+ .i-sdc-categories-list-item-icon {
+ background: url('../../../styles/images/sprites/sprite-global-old.png') no-repeat -53px -3034px;
+ width: 14px;
+ height: 16px;
+ }
+ }
+
+ &.READY_FOR_CERTIFICATION {
+ .i-sdc-categories-list-item-icon {
+ background: url('../../../styles/images/sprites/sprite-global-old.png') no-repeat -53px -2985px;
+ width: 14px;
+ height: 16px;
+ }
+ }
+
+ &.CERTIFICATION_IN_PROGRESS {
+ .i-sdc-categories-list-item-icon {
+ background: url('../../../styles/images/sprites/sprite-global-old.png') no-repeat -53px -2934px;
+ width: 14px;
+ height: 16px;
+ }
+ }
+
+ &.DISTRIBUTED,
+ &.TBD {
+ .i-sdc-categories-list-item-icon {
+ background: url('../../../styles/images/sprites/sprite-global-old.png') no-repeat -43px -3087px;
+ width: 24px;
+ height: 14px;
+
+ }
+ }
+ }
+
+ .i-sdc-categories-list-input {
+ margin: 8px;
+
+ }
+
+ .i-sdc-subcategories-list-input {
+
+ margin: 8px;
+ }
+ .i-sdc-subcategories-list-input-container {
+ margin: 0px 0px 0px 20px;
+ padding: 2px;
+ }
+
+ .w-sdc-header-catalog-search-container {
+ display: table;
+ padding: 21px 0;
+ position: relative;
+
+ .w-sdc-designer-leftbar-search-input {
+ color: #000;
+ width: 300px;
+ }
+
+ // .magnification {
+ // .sprite;
+ // .sprite.magnification-glass;
+ // .hand;
+ // position: absolute;
+ // top: 40px;
+ // right: 42px;
+ // }
+ }
+
+ .w-sdc-catalog-main {
+ padding: 10px 12px;
+ }
+ .w-sdc-dashboard-catalog-header {
+ .b_9;
+ display: inline-block;
+ font-style: italic;
+ font-weight: bold;
+ padding-left: 10px;
+ }
+
+ .w-sdc-dashboard-catalog-header-order {
+ .b_9;
+ font-weight: 800;
+ }
+
+ .w-sdc-dashboard-catalog-sort {
+ .b_9;
+ font-weight: bold;
+ white-space:pre;
+ &:hover{
+ .hand;
+ text-decoration: none;
+ .a_9;
+ }
+ &.blue {
+ .a_9;
+ }
+ }
+
+ .w-sdc-catalog-sort-arrow{
+ display: inline-block;
+ &.up{
+ .b_9;
+ width: 0;
+ height: 0;
+ border-left: 5px solid transparent;
+ border-right: 5px solid transparent;
+ border-bottom: 5px solid ;
+ }
+ &.down{
+ .b_9;
+ width: 0;
+ height: 0;
+ border-left: 5px solid transparent;
+ border-right: 5px solid transparent;
+ border-top: 5px solid;
+ }
+ }
+
+
+
+
+ .w-sdc-dashboard-catalog-header-right{
+ float: right;
+ display: inline-block;
+ padding-right:34px;
+ }
+
+ .w-sdc-header-catalog-search-input {
+ width: 420px;
+ display: table-cell;
+ padding: 0 25px 1px 10px;
+ border: 1px solid #bcbcbc;
+ .border-radius(10px);
+ height: 30px;
+ margin: 10px 30px;
+ outline: none;
+ }
+
+ .sdc-catalog-type-filter-container {
+ margin-top: -1px;
+ }
+
+ .i-sdc-designer-leftbar-section-title {
+ text-transform: uppercase;
+ .l_14_m;
+ line-height: 30px;
+ }
+
+ .i-sdc-designer-leftbar-section-title-icon {
+ .hand;
+ .tlv-sprite;
+ .footer-close;
+ transition: .3s all;
+ margin-top: -4px;
+ }
+
+ .i-sdc-designer-leftbar-section-title-text {
+ margin-left: 20px;
+ }
+
+ .seperator-left,
+ .seperator-right {
+ border-right: solid 1px @color_m;
+ display: table-cell;
+ width: 2px;
+ }
+
+ // Rotate catalog left side arrows
+ .i-sdc-designer-leftbar-section-title.expanded .i-sdc-designer-leftbar-section-title-icon {
+ transform: rotate(180deg);
+ }
+
+ // Transform catalog left side sections
+ .i-sdc-designer-leftbar-section-title + .i-sdc-designer-leftbar-section-content {
+ max-height: 0px;
+ margin: 0 auto;
+ transition: all .3s;
+ overflow: hidden;
+ padding: 0 10px 0 18px;
+ }
+
+ .i-sdc-designer-leftbar-section-title.expanded + .i-sdc-designer-leftbar-section-content {
+ max-height: 9999px;
+ margin: 0 auto 1px;
+ transition: all .3s;
+ padding: 10px 18px 10px 18px;
+ overflow: hidden;
+ }
+
+}
+
+.w-sdc-search-icon{
+ position: absolute;
+ right: 40px;
+ top: 40px;
+ &.leftbar{
+ top: 19px;
+ right: 18px;
+ }
+ &.magnification {
+ .sprite;
+ .sprite.magnification-glass;
+ .hand;
+ }
+
+ &.cancel {
+ .sprite;
+ .sprite.clear-text;
+ .hand;
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/component-viewer/activity-log/activity-log-view.html b/catalog-ui/app/scripts/view-models/component-viewer/activity-log/activity-log-view.html
new file mode 100644
index 0000000000..ac51e9014c
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/component-viewer/activity-log/activity-log-view.html
@@ -0,0 +1,16 @@
+<div ng-repeat="activityDate in activityDateArray " class="w-sdc-component-viewer-right-activity-log" >
+ <div class="w-sdc-component-viewer-right-activity-log-date" >{{activityDate | date: 'longDate'}}</div>
+ <div ng-repeat="activity in activityLog[activityDate] | orderBy: '-TIMESTAMP'">
+ <div class="w-sdc-component-viewer-right-activity-log-time">{{activity.TIMESTAMP.replace(" UTC", '') | stringToDateFilter | date: 'mediumTime':'UTC'}}</div>
+ <div class="w-sdc-component-viewer-right-activity-log-content">{{"Action: " + parseAction(activity.ACTION) + " Performed by: " + activity.MODIFIER + " Status: " + activity.STATUS}}</div>
+ </div>
+ </div>
+</div>
+
+
+
+
+
+
+
+
diff --git a/catalog-ui/app/scripts/view-models/component-viewer/activity-log/activity-log-view.less b/catalog-ui/app/scripts/view-models/component-viewer/activity-log/activity-log-view.less
new file mode 100644
index 0000000000..4a7676b6e2
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/component-viewer/activity-log/activity-log-view.less
@@ -0,0 +1,18 @@
+.w-sdc-component-viewer-right-activity-log{
+
+ .w-sdc-component-viewer-right-activity-log-date{
+ .backgroundColor.n;
+ .font-color.g;
+ padding: 4px 11px
+ }
+ .w-sdc-component-viewer-right-activity-log-time{
+ .g_3;
+ padding: 12px 0px 0px 11px;
+ }
+
+ .w-sdc-component-viewer-right-activity-log-content{
+ .g_1;
+ padding: 0px 0px 12px 11px;
+ border-bottom: 1px solid rgba(0, 0, 0, 0.17);
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/component-viewer/component-viewer-view-model.ts b/catalog-ui/app/scripts/view-models/component-viewer/component-viewer-view-model.ts
new file mode 100644
index 0000000000..3ae8ad70fb
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/component-viewer/component-viewer-view-model.ts
@@ -0,0 +1,211 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../references"/>
+
+module Sdc.ViewModels {
+ 'use strict';
+
+ interface IComponentViewerViewModelScope extends ng.IScope {
+ component: Models.Components.Component;
+ additionalInformations: Array<Models.AdditionalInformationModel>;
+ activityLog: any;
+ activityDateArray: Array<any>; //this is in order to sort the dates
+ inputs: Array<any>;
+ isLoading: boolean;
+ templateUrl: string;
+ currentTab:string;
+ preVersion:string;
+ sdcMenu:Models.IAppMenu;
+ versionsList:Array<any>;
+ close(): void;
+ hasItems(obj:any): boolean;
+ onVersionChanged(version:any) : void;
+ moveToTab(tab:string):void;
+ isSelected(tab:string):boolean;
+ getActivityLog(uniqueId:string):void;
+ parseAction(action:string):string;
+ }
+
+ export class ComponentViewerViewModel {
+
+ static '$inject' = [
+ '$scope',
+ '$modalInstance',
+ 'component',
+ 'Sdc.Services.ActivityLogService',
+ 'sdcMenu',
+ 'ComponentFactory'
+ ];
+
+ constructor(private $scope:IComponentViewerViewModelScope,
+ private $modalInstance:ng.ui.bootstrap.IModalServiceInstance,
+ private component:Models.Components.Component,
+ private activityLogService:Services.ActivityLogService,
+ private sdcMenu:Models.IAppMenu,
+ private ComponentFactory: Utils.ComponentFactory) {
+ this.initScope(component);
+ }
+
+ //creating objects for versions
+ private initVersionObject:Function = ():void => {
+ this.$scope.versionsList = [];
+ for (let version in this.$scope.component.allVersions) {
+ this.$scope.versionsList.push({
+ versionNumber: version,
+ versioning: this.versioning(version),
+ versionId: this.$scope.component.allVersions[version]
+ });
+ }
+
+ };
+
+ private versioning:Function = (versionNumber:string):string => {
+ let version:Array<string> = versionNumber.split('.');
+ return '00000000'.slice(version[0].length) + version[0] + '.' + '00000000'.slice(version[1].length) + version[1];
+ };
+
+ private showComponentInformationView:Function = ():void => {
+ if (this.$scope.component.isResource()) {
+ this.$scope.templateUrl = '/app/scripts/view-models/component-viewer/properties/resource-properties-view.html';
+ } else if(this.$scope.component.isService()) {
+ this.$scope.templateUrl = '/app/scripts/view-models/component-viewer/properties/service-properties-view.html';
+ } else {
+ this.$scope.templateUrl = '/app/scripts/view-models/component-viewer/properties/product-properties-view.html';
+ }
+ };
+
+ private showActivityLogView:Function = ():void => {
+ this.$scope.templateUrl = '/app/scripts/view-models/component-viewer/activity-log/activity-log-view.html';
+ };
+
+ private initComponent = (component:Models.Components.Component):void => {
+ this.$scope.component = component;
+ this.$scope.additionalInformations = component.getAdditionalInformation();
+ this.initVersionObject();
+ this.$scope.isLoading = false;
+ };
+
+ private initScope = (component:Models.Components.Component):void => {
+ this.$scope.isLoading = false;
+ this.initComponent(component);
+ this.$scope.currentTab = 'PROPERTIES';
+ this.$scope.preVersion = component.version;
+ this.$scope.sdcMenu = this.sdcMenu;
+ this.showComponentInformationView();
+ //service inputs
+ if (component.isService()) {
+ let inputs:Array<any> = [];
+
+ for (let group in component.componentInstancesProperties) {
+ if (component.componentInstancesProperties[group]) {
+ component.componentInstancesProperties[group].forEach((property:Models.PropertyModel):void => {
+ if (!property.value) {
+ property.value = property.defaultValue;
+ }
+ inputs.push({
+ name: property.name,
+ value: property.value,
+ type: property.type
+ });
+ });
+ }
+ }
+ this.$scope.inputs = inputs;
+ }
+
+ this.$scope.hasItems = (obj:any):boolean => {
+ return Object.keys(obj).length > 0;
+ };
+
+ this.$scope.close = ():void => {
+ this.$modalInstance.dismiss();
+ };
+
+ this.$scope.onVersionChanged = (version:any):void => {
+ if (version.versionNumber != this.$scope.component.version) {
+ this.$scope.isLoading = true;
+ this.ComponentFactory.getComponentFromServer(this.component.componentType, version.versionId).then((component: Models.Components.Component):void => {
+ this.initComponent(component);
+ });
+ if (this.$scope.currentTab === 'ACTIVITY_LOG') {
+ this.$scope.getActivityLog(version.versionId);
+ }
+
+ }
+ };
+
+ this.$scope.getActivityLog = (uniqueId:any):void => {
+
+ let onError = (response) => {
+ this.$scope.isLoading = false;
+ console.info('onFaild', response);
+
+ };
+ let onSuccess = (response:Array<Models.Activity>) => {
+ this.$scope.activityLog = _.groupBy(response, function (activity:Models.Activity) { //group by date only
+ let dateTime:Date = new Date(activity.TIMESTAMP.replace(" UTC", '').replace(" ", 'T'));
+ // let date:Date = new Date(dateTime.getFullYear(), dateTime.getMonth(), dateTime.getDate());
+ return dateTime.getTime();
+ });
+ /*this is in order to sort the jsonObject by date*/
+ this.$scope.activityDateArray = Object.keys(this.$scope.activityLog);
+ this.$scope.activityDateArray.sort().reverse();
+ this.$scope.isLoading = false;
+ };
+
+ this.$scope.isLoading = true;
+ if (this.$scope.component.isResource()) {
+ this.activityLogService.getActivityLogService('resources', uniqueId).then(onSuccess, onError);
+ }
+ if (this.$scope.component.isService()) {
+ this.activityLogService.getActivityLogService('services', uniqueId).then(onSuccess, onError);
+ }
+
+ };
+
+ this.$scope.moveToTab = (tab:string):void => {
+ if (tab === this.$scope.currentTab) {
+ return;
+ } else if (tab === 'PROPERTIES') {
+ this.showComponentInformationView();
+ this.$scope.preVersion = this.$scope.component.version;
+ } else if (tab === 'ACTIVITY_LOG') {
+ if (!this.$scope.activityLog || this.$scope.preVersion != this.$scope.component.version) {
+ this.$scope.activityLog = this.$scope.getActivityLog(this.$scope.component.uniqueId);
+ }
+ this.showActivityLogView();
+ } else {
+ console.error("Tab " + tab + " not found!");
+ return;
+ }
+ this.$scope.currentTab = tab;
+ };
+
+ this.$scope.isSelected = (tab:string):boolean => {
+ return tab === this.$scope.currentTab;
+ };
+
+ this.$scope.parseAction = (action:string) => {
+ return action ? action.split(/(?=[A-Z])/).join(' ') : '';
+ };
+
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/component-viewer/component-viewer.html b/catalog-ui/app/scripts/view-models/component-viewer/component-viewer.html
new file mode 100644
index 0000000000..6f244b048e
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/component-viewer/component-viewer.html
@@ -0,0 +1,55 @@
+<div class="w-sdc-resource-viewer">
+ <loader data-display="isLoading"></loader>
+ <div class="w-sdc-resource-viewer-modal-close sprite x-btn-black" data-ng-click="close()">X</div>
+ <div class="w-sdc-resource-viewer-content" data-ng-if="component">
+ <div class="w-sdc-resource-viewer-left">
+ <h3 class="w-sdc-resource-viewer-left-title clearfix">
+ <div class="w-sdc-resource-viewer-left-title-icon i-sdc-form-item-suggested-icon borderElement large {{component.iconSprite}} {{component.icon}}"></div>
+ <span class="w-sdc-resource-viewer-left-title-name"
+ tooltips tooltip-content="{{component.name | resourceName}}">{{component.name | resourceName}}</span>
+ <br/>
+ <span class="w-sdc-resource-viewer-left-title-version">v{{component.version}}</span>
+ </h3>
+ <p class="w-sdc-resource-viewer-left-title-uuid">
+ UUID: {{component.uuid}}
+ </p>
+ <div class="w-sdc-resource-viewer-leftbar-section">
+ <div class="w-sdc-resource-viewer-leftbar-section-title">Version History</div>
+ <perfect-scrollbar class="w-sdc-resource-viewer-version">
+ <div class="i-sdc-resource-viewer-version-container">
+ <div data-ng-repeat="version in versionsList | orderBy: '-versioning'">
+ <span class="i-sdc-resource-viewer-version-item" data-ng-class="{'active': version.versionNumber == component.version}" data-ng-click="onVersionChanged(version)">{{ version.versionNumber }}</span>
+ </div>
+ </div>
+ </perfect-scrollbar>
+ </div>
+
+ <div class="w-sdc-resource-viewer-leftbar-section">
+ <div class="w-sdc-resource-viewer-leftbar-section-title">Composition</div>
+ <perfect-scrollbar class="w-sdc-resource-viewer-leftbar-section-structure" ng-show="component.isComplex()">
+ <structure-tree component="component"></structure-tree>
+ </perfect-scrollbar>
+ </div>
+ </div>
+
+ <div class="w-sdc-resource-viewer-right">
+ <button class="w-sdc-resource-viewer-right-tab"
+ data-ng-class="{'selected': isSelected('PROPERTIES')}"
+ data-ng-click="moveToTab('PROPERTIES')"
+ translate="ENTITY_VIEWER_PROPERTIES_TAB">
+ </button>
+
+ <button class="w-sdc-resource-viewer-right-tab"
+ data-ng-if="component.isResource() || component.isService()"
+ data-ng-class="{'selected': isSelected('ACTIVITY_LOG')}"
+ data-ng-click="moveToTab('ACTIVITY_LOG')"
+ translate="ENTITY_VIEWER_ACTIVITY_LOG_TAB">
+ </button>
+
+ <perfect-scrollbar include-padding="true" class="w-sdc-resource-viewer-right-content">
+ <ng-include src="templateUrl" ng-if="true"></ng-include>
+ </perfect-scrollbar>
+ <div style="clear:both;"></div>
+ </div>
+ </div>
+</div>
diff --git a/catalog-ui/app/scripts/view-models/component-viewer/component-viewer.less b/catalog-ui/app/scripts/view-models/component-viewer/component-viewer.less
new file mode 100644
index 0000000000..2fe5676d62
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/component-viewer/component-viewer.less
@@ -0,0 +1,148 @@
+html .modal-component-viewer{
+ width: 1084px;
+}
+
+.w-sdc-resource-viewer {
+ .b_7;
+ .w-sdc-resource-viewer-modal-close{
+ z-index: 2;
+ text-indent: -100px;
+ overflow: hidden;
+ top: 19px;
+ }
+ .w-sdc-resource-viewer-content {
+ position: relative;
+ overflow: hidden;
+
+ .w-sdc-resource-viewer-left {
+
+ .bg_j;
+ display: table-cell;
+ width: 282px;
+
+ .w-sdc-resource-viewer-left-title {
+ margin: 0;
+ display: block;
+ height: 100px;
+ padding: 20px 0;
+ }
+
+ .w-sdc-resource-viewer-left-title-icon {
+ margin: 0 15px;
+ vertical-align: middle;
+ float: left;
+ }
+
+ .w-sdc-resource-viewer-left-title-name {
+ .g_7;
+ max-width: 160px;
+ overflow: hidden;
+ display: inline-block;
+ text-overflow: ellipsis;
+ vertical-align: middle;
+ line-height: 23px;
+ padding-top: 10px;
+ font-weight: bold;
+ white-space: nowrap;
+ }
+
+ .w-sdc-resource-viewer-left-title-version {
+ .g_13;
+ float: left;
+ }
+
+ .w-sdc-resource-viewer-left-title-uuid {
+ .g_14;
+ text-align: center;
+ border-top: 1px solid rgba(120, 136, 148, 0.26);
+ width: 95%;
+ margin: auto;
+ padding: 7px 0;
+ }
+
+ .w-sdc-resource-viewer-leftbar-section {
+ font-family: omnes-medium, sans-serif;
+ }
+
+ .w-sdc-resource-viewer-leftbar-section-title {
+ .bg_o;
+ color: #fff;
+ font-size: 14px;
+ padding: 12px 20px;
+ text-transform: uppercase;
+ }
+
+ .w-sdc-resource-viewer-leftbar-section-structure{
+ .perfect-scrollbar;
+ max-height: 525px;
+ }
+ .w-sdc-resource-viewer-version {
+ font-weight: bold;
+ .perfect-scrollbar;
+ }
+
+ .i-sdc-resource-viewer-version-container {
+ padding: 13px 0px 13px 13px;
+ max-height: 218px;
+
+ .i-sdc-resource-viewer-version-item {
+
+ &.active {
+ .a_7;
+ }
+ &:hover {
+ cursor: pointer;
+ }
+ }
+
+ }
+ }
+
+ .w-sdc-resource-viewer-right {
+ .bg_c;
+ display: table-cell;
+ vertical-align: top;
+ padding: 0; // for the scroller to be on all width
+ width: 716px;
+ padding: 25px 0px 0px 35px;
+
+ .w-sdc-resource-viewer-right-content {
+ padding: 0 52px 0 0px;
+ margin-bottom: 25px;
+ height: 700px;
+ overflow: hidden;
+ position: relative;
+
+ }
+
+ .w-sdc-resource-viewer-right-tab {
+ .b_6;
+
+ .hand;
+ background-color: transparent;
+ position: relative;
+ font-weight: 500;
+ line-height: 30px;
+ border: none;
+ border-bottom: solid 1px @color_c;
+ vertical-align: middle;
+ padding: 0px 30px 20px 0px;
+
+
+ &:focus,
+ &:active {
+ outline: none;
+
+ }
+ &.selected {
+ outline: none;
+ font-weight: 700;
+ .font-color.a;
+ }
+ }
+
+
+ }
+
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/component-viewer/properties/product-properties-view.html b/catalog-ui/app/scripts/view-models/component-viewer/properties/product-properties-view.html
new file mode 100644
index 0000000000..8aeda603f8
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/component-viewer/properties/product-properties-view.html
@@ -0,0 +1,76 @@
+<div class="w-sdc-component-viewer-right-properties">
+
+ <h4 class="w-sdc-resource-viewer-right-title">General Information</h4>
+ <div class="w-sdc-resource-viewer-right-content-section">
+ <div class='sdc-resource-viewer-sidebar-section-content-column-1'>
+ <div class="sdc-resource-viewer-sidebar-section-content-item">
+ <span class="sdc-resource-viewer-sidebar-section-content-item-label" translate="GENERAL_LABEL_TYPE"></span>
+ <span class="sdc-resource-viewer-sidebar-section-content-item-value" translate="GENERAL_LABEL_PRODUCT"></span>
+ </div>
+ <div class="sdc-resource-viewer-sidebar-section-content-item">
+ <span class="sdc-resource-viewer-sidebar-section-content-item-label" translate="GENERAL_LABEL_VERSION"></span>
+ <span class="sdc-resource-viewer-sidebar-section-content-item-value" data-ng-bind="component.version"></span>
+ </div>
+ <div class="sdc-resource-viewer-sidebar-section-content-item">
+ <span class="sdc-resource-viewer-sidebar-section-content-item-label" translate="GENERAL_LABEL_CATEGORY"></span>
+ <span class="sdc-resource-viewer-sidebar-section-content-item-value" tooltips tooltip-content="{{component.category}}" data-ng-bind="component.category"></span>
+ </div>
+ <div class="sdc-resource-viewer-sidebar-section-content-item">
+ <span class="sdc-resource-viewer-sidebar-section-content-item-label" translate="GENERAL_LABEL_CREATION_DATE"></span>
+ <span class="sdc-resource-viewer-sidebar-section-content-item-value" data-ng-bind="component.creationDate | date: 'MM/dd/yyyy'"></span>
+ </div>
+ <div class="sdc-resource-viewer-sidebar-section-content-item">
+ <span class="sdc-resource-viewer-sidebar-section-content-item-label" translate="GENERAL_LABEL_AUTHOR"></span>
+ <span class="sdc-resource-viewer-sidebar-section-content-item-value" data-ng-bind="component.creatorFullName"></span>
+ </div>
+ <div class="sdc-resource-viewer-sidebar-section-content-item">
+ <span class="sdc-resource-viewer-sidebar-section-content-item-label" translate="GENERAL_LABEL_CONTACT_ID"></span>
+ <span class="sdc-resource-viewer-sidebar-section-content-item-value" data-ng-bind="component.contacts[0]"></span>
+ </div>
+ <div class="sdc-resource-viewer-sidebar-section-content-item">
+ <span class="sdc-resource-viewer-sidebar-section-content-item-label" translate="GENERAL_LABEL_PROJECT_CODE"></span>
+ <span class="sdc-resource-viewer-sidebar-section-content-item-value" data-ng-bind="component.projectCode"></span>
+ </div>
+ <div class="sdc-resource-viewer-sidebar-section-content-item">
+ <span class="sdc-resource-viewer-sidebar-section-content-item-label">Life Cycle Status:</span>
+ <span class="sdc-resource-viewer-sidebar-section-content-item-value">
+ {{sdcMenu.LifeCycleStatuses[component.lifecycleState].text}}
+ </span>
+ </div>
+ <div class="sdc-resource-viewer-sidebar-section-content-item">
+ <span class="sdc-resource-viewer-sidebar-section-content-item-label">Distribution Status:</span>
+ <span class="sdc-resource-viewer-sidebar-section-content-item-value">
+ {{sdcMenu.DistributionStatuses[component.distributionStatus].text}}
+ </span>
+ </div>
+ </div>
+ <div class='sdc-resource-viewer-sidebar-section-content-column-2'>
+ <div class="sdc-resource-viewer-sidebar-section-content-item description">
+ <span class="sdc-resource-viewer-sidebar-section-content-item-label" translate="GENERAL_LABEL_DESCRIPTION"></span>
+ <span class="sdc-resource-viewer-sidebar-section-content-item-value" data-ng-bind="component.description"></span>
+ </div>
+ <div class="sdc-resource-viewer-sidebar-section-content-item" >
+ <span class="sdc-resource-viewer-sidebar-section-content-item-label" translate="GENERAL_LABEL_TAGS"></span>
+ <span tooltips tooltip-content="{{component.tags.join(', ')}}" class="sdc-resource-viewer-sidebar-section-content-tags" data-ng-repeat="(tag, tagName) in component.tags">
+ {{tagName}}{{$last ? '' : ','}}
+ </span>
+ </div>
+ </div>
+ </div>
+ <h4 class="w-sdc-resource-viewer-right-title">Additional Information</h4>
+
+ <div class="sdc-properties-container w-sdc-resource-viewer-right-content-section">
+ <table class="w-sdc-resource-viewer-right-table" data-ng-show="additionalInformations.length">
+ <thead class="w-sdc-resource-viewer-right-table-head">
+ <th class="w-sdc-resource-viewer-right-table-head-cell cols-2">Key</th>
+ <th class="w-sdc-resource-viewer-right-table-head-cell cols-2">Value</th>
+ </thead>
+ <tbody>
+ <tr data-ng-repeat="additionalInformation in additionalInformations">
+ <td><span class="ellipsis-cols2" tooltips tooltip-content="{{additionalInformation.key}}">{{additionalInformation.key}}</span></td>
+ <td><span class="ellipsis-cols2" tooltips tooltip-content="{{additionalInformation.value}}">{{additionalInformation.value}}</span></td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+</div>
diff --git a/catalog-ui/app/scripts/view-models/component-viewer/properties/properties-view.less b/catalog-ui/app/scripts/view-models/component-viewer/properties/properties-view.less
new file mode 100644
index 0000000000..c0beed338f
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/component-viewer/properties/properties-view.less
@@ -0,0 +1,128 @@
+.w-sdc-component-viewer-right-properties {
+ .w-sdc-resource-viewer-tabs {
+ height: 42px;
+ }
+
+ .w-sdc-resource-viewer-right-content-section {
+ margin: 0 0 20px 16px;
+ }
+
+ .sdc-resource-viewer-sidebar-section-content-column-1,
+ .sdc-resource-viewer-sidebar-section-content-column-2 {
+ display: table-cell;
+ width: 50%;
+ }
+ .sdc-resource-viewer-sidebar-section-content-item {
+ .b_7;
+ margin-bottom: 5px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ width: 305px;
+ }
+ .sdc-resource-viewer-sidebar-section-content-item-label {
+ .bold;
+ .g_9;
+ }
+ .w-sdc-resource-viewer-right .sdc-resource-viewer-sidebar-section-content-column-1 {
+ .sdc-resource-viewer-sidebar-section-content-item {
+ width: 390px;
+ }
+ }
+ .sdc-resource-viewer-sidebar-section-content-item.description {
+ margin: 0;
+
+ .sdc-resource-viewer-sidebar-section-content-item-value {
+ word-wrap: break-word;
+ white-space: normal;
+ display: block;
+
+ }
+ .sdc-resource-viewer-sidebar-section-content-tags {
+ word-wrap: break-word;
+ white-space: pre-wrap;
+ display: inline-block;
+ max-width: 167px;
+
+ }
+ }
+
+ .w-sdc-resource-viewer-right-title {
+ .g_1;
+ .bg_n;
+ padding: 7px 15px;
+ margin: 0px 0 25px;
+ font-weight: bold;
+ }
+
+ .w-sdc-resource-viewer-right-table-head-cell {
+ .g_9;
+ text-align: left;
+ }
+
+ .cols-1 {
+ width: 100%;
+ }
+ .cols-2 {
+ width: 50%;
+ }
+ .cols-3 {
+ width: 33%;
+ }
+
+ .sdc-properties-container table tbody td.label {
+ .bold;
+ }
+
+ .w-sdc-designer-sidebar-section-content,
+ .w-sdc-resource-viewer-right-table {
+ display: table;
+ width: 100%;
+ .b_9;
+ word-break: break-all;
+
+ tbody td {
+ padding: 4px 20px 0 0;
+
+ .ellipsis-directive-more-less {
+ display: none;
+ }
+
+ .ellipsis-cols2 {
+ .sdc-ellipsis;
+ max-width: 340px;
+ }
+ .ellipsis-cols3 {
+ .sdc-ellipsis;
+ max-width: 200px;
+ }
+ }
+
+ }
+
+ .i-sdc-designer-sidebar-section-content-column-1 {
+ display: table-cell;
+ width: 50%;
+ }
+
+ .i-sdc-designer-sidebar-section-content-column-2 {
+ display: table-cell;
+ width: 50%;
+ }
+
+ .i-sdc-resource-viewer-artifacts-item-action,
+ .sdc-information-artifacts-icon {
+ .sprite;
+ display: inline-block;
+ width: 20px;
+ height: 20px;
+ cursor: pointer;
+ vertical-align: middle;
+ &.download {
+ .sprite.e-sdc-small-download;
+ }
+ &.preview {
+ .e-sdc-small-icon-eye;
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/component-viewer/properties/resource-properties-view.html b/catalog-ui/app/scripts/view-models/component-viewer/properties/resource-properties-view.html
new file mode 100644
index 0000000000..c02e7aba7e
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/component-viewer/properties/resource-properties-view.html
@@ -0,0 +1,169 @@
+<div class="w-sdc-component-viewer-right-properties">
+
+ <h4 class="w-sdc-resource-viewer-right-title">General Information</h4>
+ <div class="w-sdc-resource-viewer-right-content-section">
+ <div class='sdc-resource-viewer-sidebar-section-content-column-1'>
+ <div class="sdc-resource-viewer-sidebar-section-content-item">
+ <span class="sdc-resource-viewer-sidebar-section-content-item-label" translate="GENERAL_LABEL_TYPE"></span>
+ <span class="sdc-resource-viewer-sidebar-section-content-item-value" >Resource</span>
+ </div>
+ <div class="sdc-resource-viewer-sidebar-section-content-item">
+ <span class="sdc-resource-viewer-sidebar-section-content-item-label" translate="GENERAL_LABEL_RESOURCE_TYPE"></span>
+ <span class="sdc-resource-viewer-sidebar-section-content-item-value" data-ng-bind="component.resourceType | resourceTypeName"></span>
+ </div>
+ <div class="sdc-resource-viewer-sidebar-section-content-item">
+ <span class="sdc-resource-viewer-sidebar-section-content-item-label" translate="GENERAL_LABEL_VERSION"></span>
+ <span class="sdc-resource-viewer-sidebar-section-content-item-value" data-ng-bind="component.version"></span>
+ </div>
+ <div class="sdc-resource-viewer-sidebar-section-content-item">
+ <span class="sdc-resource-viewer-sidebar-section-content-item-label" translate="GENERAL_LABEL_CATEGORY"></span>
+ <span class="sdc-resource-viewer-sidebar-section-content-item-value" tooltips tooltip-content="{{component.categories[0].name}}" data-ng-bind="component.categories[0].name"></span>
+ </div>
+ <div class="sdc-resource-viewer-sidebar-section-content-item">
+ <span class="sdc-resource-viewer-sidebar-section-content-item-label" translate="GENERAL_LABEL_SUB_CATEGORY"></span>
+ <span class="sdc-resource-viewer-sidebar-section-content-item-value" tooltips tooltip-content="{{component.categories[0].subcategories[0].name}}" data-ng-bind="component.categories[0].subcategories[0].name"></span>
+ </div>
+ <div class="sdc-resource-viewer-sidebar-section-content-item">
+ <span class="sdc-resource-viewer-sidebar-section-content-item-label" translate="GENERAL_LABEL_CREATION_DATE"></span>
+ <span class="sdc-resource-viewer-sidebar-section-content-item-value" data-ng-bind="component.creationDate | date: 'MM/dd/yyyy'"></span>
+ </div>
+ <div class="sdc-resource-viewer-sidebar-section-content-item">
+ <span class="sdc-resource-viewer-sidebar-section-content-item-label" translate="GENERAL_LABEL_AUTHOR"></span>
+ <span class="sdc-resource-viewer-sidebar-section-content-item-value" data-ng-bind="component.creatorFullName"></span>
+ </div>
+ <div class="sdc-resource-viewer-sidebar-section-content-item">
+ <span class="sdc-resource-viewer-sidebar-section-content-item-label" translate="GENERAL_LABEL_VENDOR_NAME"></span>
+ <span class="sdc-resource-viewer-sidebar-section-content-item-value" data-ng-bind="component.vendorName"></span>
+ </div>
+ <div class="sdc-resource-viewer-sidebar-section-content-item">
+ <span class="sdc-resource-viewer-sidebar-section-content-item-label" translate="GENERAL_LABEL_VENDOR_RELEASE"></span>
+ <span class="sdc-resource-viewer-sidebar-section-content-item-value" data-ng-bind="component.vendorRelease"></span>
+ </div>
+ <div class="sdc-resource-viewer-sidebar-section-content-item">
+ <span class="sdc-resource-viewer-sidebar-section-content-item-label" translate="GENERAL_LABEL_CONTACT_ID"></span>
+ <span class="sdc-resource-viewer-sidebar-section-content-item-value" data-ng-bind="component.contactId"></span>
+ </div>
+ <div class="sdc-resource-viewer-sidebar-section-content-item">
+ <span class="sdc-resource-viewer-sidebar-section-content-item-label">Life Cycle Status:</span>
+ <span class="sdc-resource-viewer-sidebar-section-content-item-value">
+ {{sdcMenu.LifeCycleStatuses[component.lifecycleState].text}}
+ </span>
+ </div>
+ <div class="sdc-resource-viewer-sidebar-section-content-item">
+ <span class="sdc-resource-viewer-sidebar-section-content-item-label">System Name:</span>
+ <span class="sdc-resource-viewer-sidebar-section-content-item-value" tooltips tooltip-content="{{component.systemName}}" data-ng-bind="component.systemName"></span>
+ </div>
+ <div class="sdc-resource-viewer-sidebar-section-content-item">
+ <span class="sdc-resource-viewer-sidebar-section-content-item-label" translate="GENERAL_LABEL_LICENSE_TYPE"></span>
+ <span class="sdc-resource-viewer-sidebar-section-content-item-value" data-ng-bind="component.licenseType"></span>
+ </div>
+ </div>
+ <div class='sdc-resource-viewer-sidebar-section-content-column-2'>
+ <div class="sdc-resource-viewer-sidebar-section-content-item description">
+ <span class="sdc-resource-viewer-sidebar-section-content-item-label" translate="GENERAL_LABEL_DESCRIPTION"></span>
+ <span class="sdc-resource-viewer-sidebar-section-content-item-value" data-ng-bind="component.description"></span>
+ </div>
+ <div class="sdc-resource-viewer-sidebar-section-content-item">
+ <span class="sdc-resource-viewer-sidebar-section-content-item-label" translate="GENERAL_LABEL_TAGS"></span>
+ <span class="sdc-resource-viewer-sidebar-section-content-tags" data-ng-repeat="(tag, tagName) in component.tags" tooltips tooltip-content="{{component.tags.join(', ')}}">{{tagName}}{{$last ? '' : ','}}</span>
+ </div>
+ </div>
+ </div>
+
+ <h4 class="w-sdc-resource-viewer-right-title">Additional Information</h4>
+
+ <div class="sdc-properties-container w-sdc-resource-viewer-right-content-section">
+ <table class="w-sdc-resource-viewer-right-table" data-ng-show="additionalInformations.length">
+ <thead class="w-sdc-resource-viewer-right-table-head">
+ <th class="w-sdc-resource-viewer-right-table-head-cell cols-2">Key</th>
+ <th class="w-sdc-resource-viewer-right-table-head-cell cols-2">Value</th>
+ </thead>
+ <tbody>
+ <tr data-ng-repeat="additionalInformation in additionalInformations">
+ <td><span class="ellipsis-cols2" tooltips tooltip-content="{{additionalInformation.key}}">{{additionalInformation.key}}</span></td>
+ <td><span class="ellipsis-cols2" tooltips tooltip-content="{{additionalInformation.value}}">{{additionalInformation.value}}</span></td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+
+ <h4 class="w-sdc-resource-viewer-right-title">Properties</h4>
+ <div class="sdc-properties-container w-sdc-resource-viewer-right-content-section">
+ <table class="w-sdc-resource-viewer-right-table" data-ng-show="component.properties.length">
+ <thead class="w-sdc-resource-viewer-right-table-head">
+ <th class="w-sdc-resource-viewer-right-table-head-cell cols-2">Name</th>
+ <th class="w-sdc-resource-viewer-right-table-head-cell cols-3">Type (Constraints)</th>
+ <th class="w-sdc-resource-viewer-right-table-head-cell cols-3">Default Value</th>
+ </thead>
+ <tbody>
+ <tr data-ng-repeat="property in component.properties">
+ <td><span class="ellipsis-cols2" data-tests-id="{{property.name}}" tooltips tooltip-content="{{property.name}}">{{property.name}}</span></td>
+ <td><span data-tests-id="{{property.type}}">{{property.type}}</span></td>
+ <td><span class="ellipsis-cols3" data-tests-id="{{property.defaultValue}}" tooltips tooltip-content="{{property.defaultValue}}">{{property.defaultValue}}</span></td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+
+ <h4 class="w-sdc-resource-viewer-right-title">Requirements</h4>
+ <div class="sdc-requirements-container w-sdc-resource-viewer-right-content-section" >
+ <table class="w-sdc-resource-viewer-right-table" data-ng-show="hasItems(component.requirements)">
+ <thead class="w-sdc-resource-viewer-right-table-head">
+ <th class="w-sdc-resource-viewer-right-table-head-cell cols-1">Type</th>
+ </thead>
+ <tbody>
+ <tr data-ng-repeat="(key, value) in component.requirements">
+ <td>{{value[0].name}}</td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+
+
+ <h4 class="w-sdc-resource-viewer-right-title">Deployment Artifacts</h4>
+
+ <div class="sdc-information-container w-sdc-resource-viewer-right-content-section" >
+ <table class="w-sdc-resource-viewer-right-table" data-ng-show="hasItems(component.deploymentArtifacts)">
+ <thead class="w-sdc-resource-viewer-right-table-head">
+ <th class="w-sdc-resource-viewer-right-table-head-cell cols-2">Name</th>
+ <th class="w-sdc-resource-viewer-right-table-head-cell cols-3">File</th>
+ <th class="w-sdc-resource-viewer-right-table-head-cell cols-3">Version</th>
+ <th class="w-sdc-resource-viewer-right-table-head-cell cols-3"></th>
+ </thead>
+ <tbody>
+ <tr data-ng-repeat="(artifactLogicName, artifact) in component.deploymentArtifacts">
+ <td><span class="ellipsis-cols2" data-tests-id="{{artifact.artifactDisplayName}}" tooltips tooltip-content="{{artifact.artifactDisplayName}}">{{artifact.artifactDisplayName}}</span></td>
+ <td><span class="ellipsis-cols3" data-tests-id="{{artifact.artifactName}}" tooltips tooltip-content="{{artifact.artifactName}}">{{artifact.artifactName}}</span></td>
+ <td><span class="ellipsis-cols3" data-tests-id="{{artifact.artifactVersion}}" tooltips tooltip-content="{{artifact.artifactVersion}}" data-ng-if="artifact.esId">{{artifact.artifactVersion}}</span></td>
+ <td class="cols-3">
+ <download-artifact class="sdc-information-artifacts-icon download" data-ng-if="artifact.artifactName" component="component" artifact="artifact"></download-artifact>
+ <!--span class="sdc-information-artifacts-icon preview"></span-->
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+
+ <h4 class="w-sdc-resource-viewer-right-title">Information Artifacts</h4>
+ <div class="sdc-information-container w-sdc-resource-viewer-right-content-section" >
+ <table class="w-sdc-resource-viewer-right-table" data-ng-show="hasItems(component.artifacts)">
+ <thead class="w-sdc-resource-viewer-right-table-head">
+ <th class="w-sdc-resource-viewer-right-table-head-cell cols-2">Name</th>
+ <th class="w-sdc-resource-viewer-right-table-head-cell cols-3">File</th>
+ <th class="w-sdc-resource-viewer-right-table-head-cell cols-3">Version</th>
+ <th class="w-sdc-resource-viewer-right-table-head-cell cols-3"></th>
+ </thead>
+ <tbody>
+ <tr data-ng-repeat="(artifactLogicName, artifact) in component.artifacts">
+ <td><span class="ellipsis-cols2" data-tests-id="{{artifact.artifactDisplayName}}" tooltips tooltip-content="{{artifact.artifactDisplayName}}">{{artifact.artifactDisplayName}}</span></td>
+ <td><span class="ellipsis-cols3" data-tests-id="{{artifact.artifactName}}" tooltips tooltip-content="{{artifact.artifactName}}">{{artifact.artifactName}}</span></td>
+ <td><span class="ellipsis-cols3" data-tests-id="{{artifact.artifactVersion}}" tooltips tooltip-content="{{artifact.artifactVersion}}" data-ng-if="artifact.esId">{{artifact.artifactVersion}}</span></td>
+ <td class="cols-3">
+ <download-artifact class="sdc-information-artifacts-icon download" data-ng-if="artifact.artifactName" component="component" artifact="artifact"></download-artifact>
+ <!--span class="sdc-information-artifacts-icon preview"></span-->
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+</div>
diff --git a/catalog-ui/app/scripts/view-models/component-viewer/properties/service-properties-view.html b/catalog-ui/app/scripts/view-models/component-viewer/properties/service-properties-view.html
new file mode 100644
index 0000000000..01f872c13c
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/component-viewer/properties/service-properties-view.html
@@ -0,0 +1,167 @@
+<div class="w-sdc-component-viewer-right-properties">
+
+ <h4 class="w-sdc-resource-viewer-right-title">General Information</h4>
+ <div class="w-sdc-resource-viewer-right-content-section">
+ <div class='sdc-resource-viewer-sidebar-section-content-column-1'>
+ <div class="sdc-resource-viewer-sidebar-section-content-item">
+ <span class="sdc-resource-viewer-sidebar-section-content-item-label" translate="GENERAL_LABEL_TYPE"></span>
+ <span class="sdc-resource-viewer-sidebar-section-content-item-value" translate="GENERAL_LABEL_SERVICE"></span>
+ </div>
+ <div class="sdc-resource-viewer-sidebar-section-content-item">
+ <span class="sdc-resource-viewer-sidebar-section-content-item-label" translate="GENERAL_LABEL_VERSION"></span>
+ <span class="sdc-resource-viewer-sidebar-section-content-item-value" data-ng-bind="component.version"></span>
+ </div>
+ <div class="sdc-resource-viewer-sidebar-section-content-item">
+ <span class="sdc-resource-viewer-sidebar-section-content-item-label" translate="GENERAL_LABEL_CATEGORY"></span>
+ <span class="sdc-resource-viewer-sidebar-section-content-item-value" tooltips tooltip-content="{{component.categories[0].name}}" data-ng-bind="component.categories[0].name"></span>
+ </div>
+ <div class="sdc-resource-viewer-sidebar-section-content-item">
+ <span class="sdc-resource-viewer-sidebar-section-content-item-label" translate="GENERAL_LABEL_CREATION_DATE"></span>
+ <span class="sdc-resource-viewer-sidebar-section-content-item-value" data-ng-bind="component.creationDate | date: 'MM/dd/yyyy'"></span>
+ </div>
+ <div class="sdc-resource-viewer-sidebar-section-content-item">
+ <span class="sdc-resource-viewer-sidebar-section-content-item-label" translate="GENERAL_LABEL_AUTHOR"></span>
+ <span class="sdc-resource-viewer-sidebar-section-content-item-value" data-ng-bind="component.creatorFullName"></span>
+ </div>
+ <div class="sdc-resource-viewer-sidebar-section-content-item">
+ <span class="sdc-resource-viewer-sidebar-section-content-item-label" translate="GENERAL_LABEL_CONTACT_ID"></span>
+ <span class="sdc-resource-viewer-sidebar-section-content-item-value" data-ng-bind="component.contactId"></span>
+ </div>
+ <div class="sdc-resource-viewer-sidebar-section-content-item">
+ <span class="sdc-resource-viewer-sidebar-section-content-item-label" translate="GENERAL_LABEL_PROJECT_CODE"></span>
+ <span class="sdc-resource-viewer-sidebar-section-content-item-value" data-ng-bind="component.projectCode"></span>
+ </div>
+ <div class="sdc-resource-viewer-sidebar-section-content-item">
+ <span class="sdc-resource-viewer-sidebar-section-content-item-label">Life Cycle Status:</span>
+ <span class="sdc-resource-viewer-sidebar-section-content-item-value"> {{sdcMenu.LifeCycleStatuses[component.lifecycleState].text}}</span>
+ </div>
+ <div class="sdc-resource-viewer-sidebar-section-content-item">
+ <span class="sdc-resource-viewer-sidebar-section-content-item-label">Distribution Status:</span>
+ <span class="sdc-resource-viewer-sidebar-section-content-item-value">{{sdcMenu.DistributionStatuses[component.distributionStatus].text}}</span>
+ </div>
+
+ <div class="sdc-resource-viewer-sidebar-section-content-item">
+ <span class="sdc-resource-viewer-sidebar-section-content-item-label">System Name:</span>
+ <span class="sdc-resource-viewer-sidebar-section-content-item-value" tooltips tooltip-content="{{component.systemName}}" data-ng-bind="component.systemName"></span>
+ </div>
+ </div>
+ <div class='sdc-resource-viewer-sidebar-section-content-column-2'>
+ <div class="sdc-resource-viewer-sidebar-section-content-item description">
+ <span class="sdc-resource-viewer-sidebar-section-content-item-label" translate="GENERAL_LABEL_DESCRIPTION"></span>
+ <span class="sdc-resource-viewer-sidebar-section-content-item-value" data-ng-bind="component.description"></span>
+ </div>
+ <div class="sdc-resource-viewer-sidebar-section-content-item" >
+ <span class="sdc-resource-viewer-sidebar-section-content-item-label" translate="GENERAL_LABEL_TAGS"></span>
+ <span tooltips tooltip-content="{{component.tags.join(', ')}}" class="sdc-resource-viewer-sidebar-section-content-tags" data-ng-repeat="(tag, tagName) in component.tags">{{tagName}}{{$last ? '' : ','}}</span>
+ </div>
+ </div>
+ </div>
+ <h4 class="w-sdc-resource-viewer-right-title">Additional Information</h4>
+
+ <div class="sdc-properties-container w-sdc-resource-viewer-right-content-section">
+ <table class="w-sdc-resource-viewer-right-table" data-ng-show="additionalInformations.length">
+ <thead class="w-sdc-resource-viewer-right-table-head">
+ <th class="w-sdc-resource-viewer-right-table-head-cell cols-2">Key</th>
+ <th class="w-sdc-resource-viewer-right-table-head-cell cols-2">Value</th>
+ </thead>
+ <tbody>
+ <tr data-ng-repeat="additionalInformation in additionalInformations">
+ <td><span class="ellipsis-cols2" tooltips tooltip-content="{{additionalInformation.key}}">{{additionalInformation.key}}</span></td>
+ <td><span class="ellipsis-cols2" tooltips tooltip-content="{{additionalInformation.value}}">{{additionalInformation.value}}</span></td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+
+ <h4 class="w-sdc-resource-viewer-right-title">Inputs</h4>
+
+ <div class="sdc-properties-container w-sdc-resource-viewer-right-content-section">
+ <table class="w-sdc-resource-viewer-right-table" data-ng-show="inputs.length">
+ <thead class="w-sdc-resource-viewer-right-table-head">
+ <th class="w-sdc-resource-viewer-right-table-head-cell cols-2">Name</th>
+ <th class="w-sdc-resource-viewer-right-table-head-cell cols-3">Type (Constraints)</th>
+ <th class="w-sdc-resource-viewer-right-table-head-cell cols-3">Default Value</th>
+ </thead>
+ <tbody>
+ <tr data-ng-repeat="input in inputs">
+ <td><span class="ellipsis-cols2" data-tests-id="{{input.name}}" tooltips tooltip-content="{{input.name}}">{{input.name}}</span></td>
+ <td>{{input.type}}</td>
+ <td><span class="ellipsis-cols3" data-tests-id="{{input.value}}" tooltips tooltip-content="{{input.value}}">{{input.value}}</span></td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+
+ <h4 class="w-sdc-resource-viewer-right-title">API Artifacts</h4>
+
+ <div class="sdc-requirements-container w-sdc-resource-viewer-right-content-section">
+ <table class="w-sdc-resource-viewer-right-table" data-ng-show="hasItems(component.serviceApiArtifacts)">
+ <thead class="w-sdc-resource-viewer-right-table-head">
+ <th class="w-sdc-resource-viewer-right-table-head-cell cols-2">Name</th>
+ <th class="w-sdc-resource-viewer-right-table-head-cell cols-3">File</th>
+ <th class="w-sdc-resource-viewer-right-table-head-cell cols-3">Version</th>
+ <th class="w-sdc-resource-viewer-right-table-head-cell cols-3"></th>
+ </thead>
+ <tbody>
+ <tr data-ng-repeat="(artifactLogicName, artifact) in component.serviceApiArtifacts">
+ <td><span class="ellipsis-cols2" data-tests-id="{{artifact.artifactDisplayName}}" tooltips tooltip-content="{{artifact.artifactDisplayName}}">{{artifact.artifactDisplayName}}</span></td>
+ <td><span class="ellipsis-cols3" data-tests-id="{{artifact.artifactName}}" tooltips tooltip-content="{{artifact.artifactName}}">{{artifact.artifactName}}</span></td>
+ <td><span class="ellipsis-cols3" data-tests-id="{{artifact.artifactVersion}}" tooltips tooltip-content="{{artifact.artifactVersion}}" data-ng-if="artifact.esId">{{artifact.artifactVersion}}</span></td>
+ <td class="cols-3">
+ <download-artifact class="sdc-information-artifacts-icon download" data-ng-if="artifact.artifactName" component="component" artifact="artifact"></download-artifact>
+ <!--span class="sdc-information-artifacts-icon preview"></span-->
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+
+ <h4 class="w-sdc-resource-viewer-right-title">Deployment Artifacts</h4>
+
+ <div class="sdc-information-container w-sdc-resource-viewer-right-content-section">
+ <table class="w-sdc-resource-viewer-right-table" data-ng-show="hasItems(component.deploymentArtifacts)">
+ <thead class="w-sdc-resource-viewer-right-table-head">
+ <th class="w-sdc-resource-viewer-right-table-head-cell cols-2">Name</th>
+ <th class="w-sdc-resource-viewer-right-table-head-cell cols-3">File</th>
+ <th class="w-sdc-resource-viewer-right-table-head-cell cols-3">Version</th>
+ <th class="w-sdc-resource-viewer-right-table-head-cell cols-3"></th>
+ </thead>
+ <tbody>
+ <tr data-ng-repeat="(artifactLogicName, artifact) in component.deploymentArtifacts">
+ <td><span class="ellipsis-cols2" data-tests-id="{{artifact.artifactDisplayName}}" tooltips tooltip-content="{{artifact.artifactDisplayName}}">{{artifact.artifactDisplayName}}</span></td>
+ <td><span class="ellipsis-cols3" data-tests-id="{{artifact.artifactName}}" tooltips tooltip-content="{{artifact.artifactName}}">{{artifact.artifactName}}</span></td>
+ <td><span class="ellipsis-cols3" data-tests-id="{{artifact.artifactVersion}}" tooltips tooltip-content="{{artifact.artifactVersion}}" data-ng-if="artifact.esId">{{artifact.artifactVersion}}</span></td>
+ <td class="cols-3">
+ <download-artifact class="sdc-information-artifacts-icon download" data-ng-if="artifact.artifactName" component="component" artifact="artifact"></download-artifact>
+ <!--span class="sdc-information-artifacts-icon preview"></span-->
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+
+
+ <h4 class="w-sdc-resource-viewer-right-title">Information Artifacts</h4>
+
+ <div class="sdc-information-container w-sdc-resource-viewer-right-content-section">
+ <table class="w-sdc-resource-viewer-right-table" data-ng-show="hasItems(component.artifacts)">
+ <thead class="w-sdc-resource-viewer-right-table-head">
+ <th class="w-sdc-resource-viewer-right-table-head-cell cols-2">Name</th>
+ <th class="w-sdc-resource-viewer-right-table-head-cell cols-3">File</th>
+ <th class="w-sdc-resource-viewer-right-table-head-cell cols-3">Version</th>
+ <th class="w-sdc-resource-viewer-right-table-head-cell cols-3"></th>
+ </thead>
+ <tbody>
+ <tr data-ng-repeat="(artifactLogicName, artifact) in component.artifacts">
+ <td><span class="ellipsis-cols2" data-tests-id="{{artifact.artifactDisplayName}}" tooltips tooltip-content="{{artifact.artifactDisplayName}}">{{artifact.artifactDisplayName}}</span></td>
+ <td><span class="ellipsis-cols3" data-tests-id="{{artifact.artifactName}}" tooltips tooltip-content="{{artifact.artifactName}}">{{artifact.artifactName}}</span></td>
+ <td><span class="ellipsis-cols3" data-tests-id="{{artifact.artifactVersion}}" tooltips tooltip-content="{{artifact.artifactVersion}}" data-ng-if="artifact.esId">{{artifact.artifactVersion}}</span></td>
+ <td class="cols-3">
+ <download-artifact class="sdc-information-artifacts-icon download" data-ng-if="artifact.artifactName" component="component" artifact="artifact"></download-artifact>
+ <!--span class="sdc-information-artifacts-icon preview"></span-->
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+</div>
diff --git a/catalog-ui/app/scripts/view-models/dashboard/cover/dashboard-cover-view-model.ts b/catalog-ui/app/scripts/view-models/dashboard/cover/dashboard-cover-view-model.ts
new file mode 100644
index 0000000000..9979b6451b
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/dashboard/cover/dashboard-cover-view-model.ts
@@ -0,0 +1,91 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../../references"/>
+module Sdc.ViewModels {
+
+ 'use strict';
+
+ export interface IDashboardCoverViewModelScope extends ng.IScope {
+ showTutorial:boolean;
+ version:string;
+ modalInstance:ng.ui.bootstrap.IModalServiceInstance;
+ }
+
+ export class DashboardCoverViewModel {
+ static '$inject' = [
+ '$scope',
+ '$stateParams',
+ 'Sdc.Services.CacheService',
+ '$templateCache',
+ '$state',
+ '$modal',
+ 'sdcConfig'
+ ];
+
+ constructor(private $scope:IDashboardCoverViewModelScope,
+ private $stateParams:any,
+ private cacheService:Services.CacheService,
+ private $templateCache:ng.ITemplateCacheService,
+ private $state:any,
+ private $modal:ng.ui.bootstrap.IModalService,
+ private sdcConfig:Models.IAppConfigurtaion) {
+
+ // Show the tutorial if needed when the dashboard page is opened.<script src="bower_components/angular-filter/dist/angular-filter.min.js"></script>
+ // This is called from the welcome page.
+ if (this.$stateParams.show === 'tutorial') {
+ this.$scope.showTutorial = true;
+ } else if (this.$stateParams.show === 'whatsnew') {
+ this.$scope.version = this.cacheService.get('version');
+ this.openWhatsNewModal(this.$scope);
+ }
+
+ this.initScope();
+ }
+
+ private initScope = ():void => {
+
+ };
+
+ private openWhatsNewModal = (scope:IDashboardCoverViewModelScope):void => {
+
+ let onOk = ():void => {};
+
+ let onCancel = ():void => {
+ this.$state.go('dashboard.welcome', {show: ''});
+ };
+
+ let modalOptions:ng.ui.bootstrap.IModalSettings = {
+ template: this.$templateCache.get('/app/scripts/view-models/whats-new/whats-new-view.html'),
+ controller: 'Sdc.ViewModels.WhatsNewViewModel',
+ size: 'sdc-l',
+ backdrop: 'static',
+ scope: scope,
+ resolve: {
+ 'version': scope.version
+ }
+ };
+
+ scope.modalInstance = this.$modal.open(modalOptions);
+ scope.modalInstance.result.then(onOk, onCancel);
+ };
+
+ }
+
+}
diff --git a/catalog-ui/app/scripts/view-models/dashboard/cover/dashboard-cover-view.html b/catalog-ui/app/scripts/view-models/dashboard/cover/dashboard-cover-view.html
new file mode 100644
index 0000000000..c8657cba23
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/dashboard/cover/dashboard-cover-view.html
@@ -0,0 +1 @@
+<div class="sdc-welcome-page"></div>
diff --git a/catalog-ui/app/scripts/view-models/dashboard/dashboard-view-model-tests.ts b/catalog-ui/app/scripts/view-models/dashboard/dashboard-view-model-tests.ts
new file mode 100644
index 0000000000..d97d9bb5ec
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/dashboard/dashboard-view-model-tests.ts
@@ -0,0 +1,276 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../references"/>
+
+describe("dashboard View Model ", () => {
+
+ let $controllerMock:ng.IControllerService;
+ let $qMock:ng.IQService;
+ let $httpBackendMock:ng.IHttpBackendService;
+ let $scopeMock:Sdc.ViewModels.IDashboardViewModelScope;
+ let $stateMock:ng.ui.IStateService;
+ let $stateParams:any;
+ let entityServiceMock;
+
+
+ let getAllEntitiesResponseMock = [
+ {
+ "uniqueId": "855acdc7-7976-4913-9fa6-25220bd5a069",
+ "uuid": "8bc54f94-082c-42fa-9049-84767df3ff05",
+ "contactId": "qa1234",
+ "category": "VoIP Call Control",
+ "creationDate": 1447234712398,
+ "description": "ddddd",
+ "highestVersion": true,
+ "icon": "mobility",
+ "lastUpdateDate": 1447234712398,
+ "lastUpdaterUserId": "cs0008",
+ "lastUpdaterFullName": "Carlos Santana",
+ "lifecycleState": "NOT_CERTIFIED_CHECKOUT",
+ "distributionStatus": "DISTRIBUTION_NOT_APPROVED",
+ "projectCode": "233233",
+ "name": "mas mas mas mas mas mas mas mas mas mas mas mas ma",
+ "version": "0.1",
+ "type": 0,
+ "tags": [
+ "mas mas mas mas mas mas mas mas mas mas mas mas ma"
+ ],
+ "systemName": "MasMasMasMasMasMasMasMasMasMasMasMasMa",
+ "vnf": true,
+ "$$hashKey": "object:30"
+ },
+ {
+ "uniqueId": "4bb577ce-cb2c-4cb7-bb39-58644b5e73cb",
+ "uuid": "e27f4723-c9ec-4160-89da-dbf84d19a7e3",
+ "contactId": "qa1111",
+ "category": "Mobility",
+ "creationDate": 1447238503181,
+ "description": "aqa",
+ "highestVersion": true,
+ "icon": "call_controll",
+ "lastUpdateDate": 1447248991388,
+ "lastUpdaterUserId": "jm0007",
+ "lastUpdaterFullName": "Joni Mitchell",
+ "lifecycleState": "CERTIFIED",
+ "distributionStatus": "DISTRIBUTION_REJECTED",
+ "projectCode": "111111",
+ "name": "martin18",
+ "version": "1.0",
+ "type": 0,
+ "tags": [
+ "martin18"
+ ],
+ "systemName": "Martin18",
+ "vnf": true
+ },
+ {
+ "uniqueId": "f192f4a6-7fbf-42e4-a546-37509df28dc1",
+ "uuid": "0b77dc0d-222e-4d10-85cd-e420c9481417",
+ "contactId": "fd1212",
+ "category": "Application Layer 4+/Web Server",
+ "creationDate": 1447233679778,
+ "description": "geefw",
+ "highestVersion": true,
+ "icon": "database",
+ "lastUpdateDate": 1447233681582,
+ "lastUpdaterUserId": "cs0008",
+ "lastUpdaterFullName": "Carlos Santana",
+ "lifecycleState": "NOT_CERTIFIED_CHECKOUT",
+ "name": "ger",
+ "version": "0.1",
+ "type": 1,
+ "tags": [
+ "ger"
+ ],
+ "vendorName": "fewwfe",
+ "vendorRelease": "fewew",
+ "systemName": "Ger",
+ "$$hashKey": "object:31"
+ },
+ {
+ "uniqueId": "78392d08-1859-47c2-b1f2-1a35b7f8c30e",
+ "uuid": "8cdd63b2-6a62-4376-9012-624f424f71d4",
+ "contactId": "qw1234",
+ "category": "Application Layer 4+/Application Servers",
+ "creationDate": 1447234046114,
+ "description": "test",
+ "highestVersion": true,
+ "icon": "router",
+ "lastUpdateDate": 1447234050545,
+ "lastUpdaterUserId": "cs0008",
+ "lastUpdaterFullName": "Carlos Santana",
+ "lifecycleState": "NOT_CERTIFIED_CHECKOUT",
+ "name": "test",
+ "version": "0.1",
+ "type": 1,
+ "tags": [
+ "test"
+ ],
+ "vendorName": "test",
+ "vendorRelease": "test",
+ "systemName": "Test",
+ "$$hashKey": "object:32"
+ },
+ {
+ "uniqueId": "939e153d-2236-410f-b4a9-3b4bf8c79c9e",
+ "uuid": "84862547-4f56-4058-b78e-40df5f374d7e",
+ "contactId": "qw1234",
+ "category": "Application Layer 4+/Application Servers",
+ "creationDate": 1447235242560,
+ "description": "jlk",
+ "highestVersion": true,
+ "icon": "database",
+ "lastUpdateDate": 1447235328062,
+ "lastUpdaterUserId": "cs0008",
+ "lastUpdaterFullName": "Carlos Santana",
+ "lifecycleState": "NOT_CERTIFIED_CHECKIN",
+ "name": "new",
+ "version": "0.1",
+ "type": 1,
+ "tags": [
+ "new"
+ ],
+ "vendorName": "e",
+ "vendorRelease": "e",
+ "systemName": "New",
+ "$$hashKey": "object:33"
+ },
+ {
+ "uniqueId": "ece818e0-fd59-477a-baf6-e27461a7ce23",
+ "uuid": "8db823c2-6a9c-4636-8676-f5e713270dd7",
+ "contactId": "uf2345",
+ "category": "Network Layer 2-3/Router",
+ "creationDate": 1447235352429,
+ "description": "u",
+ "highestVersion": true,
+ "icon": "network",
+ "lastUpdateDate": 1447235370064,
+ "lastUpdaterUserId": "cs0008",
+ "lastUpdaterFullName": "Carlos Santana",
+ "lifecycleState": "NOT_CERTIFIED_CHECKOUT",
+ "name": "u",
+ "version": "0.1",
+ "type": 1,
+ "tags": [
+ "u"
+ ],
+ "vendorName": "u",
+ "vendorRelease": "u",
+ "systemName": "U",
+ "$$hashKey": "object:34"
+ }
+ ];
+ let getAllEntitiesDefered:ng.IDeferred<any> = null;
+
+ beforeEach(angular.mock.module('sdcApp'));
+
+ beforeEach(angular.mock.inject((_$controller_:ng.IControllerService,
+ _$httpBackend_:ng.IHttpBackendService,
+ _$rootScope_,
+ _$q_:ng.IQService,
+ _$state_:ng.ui.IStateService,
+ _$stateParams_:any) => {
+
+ $controllerMock = _$controller_;
+ $httpBackendMock = _$httpBackend_
+ $scopeMock = _$rootScope_.$new();
+ $qMock = _$q_;
+ $stateMock = _$state_;
+ $stateParams = _$stateParams_;
+
+
+ //handle all http request thet not relevant to the tests
+ $httpBackendMock.expectGET(/.*languages\/en_US.json.*/).respond(200, JSON.stringify({}));
+ // $httpBackendMock.expectGET(/.*resources\/certified\/abstract.*/).respond(200, JSON.stringify({}));
+ $httpBackendMock.expectGET(/.*rest\/version.*/).respond(200, JSON.stringify({}));
+ $httpBackendMock.expectGET(/.*configuration\/ui.*/).respond(200, JSON.stringify({}));
+ $httpBackendMock.expectGET(/.*user\/authorize.*/).respond(200, JSON.stringify({}));
+ $httpBackendMock.expectGET(/.*categories\/services.*/).respond(200, JSON.stringify({}));
+ $httpBackendMock.expectGET(/.*categories\/resources.*/).respond(200, JSON.stringify({}));
+ $httpBackendMock.expectGET(/.*categories\/products.*/).respond(200, JSON.stringify({}));
+ $httpBackendMock.expectGET('http://feHost:8181/sdc1/feProxy/rest/version').respond(200, JSON.stringify({}));
+
+ /**
+ * Mock the service
+ * @type {any}
+ */
+ getAllEntitiesDefered = $qMock.defer();
+ getAllEntitiesDefered.resolve(getAllEntitiesResponseMock);
+ entityServiceMock = jasmine.createSpyObj('entityServiceMock', ['getAllComponents']);
+ entityServiceMock.getAllComponents.and.returnValue(getAllEntitiesDefered.promise);
+
+ // $stateParams['show'] = '';
+
+ /**
+ * Need to inject into the controller only the objects that we want to MOCK
+ * those that we need to change theirs behaviors
+ */
+ $controllerMock(Sdc.ViewModels.DashboardViewModel, {
+ '$scope': $scopeMock,
+ '$stateParams': $stateParams,
+ 'Sdc.Services.EntityService': entityServiceMock,
+ });
+
+ }));
+
+
+ describe("when Controller 'DashboardViewModel' created", () => {
+
+ it('should generate all entities', () => {
+ $scopeMock.$apply();
+ expect($scopeMock.components.length).toBe(getAllEntitiesResponseMock.length);
+ });
+
+
+ it('should show tutorial page ', () => {
+ $stateParams.show = 'tutorial';
+
+ $controllerMock(Sdc.ViewModels.DashboardViewModel, {
+ '$scope': $scopeMock,
+ '$stateParams': $stateParams,
+ 'Sdc.Services.EntityService': entityServiceMock,
+ //to complete injects
+ });
+
+ $scopeMock.$apply();
+ expect($scopeMock.isFirstTime).toBeTruthy();
+ expect($scopeMock.showTutorial).toBeTruthy();
+ });
+
+ });
+
+
+ describe("when function 'entitiesCount' invoked", () => {
+
+ beforeEach(() => {
+ $controllerMock(Sdc.ViewModels.DashboardViewModel, {
+ '$scope': $scopeMock,
+ 'Sdc.Services.EntityService': entityServiceMock,
+ });
+ $scopeMock.$apply();
+ });
+
+ it('should return entities count per folder', () => {
+
+ });
+
+
+ });
+});
diff --git a/catalog-ui/app/scripts/view-models/dashboard/dashboard-view-model.ts b/catalog-ui/app/scripts/view-models/dashboard/dashboard-view-model.ts
new file mode 100644
index 0000000000..8325a3f133
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/dashboard/dashboard-view-model.ts
@@ -0,0 +1,415 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../references"/>
+module Sdc.ViewModels {
+
+ 'use strict';
+ import ResourceType = Sdc.Utils.Constants.ResourceType;
+
+ export interface IDashboardViewModelScope extends ng.IScope {
+
+ isLoading: boolean;
+ components: Array<Models.Components.Component>;
+ folders: FoldersMenu;
+ roles: Models.IConfigRoles;
+ user: Models.IUserProperties;
+ sdcConfig:Models.IAppConfigurtaion;
+ sdcMenu:Models.IAppMenu;
+ sharingService:Sdc.Services.SharingService;
+ showTutorial:boolean;
+ isFirstTime:boolean;
+ version:string;
+ checkboxesFilter:CheckboxesFilter;
+
+ onImportVfc(file:any):void;
+ onImportVf(file:any):void;
+ openCreateModal(componentType: Utils.Constants.ComponentType, importedFile:any): void;
+ openWhatsNewModal(version:string):void;
+ openDesignerModal(isResource:boolean, uniqueId:string): void;
+ openViewerModal(entity:any) : void;
+ setSelectedFolder(folderItem: FoldersItemsMenu): void;
+ entitiesCount(folderItem: FoldersItemsMenu): number;
+ getCurrentFolderDistributed(): Array<Models.Components.Component>;
+ changeLifecycleState(entity:any, data:any): void;
+ goToComponent(component:Models.Components.Component):void;
+ wizardDebugEdit:Function;
+ notificationIconCallback:Function;
+ }
+
+ interface CheckboxesFilter {
+ // Statuses
+ selectedStatuses:Array<string>;
+ // distributed
+ distributed:Array<string>;
+ }
+
+ export interface IItemMenu {
+
+ }
+
+ export interface IMenuItemProperties {
+ text: string;
+ group: string;
+ state: string;
+ dist: string;
+ groupname: string;
+ states: Array<any>;
+ }
+
+ export class FoldersMenu {
+
+ private _folders: Array<FoldersItemsMenu> = [];
+
+ constructor(folders: Array<IMenuItemProperties>) {
+ let self = this;
+ folders.forEach(function(folder: IMenuItemProperties) {
+ if (folder.groupname){
+ self._folders.push(new FoldersItemsMenuGroup(folder));
+ } else {
+ self._folders.push(new FoldersItemsMenu(folder));
+ }
+ });
+ self._folders[0].setSelected(true);
+ }
+
+ public getFolders = (): Array<FoldersItemsMenu> => {
+ return this._folders;
+ };
+
+ public getCurrentFolder = (): FoldersItemsMenu => {
+ let menuItem: FoldersItemsMenu = undefined;
+ this.getFolders().forEach(function(tmpFolder: FoldersItemsMenu) {
+ if (tmpFolder.isSelected()){
+ menuItem = tmpFolder;
+ }
+ });
+ return menuItem;
+ };
+
+ public setSelected = (folder: FoldersItemsMenu):void => {
+ this.getFolders().forEach(function(tmpFolder: FoldersItemsMenu) {
+ tmpFolder.setSelected(false);
+ });
+ folder.setSelected(true);
+ }
+
+ }
+
+ export class FoldersItemsMenu implements IItemMenu {
+
+ public text:string;
+ public group: string;
+ public state: string;
+ public dist: string;
+ public states: Array<any>;
+
+ private selected: boolean = false;
+
+ constructor(menuProperties: IMenuItemProperties) {
+ this.text = menuProperties.text;
+ this.group = menuProperties.group;
+ this.state = menuProperties.state;
+ this.states = menuProperties.states;
+ this.dist = menuProperties.dist;
+ }
+
+ public isSelected = ():boolean => {
+ return this.selected;
+ };
+
+ public setSelected = (value: boolean):void => {
+ this.selected = value;
+ };
+
+ public isGroup = ():boolean => {
+ return false;
+ }
+
+ }
+
+ export class FoldersItemsMenuGroup extends FoldersItemsMenu {
+
+ public groupname:string;
+
+ constructor(menuProperties: IMenuItemProperties) {
+ super(menuProperties);
+ this.groupname = menuProperties.groupname;
+ }
+
+ public isGroup = ():boolean => {
+ return true;
+ }
+
+ }
+
+ export class DashboardViewModel {
+ static '$inject' = [
+ '$scope',
+ '$filter',
+ 'Sdc.Services.EntityService',
+ '$http',
+ 'sdcConfig',
+ 'sdcMenu',
+ '$modal',
+ '$templateCache',
+ '$state',
+ '$stateParams',
+ 'Sdc.Services.UserResourceService',
+ 'Sdc.Services.SharingService',
+ 'Sdc.Services.CacheService',
+ '$q',
+ 'ComponentFactory',
+ 'ChangeLifecycleStateHandler',
+ 'ModalsHandler',
+ 'MenuHandler'
+ ];
+
+ private components: Array<Models.Components.Component>;
+
+ constructor(private $scope:IDashboardViewModelScope,
+ private $filter:ng.IFilterService,
+ private entityService:Services.EntityService,
+ private $http:ng.IHttpService,
+ private sdcConfig:Models.IAppConfigurtaion,
+ private sdcMenu:Models.IAppMenu,
+ private $modal:ng.ui.bootstrap.IModalService,
+ private $templateCache:ng.ITemplateCacheService,
+ private $state:any,
+ private $stateParams:any,
+ private userResourceService:Sdc.Services.IUserResourceClass,
+ private sharingService:Services.SharingService,
+ private cacheService:Services.CacheService,
+ private $q:ng.IQService,
+ private ComponentFactory: Sdc.Utils.ComponentFactory,
+ private ChangeLifecycleStateHandler: Sdc.Utils.ChangeLifecycleStateHandler,
+ private ModalsHandler: Sdc.Utils.ModalsHandler,
+ private MenuHandler: Utils.MenuHandler
+ ) {
+ this.initScope();
+ this.initFolders();
+ this.initEntities(true);
+
+ if (this.$stateParams){
+
+ if (this.$state.params.folder){
+ let self = this;
+ let folderName = this.$state.params.folder.replaceAll("_"," ");
+
+ this.$scope.folders.getFolders().forEach(function(tmpFolder: FoldersItemsMenu) {
+ if (tmpFolder.text === folderName){
+ self.$scope.setSelectedFolder(tmpFolder);
+ }
+ });
+ }
+
+ // Show the tutorial if needed when the dashboard page is opened.<script src="bower_components/angular-filter/dist/angular-filter.min.js"></script>
+ // This is called from the welcome page.
+ else if (this.$stateParams.show==='tutorial'){
+ this.$scope.showTutorial = true;
+ this.$scope.isFirstTime = true;
+ }
+ }
+ }
+
+ private initFolders = ():void => {
+ if (this.$scope.user) {
+ this.$scope.folders = new FoldersMenu(this.$scope.roles[this.$scope.user.role].folder);
+ }
+ };
+
+ private initScope = ():void => {
+ let self = this;
+
+ this.$scope.version = this.cacheService.get('version');
+ this.$scope.sharingService = this.sharingService;
+ this.$scope.isLoading = false;
+ this.$scope.sdcConfig = this.sdcConfig;
+ this.$scope.sdcMenu = this.sdcMenu;
+ this.$scope.user = this.userResourceService.getLoggedinUser();
+ this.$scope.roles = this.sdcMenu.roles;
+ this.$scope.showTutorial = false;
+ this.$scope.isFirstTime = false;
+
+ // Open onboarding modal
+ this.$scope.notificationIconCallback = ():void => {
+ this.ModalsHandler.openOnboadrdingModal('Import').then(()=>{
+ // OK
+ }, ()=>{
+ // ERROR
+ });
+ };
+
+ // Checkboxes filter init
+ this.$scope.checkboxesFilter = <CheckboxesFilter>{};
+ this.$scope.checkboxesFilter.selectedStatuses = [];
+ this.$scope.checkboxesFilter.distributed = [];
+
+ let appendTemplateAndControllerForProduct:Function = (modalOptions:ng.ui.bootstrap.IModalSettings, isViewer:boolean):void => {
+ let viewModelsHtmlBasePath:string = '/app/scripts/view-models/';
+
+ if (isViewer) {
+ modalOptions.template = this.$templateCache.get(viewModelsHtmlBasePath + 'entity-viewer/product-viewer-view.html');
+ modalOptions.controller = 'Sdc.ViewModels.ResourceViewerViewModel';
+ } else {
+ modalOptions.template = this.$templateCache.get(viewModelsHtmlBasePath + 'entity-handler/product-form/product-form-view.html');
+ modalOptions.controller = 'Sdc.ViewModels.ProductFormViewModel';
+ }
+
+ };
+
+ this.$scope.onImportVf = (file:any):void => {
+ if(file && file.filename) {
+ // Check that the file has valid extension.
+ let fileExtension:string = file.filename.split(".").pop();
+ if (this.sdcConfig.csarFileExtension.indexOf(fileExtension.toLowerCase()) !== -1){
+ this.$state.go('workspace.general', {type:Utils.Constants.ComponentType.RESOURCE.toLowerCase(), importedFile: file, resourceType: ResourceType.VF});
+ }else {
+ let data:Sdc.ViewModels.IClientMessageModalModel = {
+ title: self.$filter('translate')("NEW_SERVICE_RESOURCE_ERROR_VALID_CSAR_EXTENSIONS_TITLE"),
+ message: self.$filter('translate')("NEW_SERVICE_RESOURCE_ERROR_VALID_CSAR_EXTENSIONS", "{'extensions': '" + this.sdcConfig.csarFileExtension + "'}"),
+ severity: Utils.Constants.SEVERITY.ERROR
+ };
+ this.ModalsHandler.openClientMessageModal(data);
+ }
+ }
+ };
+
+ this.$scope.onImportVfc = (file:any):void => {
+ if(file && file.filename) {
+ // Check that the file has valid extension.
+ let fileExtension:string = file.filename.split(".").pop();
+ if (this.sdcConfig.toscaFileExtension.indexOf(fileExtension.toLowerCase()) !== -1){
+ this.$state.go('workspace.general', {type:Utils.Constants.ComponentType.RESOURCE.toLowerCase(), importedFile: file, resourceType: ResourceType.VFC});
+ }else {
+ let data:Sdc.ViewModels.IClientMessageModalModel = {
+ title: self.$filter('translate')("NEW_SERVICE_RESOURCE_ERROR_VALID_TOSCA_EXTENSIONS_TITLE"),
+ message: self.$filter('translate')("NEW_SERVICE_RESOURCE_ERROR_VALID_TOSCA_EXTENSIONS", "{'extensions': '" + this.sdcConfig.toscaFileExtension + "'}"),
+ severity: Utils.Constants.SEVERITY.ERROR
+ };
+ this.ModalsHandler.openClientMessageModal(data);
+ }
+ }
+ };
+
+ this.$scope.openCreateModal = (componentType: string, importedFile:any):void => {
+ if (importedFile){
+ this.initEntities(true); // Return from import
+ } else {
+ this.$state.go('workspace.general', {type:componentType.toLowerCase()});
+ }
+
+ };
+
+ this.$scope.entitiesCount = (folderItem: FoldersItemsMenu): any => {
+ let self = this;
+ let total: number = 0;
+ if (folderItem.isGroup()){
+ this.$scope.folders.getFolders().forEach(function(tmpFolder: FoldersItemsMenu){
+ if (tmpFolder.group && tmpFolder.group===(<FoldersItemsMenuGroup>folderItem).groupname){
+ total = total + self._getTotalCounts(tmpFolder, self);
+ }
+ });
+ } else {
+ total = total + self._getTotalCounts(folderItem, self);
+ }
+ return total;
+ };
+
+ this.$scope.getCurrentFolderDistributed = (): Array<any> => {
+ let self = this;
+ let states = [];
+ if (this.$scope.folders) {
+ let folderItem:FoldersItemsMenu = this.$scope.folders.getCurrentFolder();
+ if (folderItem.isGroup()) {
+ this.$scope.folders.getFolders().forEach(function (tmpFolder:FoldersItemsMenu) {
+ if (tmpFolder.group && tmpFolder.group === (<FoldersItemsMenuGroup>folderItem).groupname) {
+ self._setStates(tmpFolder, states);
+ }
+ });
+ } else {
+ self._setStates(folderItem, states);
+ }
+ }
+ return states;
+ };
+
+ this.$scope.setSelectedFolder = (folderItem: FoldersItemsMenu):void => {
+ this.$scope.folders.setSelected(folderItem);
+ };
+
+ this.$scope.goToComponent = (component:Models.Components.Component):void => {
+ this.$scope.isLoading=true;
+ this.$state.go('workspace.general', {id: component.uniqueId, type:component.componentType.toLowerCase() });
+ };
+
+ };
+
+ private _getTotalCounts(tmpFolder, self): number {
+ let total: number = 0;
+ if (tmpFolder.dist !== undefined) {
+ let distributions = tmpFolder.dist.split(',');
+ distributions.forEach((item:any) => {
+ total = total + self.getEntitiesByStateDist(tmpFolder.state, item).length;
+ });
+ }
+ else {
+ total = total + self.getEntitiesByStateDist(tmpFolder.state, tmpFolder.dist).length;
+ }
+ return total;
+ }
+
+ private _setStates(tmpFolder, states) {
+ if (tmpFolder.states !== undefined) {
+ tmpFolder.states.forEach(function (item:any) {
+ states.push({"state": item.state, "dist": item.dist});
+ });
+ } else {
+ states.push({"state": tmpFolder.state, "dist": tmpFolder.dist});
+ }
+ }
+
+ private initEntities = (reload:boolean):void => {
+ this.$scope.isLoading = reload;
+ this.entityService.getAllComponents().then(
+ (components: Array<Models.Components.Component>) => {
+ this.components = components;
+ this.$scope.components = components;
+ this.$scope.isLoading = false;
+ });
+ };
+
+ private getEntitiesByStateDist = (state: string, dist: string) : Array<Models.Components.Component> => {
+ let gObj:Array<Models.Components.Component>;
+ if (this.components && (state || dist)) {
+ gObj = this.components.filter(function (obj:Models.Components.Component) {
+ if (dist !== undefined && obj.distributionStatus === dist && obj.lifecycleState === state){
+ return true;
+ } else if (dist === undefined && obj.lifecycleState === state) {
+ return true;
+ }
+ return false;
+ });
+ } else {
+ gObj = [];
+ }
+ return gObj;
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/dashboard/dashboard-view.html b/catalog-ui/app/scripts/view-models/dashboard/dashboard-view.html
new file mode 100644
index 0000000000..0aef4e19c6
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/dashboard/dashboard-view.html
@@ -0,0 +1,106 @@
+<div class="sdc-catalog-container">
+ <loader data-display="isLoading"></loader>
+
+ <!-- HEADER -->
+<!--
+ <ecomp-header menu-data="menuItems" version="{{version}}"></ecomp-header>
+-->
+
+ <div class="w-sdc-main-container">
+
+ <perfect-scrollbar include-padding="true" class="w-sdc-main-right-container">
+
+ <!-- ADD Component -->
+ <div ng-if="user.role === 'DESIGNER' || user.role === 'PRODUCT_MANAGER'" class="w-sdc-dashboard-card-new"
+ data-ng-mouseleave="displayActions = false"
+ data-ng-mouseover="displayActions = true"
+ data-ng-init="displayActions = false">
+ <div class="w-sdc-dashboard-card-new-content" data-tests-id="AddButtonsArea">
+ <div class="w-sdc-dashboard-card-new-content-plus" data-ng-show="!displayActions"></div>
+ <div class="sdc-dashboard-create-element-container" data-ng-show="displayActions">
+ <button data-ng-if="roles[user.role].dashboard.showCreateNewProduct" class="tlv-btn outline blue" data-ng-click="openCreateModal('PRODUCT')">Create Product</button>
+ <button data-ng-if="roles[user.role].dashboard.showCreateNew" data-tests-id="createResourceButton" class="tlv-btn outline blue" data-ng-click="openCreateModal('RESOURCE')">Add VF</button>
+ <button data-ng-if="roles[user.role].dashboard.showCreateNew" data-tests-id="createServiceButton" class="tlv-btn outline blue" data-ng-click="openCreateModal('SERVICE')">Add Service</button>
+ </div>
+ </div>
+ </div>
+
+ <!-- Import Component -->
+ <div ng-if="user.role === 'DESIGNER'" class="w-sdc-dashboard-card-new"
+ data-ng-mouseleave="displayActions = false"
+ data-ng-mouseover="displayActions = true"
+ data-ng-init="displayActions = false">
+ <div class="w-sdc-dashboard-card-new-content" data-tests-id="importButtonsArea" >
+ <div class="w-sdc-dashboard-card-import-content-plus" data-ng-show="!displayActions"></div>
+ <div class="sdc-dashboard-import-element-container" data-ng-show="displayActions">
+ <div data-ng-if="roles[user.role].dashboard.showCreateNew" class="tlv-btn outline blue">Import VFC
+ <file-opener on-file-upload="onImportVfc(file)" data-tests-id="importVFCbutton" extensions="{{sdcConfig.toscaFileExtension}}" data-ng-click="displayActions=false"></file-opener>
+ </div>
+ <div data-ng-if="roles[user.role].dashboard.showCreateNew" class="tlv-btn outline blue" data-ng-click="notificationIconCallback()">Import VSP</div>
+ <div data-ng-if="roles[user.role].dashboard.showCreateNew" class="tlv-btn outline blue import-dcae">Import DCAE asset
+ <file-opener on-file-upload="onImportVf(file)" data-tests-id="importVFbutton" extensions="{{sdcConfig.csarFileExtension}}" data-ng-click="displayActions=false"></file-opener>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div
+ data-ng-class="{'sdc-hide-popover': hidePopover,'resource' : component.isResource(),'service' : component.isService()}"
+ class="w-sdc-dashboard-card"
+ data-ng-repeat="component in components | entityFilter:checkboxesFilter | filter:search"
+ >
+ <div class="w-sdc-dashboard-card-body" data-tests-id="dashboard-Elements" data-ng-click="goToComponent(component)">
+ <!--<div class="w-sdc-dashboard-card-description">{{entity.description}}</div>-->
+ <div class="w-sdc-dashboard-card-avatar"><span data-tests-id="asset-type" class="{{component.getComponentSubType()}}"></span></div>
+ <!--<div class="w-sdc-dashboard-card-edit" data-ng-class="component.lifecycleState" data-tests-id="{{component.lifecycleState}}"></div>-->
+ <div class="w-sdc-dashboard-card-schema-image {{component.iconSprite}} {{component.icon}}"
+ data-ng-class="{'sprite-resource-icons': component.isResource(), 'sprite-services-icons': component.isService()}"
+ data-tests-id="{{component.name}}"></div>
+ <div class="w-sdc-dashboard-card-info-name-container">
+ <span class="w-sdc-dashboard-card-info-name" tooltips
+ tooltip-content="{{component.name | resourceName}}"> {{component.name | resourceName}}</span>
+ </div>
+ </div>
+ <div class="w-sdc-dashboard-card-footer">
+
+ <div class="w-sdc-dashboard-card-info">
+ <div class="w-sdc-dashboard-card-info-lifecycleState">
+ <span class="w-sdc-dashboard-card-info-lifecycleState" tooltips
+ tooltip-content="{{component.getStatus(sdcMenu)}}"> {{component.getStatus(sdcMenu)}}</span>
+ </div>
+ <div class="w-sdc-dashboard-card-info-user"data-tests-id="{{component.name}}Version">V {{component.version}}</div>
+ </div>
+ <!--<div class="w-sdc-dashboard-card-info-lifecycleState-icon sprite-new {{sdcMenu.LifeCycleStatuses[component.lifecycleState].icon}}"></div>-->
+ </div>
+ </div>
+
+ </perfect-scrollbar>
+
+ <div class="w-sdc-left-sidebar">
+ <div class="i-sdc-left-sidebar-item "
+ data-ng-repeat="folder in folders.getFolders()"
+ data-ng-class="{'category-title': folder.isGroup(), 'selectedLink': folder.isSelected()}"
+ >
+ <span data-ng-if="folder.isGroup()">{{folder.text}}</span>
+
+ <sdc-checkbox data-ng-if="!folder.isGroup() && !folder.dist"
+ elem-id="checkbox-{{folder.text | lowercase | clearWhiteSpaces}}"
+ sdc-checklist-model="checkboxesFilter.selectedStatuses"
+ sdc-checklist-value="folder.state"
+ text="{{folder.text}}"></sdc-checkbox>
+
+ <sdc-checkbox data-ng-if="!folder.isGroup() && folder.dist"
+ elem-id="checkbox-{{folder.text | lowercase | clearWhiteSpaces}}"
+ sdc-checklist-model="checkboxesFilter.distributed"
+ sdc-checklist-value="folder.dist"
+ text="{{folder.text}}"></sdc-checkbox>
+ <span class="i-sdc-left-sidebar-item-state-count">{{entitiesCount(folder)}}</span>
+ </div>
+ </div>
+
+ </div>
+
+ <top-nav top-lvl-selected-index="0" version="{{version}}" search-bind="search.filterTerm" notification-icon-callback="notificationIconCallback" version="{{version}}"></top-nav>
+
+</div>
+<div data-ui-view=""></div>
diff --git a/catalog-ui/app/scripts/view-models/dashboard/dashboard.less b/catalog-ui/app/scripts/view-models/dashboard/dashboard.less
new file mode 100644
index 0000000000..7b2522113b
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/dashboard/dashboard.less
@@ -0,0 +1,420 @@
+.sdc-dashboard-container {
+ .tlv-loader {
+ top: -110px;
+ left: 80px;
+ }
+ .sdc-hide-popover {
+ .popover {
+ display: none !important;
+ }
+ }
+}
+
+.w-sdc-left-sidebar-nav {
+ margin-top: 46px;
+}
+
+.w-sdc-main-right-container-element {
+ float: left;
+ height: 217px;
+ width: 217px;
+ margin: 10px;
+ position: relative;
+}
+
+.w-sdc-main-right-container-element-details-container {
+ position: absolute;
+ top: 165px;
+ left: 50px;
+}
+
+.w-sdc-main-right-container-element-name {
+ font-weight: bold;
+}
+
+.w-sdc-main-right-container-element-owner {
+
+}
+
+//////////////////////////////Cards////////////////////
+.w-sdc-dashboard-card-new {
+ border: 2px dashed @color_m;
+ .border-radius(2px);
+ cursor: pointer;
+ display: inline-block;
+ height: 200px;
+ margin: 9px;
+ position: relative;
+ vertical-align: middle;
+ width: 204px;
+}
+
+.w-sdc-dashboard-card-new-content {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex-direction: column;
+ height: 100%;
+}
+
+.w-sdc-dashboard-card-new-content-plus {
+ .sprite-new;
+ .add-icon;
+ position: relative;
+ margin-bottom: 20px;
+
+ &:after {
+ .n_14_m;
+ content: 'ADD';
+ position: absolute;
+ top: 25px;
+ left: -3px;
+ vertical-align: -50%;
+ }
+}
+
+.w-sdc-dashboard-card-import-content-plus {
+ .sprite-new;
+ .import-icon;
+ position: relative;
+ margin-bottom: 20px;
+
+ &:after {
+ .n_14_m;
+ content: 'IMPORT';
+ position: absolute;
+ top: 25px;
+ left: -16px;
+ vertical-align: -50%;
+ }
+}
+
+.sdc-dashboard-create-element-container,
+.sdc-dashboard-import-element-container {
+
+ width: 140px;
+
+ .tlv-btn.import-dcae {
+ padding: 0;
+ }
+
+ .tlv-btn {
+ position: relative;
+ width: 100%;
+ margin-bottom: 10px;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+
+ input[type="file"] {
+ cursor: inherit;
+ filter: alpha(opacity=0);
+ opacity: 0;
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 138px;
+ height: 30px;
+ }
+}
+
+.w-sdc-dashboard-card {
+ width: 204px;
+ height: 200px;
+ background-color: @main_color_p;
+ .border-radius(2px);
+ .box-shadow(0px 2px 2px 0px rgba(24, 24, 25, 0.05));
+ display: inline-block;
+ margin: 10px;
+ position: relative;
+ vertical-align: middle;
+ border: solid 1px @main_color_p;
+
+ &:hover {
+ border: solid 1px @main_color_o;
+ .box-shadow(3px 3px 2px 0px rgba(24, 24, 25, 0.05));
+ }
+
+ &:active {
+ border: solid 1px @main_color_c;
+ .box-shadow(3px 3px 2px 0px rgba(24, 24, 25, 0.05));
+ }
+}
+
+.w-sdc-dashboard-card-body {
+ .hand;
+ border-bottom: 1px solid @color_j;
+ height: 155px;
+ position: relative;
+ text-align: center;
+}
+
+.w-sdc-dashboard-card-description {
+ .c_3;
+ .hand;
+ background-color: rgba(57, 73, 84, 0.9);
+ border-radius: 4px 4px 0 0;
+ bottom: 0;
+ left: 0;
+ opacity: 0;
+ padding: 10px;
+ position: absolute;
+ right: 0;
+ text-align: left;
+ top: 0;
+ word-wrap: break-word;
+ z-index: 4;
+ min-height: 100px;
+ overflow: hidden;
+}
+
+
+.w-sdc-dashboard-card-schema {
+ margin-top: 30px;
+}
+
+.w-sdc-dashboard-card-edit {
+ .hand;
+ position: absolute;
+ right: 13px;
+ top: 15px;
+ z-index: 2;
+}
+
+.w-sdc-dashboard-card-footer {
+ padding: 3px 12px 10px 12px;
+ position: relative;
+}
+
+.w-sdc-dashboard-card-avatar {
+ .uppercase;
+ border-radius: 50%;
+ display: inline-block;
+ position: absolute;
+ left: -6px;
+ text-align: center;
+ top: -6px;
+
+ span {
+
+ background-color: @main_color_p;
+ .border-radius(15px);
+ color: @color_c;
+ content: '';
+ height: 30px;
+ text-align: center;
+ display: block;
+ border: solid 2px #ECEFF3;
+ padding: 3px 10px 2px 10px;
+
+ &.VF {
+ .j_14_m;
+ &::before {
+ content: 'VF';
+ }
+ }
+
+ &.VFC {
+ .j_14_m;
+ &::before {
+ content: 'VFC';
+ }
+ }
+
+ &.CP {
+ .j_14_m;
+ &::before {
+ content: 'CP';
+ }
+ }
+
+ &.VL {
+ .j_14_m;
+ &::before {
+ content: 'VL';
+ }
+ }
+
+ &.SERVICE {
+ .c_14_m;
+ &::before {
+ content: 'S';
+ }
+ }
+
+ &.PRODUCT {
+ .b_14_m;
+ &::before {
+ content: 'P';
+ }
+ }
+
+ &.green {
+ .d_12;
+ &::before {
+ content: 'R';
+ }
+ }
+ &.red {
+ .r_12;
+ &::before {
+ content: 'S';
+ }
+ }
+ &.dblack {
+ .s_12;
+ &::before {
+ content: 'P';
+ }
+ }
+ }
+}
+
+.w-sdc-dashboard-card-info {
+ display: inline-block;
+ vertical-align: middle;
+ max-width: 165px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.w-sdc-dashboard-card-info-name-container{
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ margin: 0 0 2px 10px;
+}
+.w-sdc-dashboard-card-info-name {
+ .m_14_m;
+ display: inline-block;
+ vertical-align: middle;
+ max-width: 165px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.w-sdc-dashboard-card-info-lifecycleState {
+ .m_13_m;
+ display: inline-block;
+ vertical-align: middle;
+ max-width: 165px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.w-sdc-dashboard-card-info-user {
+ .n_13_r;
+ line-height: 18px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ width: 100%;
+}
+
+.w-sdc-dashboard-card-menu-button {
+ display: inline-block;
+ padding: 12px 0 0 10px;
+ position: absolute;
+ right: 12px;
+ top: 8px;
+ border-left: solid 1px @color_k;
+ height: 42px;
+
+ &:hover {
+ .w-sdc-dashboard-card-menu {
+ display: block;
+ }
+ }
+}
+
+.w-sdc-dashboard-card-menu {
+ .bg_c;
+ border-radius: 0 0 4px 4px;
+ border-top: 3px solid @color_a;
+ box-shadow: 0 2px 2px 0px rgba(0, 0, 0, 0.2);
+ color: @color_s;
+ display: none;
+ min-height: 30px;
+ padding: 9px 0;
+ position: absolute;
+ right: -27px;
+ width: 208px;
+ z-index: 9;
+ max-height: 164px;
+
+ &::before {
+ //TODO: Missing image for small blue triangle.
+ background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAFCAYAAAB4ka1VAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMDE0IDc5LjE1Njc5NywgMjAxNC8wOC8yMC0wOTo1MzowMiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTQgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOkE1OTIzNDI1MENFQjExRTU4ODRERTI1MDM2REZCOUYzIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOkE1OTIzNDI2MENFQjExRTU4ODRERTI1MDM2REZCOUYzIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6QTU5MjM0MjMwQ0VCMTFFNTg4NERFMjUwMzZERkI5RjMiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6QTU5MjM0MjQwQ0VCMTFFNTg4NERFMjUwMzZERkI5RjMiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4gBXTlAAAAOElEQVR42mK0rp7NgASMgZgFiE/CBJjQJPcA8U4gNkdXAJMUAGJ+ZEVMaJIwAFfEhEUSRRFAgAEAVtgJyiLAPWAAAAAASUVORK5CYII=');
+ content: '';
+ display: block;
+ height: 21px;
+ position: absolute;
+ right: 24px;
+ top: -24px;
+ width: 184px;
+ background-repeat: no-repeat;
+ background-position: 175px 16px;
+ }
+}
+
+.i-sdc-dashboard-card-menu-item {
+ .hand;
+ line-height: 24px;
+ padding: 0 10px;
+ &:hover { .a_7; }
+}
+
+.w-sdc-dashboard-card-info-lifecycleState-icon{
+ position:absolute;
+ bottom:18px;
+ right:10px;
+}
+
+// Same for dashboard and catalog view.
+.w-sdc-dashboard-card-schema-image {
+ position: absolute;
+ top: 41%;
+
+ //TODO: Israel - remove this after getting the services sprite.
+ height: 45px;
+ width: 53px;
+ background-repeat: no-repeat;
+
+ // Center the icon vertical and horizontal.
+ margin: auto;
+ left: 0;
+ right: 0;
+ top: -10px;
+ bottom: 0;
+}
+
+/* dashboard card main icons */
+.w-sdc-dashboard-card-schema-image.service { .s-sdc-service }
+.w-sdc-dashboard-card-schema-image.resource { .s-sdc-resource }
+
+/* dashboard card statuses icons */
+.w-sdc-dashboard-card-edit.NOT_CERTIFIED_CHECKIN { .sprite; .s-sdc-state.NOT_CERTIFIED_CHECKIN; }
+.w-sdc-dashboard-card-edit.NOT_CERTIFIED_CHECKOUT { .sprite; .s-sdc-state.NOT_CERTIFIED_CHECKOUT; }
+.w-sdc-dashboard-card-edit.CERTIFIED { .sprite; .s-sdc-state.CERTIFIED; }
+.w-sdc-dashboard-card-edit.READY_FOR_CERTIFICATION { .sprite; .s-sdc-state.READY_FOR_CERTIFICATION; }
+.w-sdc-dashboard-card-edit.CERTIFICATION_IN_PROGRESS { .sprite; .s-sdc-state.CERTIFICATION_IN_PROGRESS; }
+.w-sdc-dashboard-card-edit.DISTRIBUTED { .sprite; .s-sdc-state.DISTRIBUTED; }
+
+.w-sdc-dashboard-card-avatar.green + .w-sdc-dashboard-card-edit.NOT_CERTIFIED_CHECKIN { .sprite; .s-sdc-state.NOT_CERTIFIED_CHECKIN.green; }
+.w-sdc-dashboard-card-avatar.green + .w-sdc-dashboard-card-edit.NOT_CERTIFIED_CHECKOUT { .sprite; .s-sdc-state.NOT_CERTIFIED_CHECKOUT.green; }
+.w-sdc-dashboard-card-avatar.green + .w-sdc-dashboard-card-edit.CERTIFIED { .sprite; .s-sdc-state.CERTIFIED.green; }
+.w-sdc-dashboard-card-avatar.green + .w-sdc-dashboard-card-edit.READY_FOR_CERTIFICATION { .sprite; .s-sdc-state.READY_FOR_CERTIFICATION.green; }
+.w-sdc-dashboard-card-avatar.green + .w-sdc-dashboard-card-edit.CERTIFICATION_IN_PROGRESS { .sprite; .s-sdc-state.CERTIFICATION_IN_PROGRESS.green; }
+.w-sdc-dashboard-card-avatar.green + .w-sdc-dashboard-card-edit.DISTRIBUTED { .sprite; .s-sdc-state.DISTRIBUTED.green; }
+
+.w-sdc-dashboard-card-avatar.red + .w-sdc-dashboard-card-edit.NOT_CERTIFIED_CHECKIN { .sprite; .s-sdc-state.NOT_CERTIFIED_CHECKIN.red; }
+.w-sdc-dashboard-card-avatar.red + .w-sdc-dashboard-card-edit.NOT_CERTIFIED_CHECKOUT { .sprite; .s-sdc-state.NOT_CERTIFIED_CHECKOUT.red; }
+.w-sdc-dashboard-card-avatar.red + .w-sdc-dashboard-card-edit.CERTIFIED { .sprite; .s-sdc-state.CERTIFIED.red; }
+.w-sdc-dashboard-card-avatar.red + .w-sdc-dashboard-card-edit.READY_FOR_CERTIFICATION { .sprite; .s-sdc-state.READY_FOR_CERTIFICATION.red; }
+.w-sdc-dashboard-card-avatar.red + .w-sdc-dashboard-card-edit.CERTIFICATION_IN_PROGRESS { .sprite; .s-sdc-state.CERTIFICATION_IN_PROGRESS.red; }
+.w-sdc-dashboard-card-avatar.red + .w-sdc-dashboard-card-edit.DISTRIBUTED { .sprite; .s-sdc-state.DISTRIBUTED.red; }
diff --git a/catalog-ui/app/scripts/view-models/forms/artifact-form/artifact-form-view-model.ts b/catalog-ui/app/scripts/view-models/forms/artifact-form/artifact-form-view-model.ts
new file mode 100644
index 0000000000..092594b0d5
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/forms/artifact-form/artifact-form-view-model.ts
@@ -0,0 +1,354 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../../references"/>
+
+module Sdc.ViewModels {
+ 'use strict';
+ import Resource = Sdc.Models.Components.Resource;
+
+ export interface IEditArtifactModel {
+ artifactResource: Models.ArtifactModel;
+ artifactTypes: Array<string>;
+ artifactFile: any;
+ }
+
+ export interface IArtifactResourceFormViewModelScope extends ng.IScope {
+ forms:any;
+ $$childTail: any;
+ isNew: boolean;
+ isLoading: boolean;
+ validationPattern: RegExp;
+ urlValidationPattern: RegExp;
+ labelValidationPattern: RegExp;
+ integerValidationPattern: RegExp;
+ commentValidationPattern: RegExp;
+ artifactType: string;
+ editArtifactResourceModel: IEditArtifactModel;
+ defaultHeatTimeout: number;
+ validExtensions: any;
+ originalArtifactName: string;
+ editForm: ng.IFormController;
+ footerButtons: Array<any>;
+ modalInstanceArtifact:ng.ui.bootstrap.IModalServiceInstance;
+
+ fileExtensions():string;
+ save(doNotCloseModal?:boolean): void;
+ saveAndAnother(): void;
+ close(): void;
+ getOptions(): Array<string>;
+ isDeploymentHeat(): boolean;
+ onFileChange(): void;
+ setDefaultTimeout(): void;
+ openEditEnvParametersModal(artifact:Models.ArtifactModel):void;
+ getFormTitle():string;
+ fileUploadRequired():string;
+ isArtifactOwner():boolean;
+ }
+
+ export class ArtifactResourceFormViewModel {
+
+ private artifactArr:Array<Models.ArtifactModel>;
+
+ static '$inject' = [
+ '$scope',
+ '$modalInstance',
+ 'artifact',
+ 'Sdc.Services.CacheService',
+ 'ValidationPattern',
+ 'UrlValidationPattern',
+ 'LabelValidationPattern',
+ 'IntegerValidationPattern',
+ 'CommentValidationPattern',
+ 'ValidationUtils',
+ '$base64',
+ '$state',
+ 'ArtifactsUtils',
+ '$modal',
+ '$templateCache',
+ 'component'
+ ];
+
+ private formState:Utils.Constants.FormState;
+ private entityId:string;
+
+ constructor(private $scope:IArtifactResourceFormViewModelScope,
+ private $modalInstance:ng.ui.bootstrap.IModalServiceInstance,
+ private artifact:Models.ArtifactModel,
+ private cacheService:Services.CacheService,
+ private ValidationPattern:RegExp,
+ private UrlValidationPattern:RegExp,
+ private LabelValidationPattern:RegExp,
+ private IntegerValidationPattern : RegExp,
+ private CommentValidationPattern: RegExp,
+ private ValidationUtils: Sdc.Utils.ValidationUtils,
+ private $base64:any,
+ private $state:any,
+ private artifactsUtils:Sdc.Utils.ArtifactsUtils,
+ private $modal:ng.ui.bootstrap.IModalService,
+ private $templateCache:ng.ITemplateCacheService,
+ private component:Models.Components.Component) {
+
+
+ this.entityId = this.component.uniqueId;
+ this.artifactArr = [];
+ this.formState = angular.isDefined(artifact.artifactLabel) ? Utils.Constants.FormState.UPDATE : Utils.Constants.FormState.CREATE;
+ this.initScope();
+ }
+
+ private initEntity = ():void => {
+ this.$scope.editArtifactResourceModel.artifactResource = this.artifact;
+ this.$scope.originalArtifactName = this.artifact.artifactName;
+ };
+
+
+ private initFooterButtons = ():void =>{
+
+ this.$scope.footerButtons = [
+ {'name': 'Done', 'css': 'blue', 'callback': this.$scope.save}
+ ];
+ if (this.$scope.isNew){
+ this.$scope.footerButtons.push( {'name': 'Add Another', 'css': 'grey', 'disabled': !this.$scope.isNew && 'deployment' === this.$scope.artifactType, 'callback': this.$scope.saveAndAnother});
+ }
+
+ };
+
+
+ private initArtifactTypes = ():void => {
+
+ let artifactTypes:any = this.cacheService.get('UIConfiguration');
+
+ if('deployment' === this.$scope.artifactType) {
+
+ this.$scope.validExtensions = ('HEAT_ENV' == this.artifact.artifactType||this.component.selectedInstance)?//to remove the first condition?
+ artifactTypes.artifacts.deployment.resourceInstanceDeploymentArtifacts
+ : this.component.isResource() ? artifactTypes.artifacts.deployment.resourceDeploymentArtifacts
+ : artifactTypes.artifacts.deployment.serviceDeploymentArtifacts;
+
+ if(this.$scope.validExtensions) {
+ this.$scope.editArtifactResourceModel.artifactTypes = Object.keys(this.$scope.validExtensions);
+ }
+ this.$scope.defaultHeatTimeout = artifactTypes.defaultHeatTimeout;
+ if(this.$scope.isNew) {
+ let isHeat='HEAT_ENV' == this.artifact.artifactType;
+ _.remove(this.$scope.editArtifactResourceModel.artifactTypes, (item:string)=> {
+ return 'HEAT' == item.substring(0,4)||(!isHeat && item == "VF_MODULES_METADATA") ||
+ _.has(Utils.Constants.ArtifactType.THIRD_PARTY_RESERVED_TYPES, item);
+ });
+ }
+
+ }if (this.$scope.artifactType === 'normal') {
+ this.$scope.editArtifactResourceModel.artifactTypes = artifactTypes.artifacts.other.map((element:any)=> {
+ return element.name;
+ });
+ _.remove(this.$scope.editArtifactResourceModel.artifactTypes, (item:string)=> {
+ return _.has(Utils.Constants.ArtifactType.THIRD_PARTY_RESERVED_TYPES, item) ||
+ _.has(Utils.Constants.ArtifactType.TOSCA, item);
+ })
+ }
+
+ if(this.component.isResource() && (<Resource>this.component).isCsarComponent()) {
+ _.remove(this.$scope.editArtifactResourceModel.artifactTypes, (item:string) => {
+ return this.artifactsUtils.isLicenseType(item);
+ })
+ }
+
+ };
+
+ private initEditArtifactResourceModel = ():void => {
+ this.$scope.editArtifactResourceModel = {
+ artifactResource: null,
+ artifactTypes: null,
+ artifactFile:{}
+ };
+
+ this.initEntity();
+ };
+
+ private initScope = ():void => {
+
+ this.$scope.validationPattern = this.ValidationPattern;
+ this.$scope.urlValidationPattern = this.UrlValidationPattern;
+ this.$scope.labelValidationPattern = this.LabelValidationPattern;
+ this.$scope.integerValidationPattern = this.IntegerValidationPattern;
+ this.$scope.commentValidationPattern = this.CommentValidationPattern;
+ this.$scope.isLoading = false;
+ this.$scope.isNew = (this.formState === Utils.Constants.FormState.CREATE);
+ this.$scope.artifactType = this.artifactsUtils.getArtifactTypeByState(this.$state.current.name);
+ this.$scope.modalInstanceArtifact = this.$modalInstance;
+
+ this.initEditArtifactResourceModel();
+ this.initArtifactTypes();
+
+ // In case of edit, show the file name in browse.
+ if (this.artifact.artifactName!=="" && 'HEAT_ENV'!==this.artifact.artifactType){
+ this.$scope.editArtifactResourceModel.artifactFile = {};
+ this.$scope.editArtifactResourceModel.artifactFile.filename = this.artifact.artifactName;
+ }
+
+ //scope methods
+ this.$scope.isDeploymentHeat = ():boolean => {
+ return !this.$scope.isNew && this.$scope.artifactType === 'deployment' &&
+ 'HEAT' === this.$scope.editArtifactResourceModel.artifactResource.artifactType.substring(0,4);
+ };
+ this.$scope.onFileChange = ():void => {
+ if(this.$scope.editArtifactResourceModel.artifactFile && this.$scope.editArtifactResourceModel.artifactFile.filename) {
+ this.$scope.editArtifactResourceModel.artifactResource.artifactName = this.$scope.editArtifactResourceModel.artifactFile.filename;
+ } else {
+ this.$scope.editArtifactResourceModel.artifactResource.artifactName = this.$scope.originalArtifactName;
+ }
+ };
+ this.$scope.setDefaultTimeout = ():void => {
+ if(this.$scope.isDeploymentHeat() && !this.$scope.editArtifactResourceModel.artifactResource.timeout) {
+ this.$scope.editArtifactResourceModel.artifactResource.timeout = this.$scope.defaultHeatTimeout;
+ }
+ };
+
+ this.$scope.fileExtensions = ():string => {
+ let type:string = this.$scope.editArtifactResourceModel.artifactResource.artifactType;
+ return type && this.$scope.validExtensions && this.$scope.validExtensions[type].acceptedTypes ?
+ this.$scope.validExtensions[type].acceptedTypes.join(',') : "";
+ };
+
+ this.$scope.save = (doNotCloseModal?:boolean):void => {
+ this.$scope.isLoading = true;
+ this.$scope.editArtifactResourceModel.artifactResource.description = this.ValidationUtils.stripAndSanitize(this.$scope.editArtifactResourceModel.artifactResource.description);
+
+ if (!this.$scope.isDeploymentHeat()) {
+ this.$scope.editArtifactResourceModel.artifactResource.timeout = null;
+ }
+
+ if (this.$scope.editArtifactResourceModel.artifactFile) {
+ this.$scope.editArtifactResourceModel.artifactResource.payloadData = this.$scope.editArtifactResourceModel.artifactFile.base64;
+ this.$scope.editArtifactResourceModel.artifactResource.artifactName = this.$scope.editArtifactResourceModel.artifactFile.filename;
+ }
+
+ let onFaild = (response):void => {
+ this.$scope.isLoading = false;
+ console.info('onFaild', response);
+ };
+
+ let onSuccess = (artifactResource:Models.ArtifactModel):void => {
+ this.$scope.isLoading = false;
+ this.$scope.originalArtifactName = "";
+
+ if(this.$scope.isDeploymentHeat()){
+ if(artifactResource.heatParameters) {
+ this.$scope.openEditEnvParametersModal(artifactResource);
+ }
+ }
+
+ if (!doNotCloseModal) {
+ this.$modalInstance.close();
+ //this.artifactArr = [];
+ } else {
+ this.$scope.editArtifactResourceModel.artifactFile = null;
+ angular.element("input[type='file']").val(null); // for support chrome when upload the same file
+ this.artifactsUtils.addAnotherAfterSave(this.$scope);
+ }
+
+ };
+
+ if('HEAT_ENV' == this.artifact.artifactType){
+ this.component.uploadInstanceEnvFile(this.$scope.editArtifactResourceModel.artifactResource).then(onSuccess, onFaild);
+ }else if(this.$scope.isArtifactOwner()){
+ this.component.addOrUpdateInstanceArtifact(this.$scope.editArtifactResourceModel.artifactResource).then(onSuccess, onFaild);
+ }else {
+ this.component.addOrUpdateArtifact(this.$scope.editArtifactResourceModel.artifactResource).then(onSuccess, onFaild);
+ }
+ };
+
+ this.$scope.isArtifactOwner = ():boolean=> {
+ return this.component.isService() && !!this.component.selectedInstance;
+ };
+
+ this.$scope.saveAndAnother = ():void => {
+ this.$scope.save(true);
+ };
+
+ this.$scope.close = ():void => {
+ this.$modalInstance.close();
+ this.artifactArr = [];
+ };
+
+ this.$scope.fileUploadRequired = ():string => {
+ if (this.$scope.editArtifactResourceModel.artifactFile.filename){
+ // This is edit mode
+ return 'false';
+ } else {
+ return 'true';
+ }
+ };
+
+ this.$scope.getFormTitle =(): string =>{
+ if('HEAT_ENV' == this.artifact.artifactType){
+ return 'Update HEAT ENV';
+ }
+ if(this.$scope.isDeploymentHeat()){
+ if(!this.$scope.editArtifactResourceModel.artifactResource.artifactChecksum){
+ return 'Add HEAT Template';
+ }
+ return 'Update HEAT Template';
+ }
+ if(this.$scope.isNew){
+ return 'Add Artifact';
+ }
+ return 'Update Artifact';
+ };
+
+ this.$scope.openEditEnvParametersModal = (artifactResource:Models.ArtifactModel):void => {
+
+ let modalOptions:ng.ui.bootstrap.IModalSettings = {
+ template: this.$templateCache.get('/app/scripts/view-models/forms/env-parameters-form/env-parameters-form.html'),
+ controller: 'Sdc.ViewModels.EnvParametersFormViewModel',
+ size: 'sdc-md',
+ backdrop: 'static',
+ resolve: {
+ artifact: ():Models.ArtifactModel => {
+ return artifactResource;
+ },
+ component: ():Models.Components.Component => {
+ return this.component;
+ }
+ }
+ };
+
+ let modalInstance:ng.ui.bootstrap.IModalServiceInstance = this.$modal.open(modalOptions);
+ modalInstance
+ .result
+ .then(():void => {
+ });
+ };
+
+ this.$scope.forms = {};
+
+ this.initFooterButtons();
+
+
+ this.$scope.$watch("forms.editForm.$invalid", (newVal, oldVal) => {
+ this.$scope.footerButtons[0].disabled = this.$scope.forms.editForm.$invalid;
+ if(this.$scope.isNew){
+ this.$scope.footerButtons[1].disabled = this.$scope.forms.editForm.$invalid;
+ }
+ });
+
+ }
+
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/forms/artifact-form/artifact-form-view.html b/catalog-ui/app/scripts/view-models/forms/artifact-form/artifact-form-view.html
new file mode 100644
index 0000000000..74a19c8776
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/forms/artifact-form/artifact-form-view.html
@@ -0,0 +1,169 @@
+<sdc-modal modal="modalInstanceArtifact" type="classic" class="sdc-add-artifact" buttons="footerButtons" header="{{getFormTitle()}}" show-close-button="true" get-close-modal-response="close">
+
+ <loader data-display="isLoading"></loader>
+
+ <div class="sdc-edit-artifact-form-container"
+ data-ng-class="{'mandatory-artifact': (editArtifactResourceModel.artifactResource.mandatory && artifactType !=='deployment') || artifactType === 'api'}">
+ <form novalidate class="w-sdc-form" name="forms.editForm">
+
+ <!--------------------- ARTIFACT FILE START-------------------->
+ <div class="i-sdc-form-item">
+ <label class="required">Upload File</label>
+ <file-upload id="fileUploadElement"
+ form-element="forms.editForm"
+ element-required="{{::fileUploadRequired()}}"
+ element-name="myArtifactFile"
+ file-model="editArtifactResourceModel.artifactFile"
+ extensions="{{fileExtensions()}}"
+ element-disabled="{{!editArtifactResourceModel.artifactResource.artifactType}}"
+ data-ng-class="{'error': forms.editForm.myArtifactFile.$dirty && forms.editForm.myArtifactFile.$invalid}"></file-upload>
+
+ <div class="input-error-file-upload" data-ng-show="forms.editForm.myArtifactFile.$dirty && forms.editForm.myArtifactFile.$invalid">
+ <span ng-show="forms.editForm.myArtifactFile.$error.required && !forms.editForm.myArtifactFile.$error.emptyFile" translate="ADD_ARTIFACT_ERROR_FILE_REQUIRED"></span>
+ <span ng-show="forms.editForm.myArtifactFile.$error.maxsize" translate="VALIDATION_ERROR_MAX_FILE_SIZE"></span>
+ <span ng-if="artifactType === 'deployment'" ng-show="forms.editForm.myArtifactFile.$error.filetype" translate="ADD_ARTIFACT_ERROR_VALID_EXTENSIONS" translate-values="{'extensions': '{{fileExtensions()}}' }"></span>
+ <span ng-show="forms.editForm.myArtifactFile.$error.emptyFile" translate="VALIDATION_ERROR_EMPTY_FILE"></span>
+ </div>
+ </div>
+ <!--------------------- ARTIFACT FILE END -------------------->
+
+ <div class="w-sdc-form-columns-wrapper">
+
+ <div class="w-sdc-form-column" data-ng-if="artifactType === 'deployment' || (!editArtifactResourceModel.artifactResource.mandatory && artifactType !== 'api')">
+
+ <div class="i-sdc-form-item"
+ data-ng-class="{error:(forms.editForm.artifactLabel.$dirty && forms.editForm.artifactLabel.$invalid)}"
+ data-ng-if="!isDeploymentHeat()">
+ <label class="i-sdc-form-label required">Artifact Label</label>
+ <input class="i-sdc-form-input"
+ data-ng-maxlength="25"
+ data-ng-model="editArtifactResourceModel.artifactResource.artifactLabel"
+ type="text"
+ name="artifactLabel"
+ data-required
+ data-ng-model-options="{ debounce: 200 }"
+ data-ng-pattern="labelValidationPattern"
+ maxlength="25"
+ data-ng-disabled="!isNew"
+ data-tests-id="artifact-label"
+ autofocus/>
+
+ <div class="input-error" data-ng-show="forms.editForm.artifactLabel.$dirty && forms.editForm.artifactLabel.$invalid">
+ <span ng-show="forms.editForm.artifactLabel.$error.required" translate="ADD_ARTIFACT_ERROR_LABEL_REQUIRED"></span>
+ <span ng-show="forms.editForm.artifactLabel.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '25' }"></span>
+ <span ng-show="forms.editForm.artifactLabel.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span>
+ </div>
+
+ </div>
+
+ <div class="i-sdc-form-item" data-ng-class="{error:(forms.editForm.type.$dirty && forms.editForm.type.$invalid)}">
+ <label class="i-sdc-form-label required">Type</label>
+ <select class="i-sdc-form-select"
+ data-required
+ name="type"
+ data-ng-disabled="!isNew"
+ data-ng-change="setDefaultTimeout()"
+ data-ng-model="editArtifactResourceModel.artifactResource.artifactType"
+ data-ng-options="type as type for type in editArtifactResourceModel.artifactTypes track by type | uppercase"
+ data-tests-id="artifacttype">
+ <option value="">Choose Type</option>
+ </select>
+
+ <div class="input-error" data-ng-show="forms.editForm.type.$dirty && forms.editForm.type.$invalid">
+ <span ng-show="forms.editForm.type.$error.required" translate="ADD_ARTIFACT_ERROR_TYPE_REQUIRED"></span>
+ </div>
+
+ </div>
+
+ <div class="i-sdc-form-item" data-ng-class="{error:(forms.editForm.timeout.$dirty && forms.editForm.timeout.$invalid)}" data-ng-if="isDeploymentHeat()">
+ <label class="i-sdc-form-label">Deployment Timeout (minutes)</label>
+ <input class="i-sdc-form-input"
+ data-ng-maxlength="25"
+ data-ng-model="editArtifactResourceModel.artifactResource.timeout"
+ type="number"
+ name="timeout"
+ min="1"
+ max="2147483647"
+ data-ng-disabled="'HEAT_ENV'==editArtifactResourceModel.artifactResource.artifactType"
+ data-ng-model-options="{ debounce: 200 }"
+ data-ng-pattern="integerValidationPattern"
+ data-ng-init="setDefaultTimeout()"
+ data-ng-change="setDefaultTimeout()"
+ maxlength="25"
+ data-tests-id="timeout" />
+
+ <div class="input-error" data-ng-show="forms.editForm.timeout.$dirty && forms.editForm.timeout.$invalid">
+ <span ng-show="forms.editForm.timeout.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '25' }"></span>
+ <span ng-show="forms.editForm.timeout.$error.pattern" translate="ADD_ARTIFACT_ERROR_TIMEOUT_PATTERN"></span>
+ <span ng-show="forms.editForm.timeout.$error.min" translate="ADD_ARTIFACT_ERROR_TIMEOUT_MIN"></span>
+ </div>
+
+ </div>
+
+ </div><!-- w-sdc-form-column -->
+
+ <div class="w-sdc-form-column i-sdc-form-url" data-ng-if="artifactType==='api'">
+
+ <div class="i-sdc-form-item"
+ data-ng-class="{error:(forms.editForm.apiUrl.$dirty && forms.editForm.apiUrl.$invalid)}">
+ <label class="i-sdc-form-label required">URL</label>
+ <input class="i-sdc-form-input"
+ data-ng-maxlength="100"
+ data-ng-model="editArtifactResourceModel.artifactResource.apiUrl"
+ data-ng-model-options="{ debounce: 200 }"
+ type="url"
+ name="apiUrl"
+ data-required
+ ng-pattern="urlValidationPattern"
+ maxlength="100"
+ autofocus
+ invalid-characters=',#?&@$<>~^`\[]{}|")(*!+=;%' />
+
+ <div class="input-error" data-ng-show="forms.editForm.apiUrl.$dirty && forms.editForm.apiUrl.$invalid">
+ <span ng-show="forms.editForm.apiUrl.$error.required" translate="ADD_ARTIFACT_ERROR_APIURL_REQUIRED"></span>
+ <span ng-show="forms.editForm.apiUrl.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '100' }"></span>
+ <span ng-show="forms.editForm.apiUrl.$error.url || forms.editForm.apiUrl.$error.pattern || forms.editForm.apiUrl.$error.invalidCharacters" translate="ADD_ARTIFACT_ERROR_APIURL_URL"></span>
+ </div>
+
+ </div>
+
+ </div><!-- w-sdc-form-column -->
+
+ <div class="w-sdc-form-column">
+
+ <div class="i-sdc-form-item"
+ data-ng-class="{error:(forms.editForm.description.$dirty && forms.editForm.description.$invalid)}">
+ <label class="i-sdc-form-label required">Description</label>
+ <textarea class="i-sdc-form-textarea"
+ data-ng-maxlength="256"
+ maxlength="256"
+ data-required
+ name="description"
+ data-ng-model="editArtifactResourceModel.artifactResource.description"
+ data-ng-model-options="{ debounce: 200 }"
+ data-ng-pattern="commentValidationPattern"
+ data-tests-id="description"></textarea>
+
+ <div class="input-error" data-ng-show="forms.editForm.description.$dirty && forms.editForm.description.$invalid">
+ <span ng-show="forms.editForm.description.$error.required" translate="ADD_ARTIFACT_ERROR_DESCRIPTION_REQUIRED"></span>
+ <span ng-show="forms.editForm.description.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '256' }"></span>
+ <span ng-show="forms.editForm.description.$error.pattern" translate="ADD_ARTIFACT_ERROR_DESCRIPTION_PATTERN"></span>
+ </div>
+
+ <div class="w-sdc-form-column artifact-info" data-ng-show="!isNew && editArtifactResourceModel.artifactResource.esId">
+ UUID <span data-ng-bind="editArtifactResourceModel.artifactResource.artifactUUID"></span>
+ <br />
+ Version <span data-ng-bind="editArtifactResourceModel.artifactResource.artifactVersion"></span>
+ </div>
+ </div>
+
+ </div><!-- w-sdc-form-column -->
+
+ </div><!-- w-sdc-form-columns-wrapper -->
+
+ <span class="w-sdc-form-note" data-ng-show="forms.editForm.$invalid && false" translate="LABEL_ALL_FIELDS_ARE_MANDATORY"></span>
+
+ </form>
+ </div>
+</sdc-modal>
+
diff --git a/catalog-ui/app/scripts/view-models/forms/artifact-form/artifact-form.less b/catalog-ui/app/scripts/view-models/forms/artifact-form/artifact-form.less
new file mode 100644
index 0000000000..1f77958c88
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/forms/artifact-form/artifact-form.less
@@ -0,0 +1,44 @@
+.sdc-edit-artifact-form-container {
+
+ .w-sdc-form-note {
+ .h_9;
+ display: block;
+ position: relative;
+ top: 13px;
+ }
+
+ .i-sdc-form-textarea{
+ min-height: 95px;
+ }
+
+ .i-sdc-form-url {
+ padding-bottom: 0px;
+ }
+
+ &.mandatory-artifact {
+ .w-sdc-form-column {
+ width: 100%;
+ padding: 0;
+ min-height: initial;
+ }
+ }
+ .w-sdc-form .i-sdc-form-item.upload input[type="file"] {
+ display: none
+ }
+
+ .artifact-info {
+ text-align: left;
+ color: rgb(140, 140, 140);
+ font-size: 13px;
+ margin-top: -10px;
+ margin-bottom: 5px;
+ width: 100%;
+ min-height: initial;
+
+ span {
+ color: #666666;
+ padding-left: 4px;
+ }
+ }
+
+}
diff --git a/catalog-ui/app/scripts/view-models/forms/attribute-form/attribute-form-view.html b/catalog-ui/app/scripts/view-models/forms/attribute-form/attribute-form-view.html
new file mode 100644
index 0000000000..432b32fbd3
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/forms/attribute-form/attribute-form-view.html
@@ -0,0 +1,153 @@
+<sdc-modal modal="modalInstanceAttribute" type="classic" class="sdc-edit-attribute-container" buttons="footerButtons" header="{{isNew ? 'Add' : 'Update' }} Attribute" show-close-button="true">
+
+ <div class="sdc-edit-attribute-form-container" >
+ <form novalidate class="w-sdc-form two-columns" name="forms.editForm" >
+
+ <div class="w-sdc-form-columns-wrapper">
+
+ <div class="w-sdc-form-column">
+
+ <!-- Name -->
+ <div class="i-sdc-form-item" data-ng-class="{error:(forms.editForm.attributeName.$dirty && forms.editForm.attributeName.$invalid)}">
+ <label class="i-sdc-form-label required">Name</label>
+ <input class="i-sdc-form-input"
+ data-tests-id="attributeName"
+ data-ng-maxlength="50"
+ data-ng-disabled="!isNew"
+ maxlength="50"
+ data-ng-model="editAttributeModel.attribute.name"
+ type="text"
+ name="attributeName"
+ data-ng-pattern="propertyNameValidationPattern"
+ data-required
+ data-ng-model-options="{ debounce: 200 }"
+ data-ng-change="validateName()"
+ autofocus />
+ <div class="input-error" data-ng-show="forms.editForm.attributeName.$dirty && forms.editForm.attributeName.$invalid">
+ <span ng-show="forms.editForm.attributeName.$error.required" translate="VALIDATION_ERROR_REQUIRED" translate-values="{'field': 'Attribute name' }"></span>
+ <span ng-show="forms.editForm.attributeName.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '128' }"></span>
+ <span ng-show="forms.editForm.attributeName.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span>
+ <span ng-show="forms.editForm.attributeName.$error.nameExist" translate="NEW_ATTRIBUTE_ERROR_NAME_EXISTS"></span>
+ </div>
+ </div>
+
+ <!-- Description -->
+ <div class="i-sdc-form-item" data-ng-class="{error:(forms.editForm.description.$dirty && forms.editForm.description.$invalid)}">
+ <label class="i-sdc-form-label">Description</label>
+ <textarea class="i-sdc-form-textarea"
+ data-ng-maxlength="256"
+ data-ng-disabled="editAttributeModel.attribute.readonly"
+ maxlength="256"
+ data-ng-pattern="commentValidationPattern"
+ name="description"
+ data-ng-model="editAttributeModel.attribute.description"
+ data-ng-model-options="{ debounce: 200 }"
+ data-tests-id="description"></textarea>
+ <div class="input-error" data-ng-show="forms.editForm.description.$dirty && forms.editForm.description.$invalid">
+ <span ng-show="forms.editForm.description.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '256' }"></span>
+ <span ng-show="forms.editForm.description.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span>
+ <span ng-show="forms.editForm.description.$error.required" translate="VALIDATION_ERROR_REQUIRED" translate-values="{'field': 'Description' }"></span>
+ </div>
+ </div>
+
+
+ </div>
+
+ <div class="w-sdc-form-column">
+ <!-- Type -->
+ <div class="i-sdc-form-item" data-ng-class="{error:(forms.editForm.type.$dirty && forms.editForm.type.$invalid)}">
+ <label class="i-sdc-form-label required">Type</label>
+ <select class="i-sdc-form-select"
+ data-tests-id="type-field"
+ data-required
+ data-ng-disabled="editAttributeModel.attribute.readonly"
+ name="type"
+ data-ng-change="onTypeChange()"
+ data-ng-model="editAttributeModel.attribute.type"
+ data-ng-options="type for type in editAttributeModel.types">
+ <option value="">Choose Type</option>
+ </select>
+ <div class="input-error" data-ng-show="forms.editForm.type.$dirty && forms.editForm.type.$invalid">
+ <span ng-show="forms.editForm.type.$error.required" translate="VALIDATION_ERROR_REQUIRED" translate-values="{'field': 'Type' }"></span>
+ </div>
+ </div>
+
+ <!-- schema -->
+ <div class="i-sdc-form-item" data-ng-class="{error:(forms.editForm.schema.$dirty && forms.editForm.schema.$invalid)}"
+ data-ng-if="showSchema()">
+ <label class="i-sdc-form-label required">Entry Schema</label>
+ <select class="i-sdc-form-select" ng-if="isSchemaEditable()"
+ data-required
+ name="schema"
+ data-ng-disabled="editAttributeModel.attribute.readonly"
+ data-ng-change="onTypeChange(false)"
+ data-ng-model="editAttributeModel.attribute.schema.property.type"
+ data-ng-options="type for type in editAttributeModel.simpleTypes">
+ <option value="">Choose Schema Type</option>
+ </select>
+ <input class="i-sdc-form-input"
+ ng-if="!isSchemaEditable()"
+ data-tests-id="schema"
+ data-ng-disabled="true"
+ data-ng-model="editAttributeModel.attribute.schema.property.type"
+ type="text"
+ name="schema"/>
+ <div class="input-error" data-ng-show="forms.editForm.schema.$dirty && forms.editForm.schema.$invalid">
+ <span ng-show="forms.editForm.schema.$error.required" translate="VALIDATION_ERROR_REQUIRED" translate-values="{'field': 'Entry schema' }"></span>
+ </div>
+ </div>
+
+ <!-- Default value -->
+ <div class="i-sdc-form-item" data-ng-class="{error:(forms.editForm.value.$dirty && forms.editForm.value.$invalid)}">
+ <label class="i-sdc-form-label">Default Value</label>
+ <input class="i-sdc-form-input"
+ data-tests-id="defaultvalue"
+ ng-if="!(editAttributeModel.attribute.type == 'boolean')"
+ data-ng-maxlength="100"
+ data-ng-disabled="editAttributeModel.attribute.readonly && !isAttributeValueOwner()"
+ maxlength="100"
+ data-ng-model="editAttributeModel.attribute[isAttributeValueOwner()?'value':'defaultValue']"
+ type="text"
+ name="value"
+ data-custom-validation="" data-validation-func="validateUniqueKeys"
+ data-tests-id="defaultvalue"
+ data-ng-pattern="validationPattern"
+ data-ng-model-options="{ debounce: 200 }"
+ data-ng-change="!forms.editForm.value.$error.pattern && ('integer'==editAttributeModel.attribute.type && forms.editForm.value.$setValidity('pattern', validateIntRange(editAttributeModel.attribute.value)) || onValueChange())"
+ autofocus />
+ <select class="i-sdc-form-select"
+ data-tests-id="booleantype"
+ ng-if="editAttributeModel.attribute.type == 'boolean'"
+ data-ng-disabled="editAttributeModel.attribute.readonly && !isAttributeValueOwner()"
+ name="value"
+ data-ng-change="onValueChange()"
+ data-ng-model="editAttributeModel.attribute[isAttributeValueOwner()?'value':'defaultValue']">
+ <option value="true">true</option>
+ <option value="false">false</option>
+ </select>
+ <div class="input-error" data-ng-show="forms.editForm.value.$dirty && forms.editForm.value.$invalid">
+ <span ng-show="forms.editForm.value.$error.required" translate="VALIDATION_ERROR_REQUIRED" translate-values="{'field': 'Default value' }"></span>
+ <span ng-show="forms.editForm.value.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '100' }"></span>
+ <span ng-show="forms.editForm.value.$error.pattern" translate="{{getValidationTranslate()}}"></span>
+ <span ng-show="forms.editForm.value.$error.customValidation" translate="ATTRIBUTE_EDIT_MAP_UNIQUE_KEYS"></span>
+ </div>
+ </div>
+
+ <!-- hidden -->
+ <div class="i-sdc-form-item" data-ng-if="isAttributeValueOwner()">
+ <label class="i-sdc-form-label">Hidden</label>
+ <input class="i-sdc-form-input"
+ data-tests-id="hidden"
+ data-ng-disabled="editAttributeModel.attribute.readonly && !isAttributeValueOwner()"
+ data-ng-model="editAttributeModel.attribute.hidden"
+ type="checkbox"
+ name="hidden"/>
+ </div>
+ </div>
+
+ </div>
+
+ </form>
+ </div>
+
+</sdc-modal>
diff --git a/catalog-ui/app/scripts/view-models/forms/attribute-form/attribute-from-view-model.ts b/catalog-ui/app/scripts/view-models/forms/attribute-form/attribute-from-view-model.ts
new file mode 100644
index 0000000000..d369cfa5d1
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/forms/attribute-form/attribute-from-view-model.ts
@@ -0,0 +1,255 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../../references"/>
+
+module Sdc.ViewModels {
+ 'use strict';
+
+ export interface IEditAttributeModel {
+ attribute: Models.AttributeModel;
+ types: Array<string>;
+ simpleTypes: Array<string>;
+ }
+
+ interface IAttributeFormViewModelScope extends ng.IScope {
+ $$childTail: any;
+ forms:any;
+ editForm:ng.IFormController;
+ footerButtons: Array<any>;
+ isService: boolean;
+ editAttributeModel: IEditAttributeModel;
+ modalInstanceAttribute:ng.ui.bootstrap.IModalServiceInstance;
+ isNew: boolean;
+ listRegex: Sdc.Utils.IMapRegex;
+ mapRegex: Sdc.Utils.IMapRegex;
+ propertyNameValidationPattern: RegExp;
+ commentValidationPattern: RegExp;
+ isLoading: boolean;
+ validationPattern: RegExp;
+
+ save():void;
+ close(): void;
+ onTypeChange():void;
+ onValueChange(): void;
+ isAttributeValueOwner():boolean;
+ validateIntRange(value:string):boolean;
+ validateUniqueKeys(viewValue:string):boolean;
+ getValidationTranslate(): string;
+ showSchema(): boolean;
+ isSchemaEditable(): boolean;
+ validateName():void;
+ }
+
+ export class AttributeFormViewModel {
+
+ static '$inject' = [
+ '$scope',
+ '$modalInstance',
+ 'attribute',
+ 'ValidationUtils',
+ 'CommentValidationPattern',
+ 'PropertyNameValidationPattern',
+ 'component'
+ ];
+
+ private formState: Sdc.Utils.Constants.FormState;
+
+
+ constructor(private $scope:IAttributeFormViewModelScope,
+ private $modalInstance:ng.ui.bootstrap.IModalServiceInstance,
+ private attribute: Models.AttributeModel,
+ private ValidationUtils:Sdc.Utils.ValidationUtils,
+ private CommentValidationPattern:RegExp,
+ private PropertyNameValidationPattern: RegExp,
+ private component: Models.Components.Component) {
+ this.formState = angular.isDefined(attribute.name) ? Utils.Constants.FormState.UPDATE : Utils.Constants.FormState.CREATE;
+ this.initScope();
+ }
+
+ private initResource = ():void => {
+ this.$scope.editAttributeModel.attribute = new Sdc.Models.AttributeModel(this.attribute);
+ if (this.$scope.editAttributeModel.types.indexOf(this.attribute.type) === -1) {//attribute defaulte type is string too?
+ this.attribute.type = "string";
+ }
+ };
+
+ private initEditAttributeModel = ():void => {
+ this.$scope.editAttributeModel = {
+ attribute: null,
+ types: ['integer', 'string', 'float', 'boolean', 'list', 'map'],
+ simpleTypes: ['integer', 'string', 'float', 'boolean']
+ };
+
+ this.initResource();
+ };
+
+ private initScope = ():void => {
+
+ //scope attributes
+ this.$scope.forms = {};
+ this.$scope.propertyNameValidationPattern = this.PropertyNameValidationPattern;
+ this.$scope.commentValidationPattern = this.CommentValidationPattern;
+
+ this.$scope.modalInstanceAttribute = this.$modalInstance;
+ this.$scope.listRegex = this.ValidationUtils.getPropertyListPatterns();
+ this.$scope.mapRegex = this.ValidationUtils.getPropertyMapPatterns();
+
+ this.$scope.isNew = (this.formState === Utils.Constants.FormState.CREATE);
+ this.$scope.isLoading = false;
+
+ this.initEditAttributeModel();
+ this.setValidationPattern();
+
+ //scope methods
+ this.$scope.save = ():void => {
+ if(!this.$scope.forms.editForm.$invalid){
+ let attribute:Models.AttributeModel = this.$scope.editAttributeModel.attribute;
+ this.$scope.editAttributeModel.attribute.description = this.ValidationUtils.stripAndSanitize(this.$scope.editAttributeModel.attribute.description);
+ ////if read only - just closes the modal
+ if (this.$scope.editAttributeModel.attribute.readonly && !this.$scope.isAttributeValueOwner()) {
+ this.$modalInstance.close();
+ return;
+ }
+ this.$scope.isLoading = true;
+ let onAttributeFaild = (response):void => {
+ console.info('onFaild', response);
+ this.$scope.isLoading = false;
+ };
+
+ let onAttributeSuccess = (attributeFromBE:Models.AttributeModel):void => {
+ console.info('onAttributeResourceSuccess : ', attributeFromBE);
+ this.$scope.isLoading = false;
+ this.$modalInstance.close();
+ };
+
+ //in case we have uniqueId we call update method
+ if (this.$scope.isAttributeValueOwner()) {
+ this.component.updateInstanceAttribute(attribute).then(onAttributeSuccess, onAttributeFaild);
+ } else {
+ this.component.addOrUpdateAttribute(attribute).then(onAttributeSuccess, onAttributeFaild);
+ }
+ }
+ };
+
+ this.$scope.close = ():void => {
+ this.$modalInstance.close();
+ };
+
+ this.$scope.validateName = ():void => {
+ let existsAttr: Models.AttributeModel = _.find(this.component.attributes, (attribute:Models.AttributeModel) => {
+ return attribute.name === this.$scope.editAttributeModel.attribute.name;
+ });
+ if(existsAttr){
+ this.$scope.forms.editForm["attributeName"].$setValidity('nameExist', false);
+ }else{
+ this.$scope.forms.editForm["attributeName"].$setValidity('nameExist', true);
+ }
+
+ };
+
+ this.$scope.onTypeChange = ():void => {
+ this.$scope.editAttributeModel.attribute.value = '';
+ this.$scope.editAttributeModel.attribute.defaultValue = '';
+ this.setValidationPattern();
+ };
+
+ this.$scope.isAttributeValueOwner = ():boolean=> {
+ return this.component.isService() || !!this.component.selectedInstance;
+ };
+
+ this.$scope.onValueChange = ():void => {
+ if (!this.$scope.editAttributeModel.attribute.value) {
+ if (this.$scope.isAttributeValueOwner()) {
+ this.$scope.editAttributeModel.attribute.value = this.$scope.editAttributeModel.attribute.defaultValue;
+ }
+ }
+ };
+
+
+ this.$scope.validateUniqueKeys = (viewValue:string) : boolean => {
+ if(this.$scope.editAttributeModel.attribute.type === 'map') {
+ return this.ValidationUtils.validateUniqueKeys(viewValue);
+ }
+ else {
+ return true; //always valid if not a map
+ }
+ };
+
+ this.$scope.validateIntRange = (value:string):boolean => {
+ return !value || this.ValidationUtils.validateIntRange(value);
+ };
+
+ this.$scope.isSchemaEditable = () :boolean => {
+ let schemaType=this.$scope.editAttributeModel.attribute.schema.property.type;
+ return this.$scope.editAttributeModel.simpleTypes.indexOf(schemaType) > -1||!schemaType;
+ };
+
+ this.$scope.showSchema = () :boolean => {
+ return ['list', 'map'].indexOf(this.$scope.editAttributeModel.attribute.type) > -1;
+ };
+
+ this.$scope.getValidationTranslate = () : string => {
+ let result = "ATTRIBUTE_EDIT_PATTERN";
+ if (this.$scope.showSchema()) {
+
+ result = "ATTRIBUTE_EDIT_" + this.$scope.editAttributeModel.attribute.type.toUpperCase();
+
+ if(this.$scope.editAttributeModel.attribute.schema.property.type === Utils.Constants.PROPERTY_TYPES.STRING) {
+ result += "_STRING";
+ }else if(this.$scope.editAttributeModel.attribute.schema.property.type === Utils.Constants.PROPERTY_TYPES.BOOLEAN) {
+ result += "_BOOLEAN";
+ } else {
+ result += "_GENERIC";
+ }
+ }
+
+ return result;
+ };
+
+ // Add the done button at the footer.
+ this.$scope.footerButtons = [
+ {'name': 'Done', 'css':'blue', 'callback': this.$scope.save},
+ {'name':'Cancel', 'css':'grey', 'callback': this.$scope.close}
+ ];
+
+ this.$scope.$watchCollection("forms.editForm.$invalid", (newVal, oldVal) => {
+ this.$scope.footerButtons[0].disabled = this.$scope.forms.editForm.$invalid;
+ });
+
+ }
+
+
+ private setValidationPattern = ():void => {
+
+ if(this.$scope.editAttributeModel.attribute.type === 'list') {
+ this.$scope.validationPattern = this.$scope.listRegex[this.$scope.editAttributeModel.attribute.schema.property.type];
+ }
+ else if(this.$scope.editAttributeModel.attribute.type === 'map') {
+ this.$scope.validationPattern = this.$scope.mapRegex[this.$scope.editAttributeModel.attribute.schema.property.type];
+ }
+ else{
+ this.$scope.validationPattern = this.ValidationUtils.getValidationPattern(this.$scope.editAttributeModel.attribute.type);
+ }
+
+ };
+
+
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/forms/env-parameters-form/env-parameters-form.html b/catalog-ui/app/scripts/view-models/forms/env-parameters-form/env-parameters-form.html
new file mode 100644
index 0000000000..69367dc68c
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/forms/env-parameters-form/env-parameters-form.html
@@ -0,0 +1,39 @@
+<sdc-modal modal="envParametersModal" type="classic" class="sdc-env-form-container" buttons="saveButton" header="{{isInstance()?'HEAT Template':'Update Parameters'}}" show-close-button="true">
+ <div class="sdc-env-form-container">
+ <div class="w-sdc-modal-body">
+ <span class="w-sdc-modal-body-content" data-ng-if="!isInstance()" translate="UPDATE_PARAMETERS_TEXT"></span>
+ <form novalidate class="" name="editForm">
+ <perfect-scrollbar class="perfect-scrollbar w-sdc-form w-sdc-env-form-container">
+ <div class="i-sdc-form-item" data-ng-repeat="parameter in heatParameters track by $index">
+ <div class="left-column-container">
+ <ng-form name="editForm">
+ <label class="i-sdc-env-form-label" data-ng-class="{required:parameter.defaultValue}"
+ data-ng-bind="parameter.name +' (' + parameter.type + ')'" tooltips tooltip-content="{{parameter.name +' (' + parameter.type + ')'}}"></label>
+ <input class="i-sdc-form-input" data-ng-class="{error:(editForm.currentValue.$invalid)}"
+ data-ng-model-options="{ debounce: 200 }"
+ data-ng-model="parameter.currentValue"
+ type="text"
+ name="currentValue"
+ data-ng-pattern="getValidationPattern(parameter.type, 'heat')"
+ data-ng-required="parameter.defaultValue"
+ data-ng-change="'json'==parameter.type && editForm.currentValue.$setValidity('pattern', validateJson(parameter.currentValue))"
+ data-ng-blur="(editForm.currentValue.$error.required && (parameter.currentValue=parameter.defaultValue))"
+ />
+
+ <div class="input-error" data-ng-show="editForm.currentValue.$invalid">
+ <span ng-show="editForm.currentValue.$error.required" translate="VALIDATION_ERROR_REQUIRED" translate-values="{'field': 'Value'}"></span>
+ <span ng-show="editForm.currentValue.$error.pattern && parameter.type==='string'" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span>
+ <span ng-show="editForm.currentValue.$error.pattern && !(parameter.type==='string')" translate="VALIDATION_ERROR_TYPE" translate-values="{'type': '{{parameter.type}}'}"></span>
+ </div>
+ </ng-form>
+ </div>
+ <div class="i-sdc-env-form-label-description">
+ <label class="i-sdc-env-form-label" data-ng-bind="parameter.description" tooltips tooltip-content="{{parameter.description}}"></label>
+ </div>
+ </div>
+ </perfect-scrollbar>
+ <div class="env-file-generation-label" data-ng-if="isInstance()" translate="ENV_FILE_GENERATION"></div>
+ </form>
+ </div>
+ </div>
+</sdc-modal>
diff --git a/catalog-ui/app/scripts/view-models/forms/env-parameters-form/env-parameters-form.less b/catalog-ui/app/scripts/view-models/forms/env-parameters-form/env-parameters-form.less
new file mode 100644
index 0000000000..c58c94ab22
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/forms/env-parameters-form/env-parameters-form.less
@@ -0,0 +1,74 @@
+
+.sdc-env-form-container{
+ .w-sdc-modal-body{
+ padding: 20px 10px 2px 10px;
+ }
+ .w-sdc-modal-body-content{
+ .b_6;
+ display: block;
+ }
+
+ .env-file-generation-label{
+ .p_9;
+ .bold;
+ margin-bottom: 20px;
+ }
+}
+
+.w-sdc-env-form-container {
+ border-top: 1px solid #cdcdcd;
+ border-bottom: 1px solid #cdcdcd;
+ height: 356px;
+ margin: 35px 0 10px 0;
+
+ .w-sdc-form {
+ text-align: left;
+ }
+ .i-sdc-form-item{
+ display: inline-block;
+ .description{
+ margin-bottom: 2px;
+ vertical-align: baseline;
+ padding: 32px 0 0 0;
+ text-transform: capitalize;
+ .b_1;
+ }
+ }
+ .left-column-container{
+ width: 250px;
+ float: left;
+ .i-sdc-env-form-label {
+ overflow: hidden;
+ max-width: 100%;
+ text-overflow: ellipsis;
+ display: inline-block;
+ white-space: nowrap;
+
+ &.required::before {
+ color: #f33;
+ content: '*';
+ margin-right: 4px;
+ }
+ }
+ }
+ .i-sdc-env-form-label-description {
+ float: right;
+
+ .i-sdc-env-form-label {
+ .p_9;
+ // height: 20px;
+ margin: 30px 0px 0px 30px;
+ overflow: hidden;
+ max-width: 245px;
+ text-overflow: ellipsis;
+ display: inline-block;
+ white-space: nowrap;
+
+ &.required::before {
+ color: #f33;
+ content: '*';
+ margin-right: 4px;
+ }
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/forms/env-parameters-form/env-parameters-form.ts b/catalog-ui/app/scripts/view-models/forms/env-parameters-form/env-parameters-form.ts
new file mode 100644
index 0000000000..d1bae440cc
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/forms/env-parameters-form/env-parameters-form.ts
@@ -0,0 +1,149 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../../references"/>
+
+module Sdc.ViewModels {
+ 'use strict';
+
+ export interface IEnvParametersFormViewModelScope extends ng.IScope {
+ isLoading: boolean;
+ type:string;
+ heatParameters:any;
+ editForm:ng.IFormController;
+ artifactResource:Models.ArtifactModel;
+ saveButton: Array<any>;
+ envParametersModal: ng.ui.bootstrap.IModalServiceInstance;
+
+ getValidationPattern(type:string):RegExp;
+ isInstance():boolean;
+ validateJson(json:string):boolean;
+ close(): void;
+ save():void;
+ }
+
+ export class EnvParametersFormViewModel {
+
+
+ static '$inject' = [
+ '$scope',
+ '$state',
+ '$modalInstance',
+ 'artifact',
+ // 'ArtifactsUtils',
+ 'ValidationUtils',
+ 'component'
+ ];
+
+
+ constructor(private $scope:IEnvParametersFormViewModelScope,
+ private $state:any,
+ private $modalInstance:ng.ui.bootstrap.IModalServiceInstance,
+ private artifact:Models.ArtifactModel,
+ // private artifactsUtils:Sdc.Utils.ArtifactsUtils,
+ private ValidationUtils: Sdc.Utils.ValidationUtils,
+ private component:Models.Components.Component) {
+
+
+ this.initScope();
+ }
+
+ private updateInstanceHeat = ():void => {
+ let success =(responseArtifact:Models.ArtifactModel): void => {
+ this.$scope.isLoading = false;
+ this.$modalInstance.close();
+ };
+
+ let error = ():void => {
+ this.$scope.isLoading = false;
+ console.info('Failed to load save artifact');
+ };
+
+ this.component.addOrUpdateInstanceArtifact(this.$scope.artifactResource).then(success, error);
+
+ };
+
+ private initScope = ():void => {
+ this.$scope.envParametersModal = this.$modalInstance;
+ this.$scope.artifactResource= this.artifact;
+ this.$scope.heatParameters = angular.copy(this.artifact.heatParameters);
+
+ this.$scope.getValidationPattern = (validationType:string , parameterType?:string):RegExp => {
+ return this.ValidationUtils.getValidationPattern(validationType, parameterType);
+ };
+
+ this.$scope.validateJson = (json:string):boolean => {
+ if(!json){
+ return true;
+ }
+ return this.ValidationUtils.validateJson(json);
+ };
+
+ this.$scope.isInstance =(): boolean =>{
+ return !!this.component.selectedInstance;
+ };
+
+
+ this.$scope.save = ():void => {
+ this.$scope.isLoading = true;
+ this.artifact.heatParameters = this.$scope.heatParameters;
+ this.artifact.heatParameters.forEach((parameter:any):void => {
+ /* if ("" === parameter.currentValue) {
+ parameter.currentValue = null;
+ }else */
+ if(!parameter.currentValue && parameter.defaultValue) {
+ parameter.currentValue = parameter.defaultValue;
+ }
+ });
+
+ if(this.$scope.isInstance()){
+ this.updateInstanceHeat();
+ return;
+ }
+
+ let success =(responseArtifact:Models.ArtifactModel): void => {
+ this.$scope.isLoading = false;
+ this.$modalInstance.close();
+
+ };
+
+ let error = ():void => {
+ this.$scope.isLoading = false;
+ console.info('Failed to load save artifact');
+ };
+
+ this.component.addOrUpdateArtifact(this.$scope.artifactResource).then(success, error);
+ };
+
+ this.$scope.saveButton = [
+ {'name': 'Save', 'css': 'blue', 'callback': this.$scope.save}
+ ];
+
+ this.$scope.close = ():void => {
+ //this.artifact.heatParameters.forEach((parameter:any):void => {
+ // if (!parameter.currentValue && parameter.defaultValue) {
+ // parameter.currentValue = parameter.defaultValue;
+ // }
+ //});
+ this.$modalInstance.dismiss();
+ };
+
+ };
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/forms/property-form/property-form-view-model.ts b/catalog-ui/app/scripts/view-models/forms/property-form/property-form-view-model.ts
new file mode 100644
index 0000000000..c9732aa9a6
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/forms/property-form/property-form-view-model.ts
@@ -0,0 +1,330 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../../references"/>
+
+module Sdc.ViewModels {
+ 'use strict';
+
+ export interface IEditPropertyModel {
+ property: Models.PropertyModel;
+ types: Array<string>;
+ simpleTypes: Array<string>;
+ sources: Array<string>;
+ }
+
+ interface IPropertyFormViewModelScope extends ng.IScope {
+ forms:any;
+ editForm:ng.IFormController;
+ footerButtons: Array<any>;
+ isNew: boolean;
+ isLoading: boolean;
+ isService: boolean;
+ validationPattern: RegExp;
+ propertyNameValidationPattern: RegExp;
+ commentValidationPattern: RegExp;
+ editPropertyModel: IEditPropertyModel;
+ modalInstanceProperty:ng.ui.bootstrap.IModalServiceInstance;
+ currentPropertyIndex:number;
+ isLastProperty:boolean;
+ myValue:any;
+ nonPrimitiveTypes:Array<string>;
+ dataTypes:Models.DataTypesMap;
+ isTypeDataType:boolean;
+ maxLength:number;
+
+ save(doNotCloseModal?:boolean): void;
+ getValidationPattern(type:string): RegExp;
+ validateIntRange(value:string):boolean;
+ close(): void;
+ onValueChange(): void;
+ onSchemaTypeChange():void;
+ onTypeChange(resetSchema:boolean): void;
+ isPropertyValueOwner():boolean;
+ showSchema(): boolean;
+ delete(property:Models.PropertyModel): void;
+ getPrev(): void;
+ getNext(): void;
+ isSimpleType(typeName:string):boolean;
+ getDefaultValue():any;
+ }
+
+ export class PropertyFormViewModel {
+
+ static '$inject' = [
+ '$scope',
+ 'Sdc.Services.DataTypesService',
+ '$modalInstance',
+ 'property',
+ 'ValidationPattern',
+ 'PropertyNameValidationPattern',
+ 'CommentValidationPattern',
+ 'ValidationUtils',
+ 'component',
+ '$filter',
+ 'ModalsHandler',
+ 'filteredProperties',
+ '$timeout'
+ ];
+
+ private formState: Sdc.Utils.Constants.FormState;
+
+ constructor(private $scope:IPropertyFormViewModelScope,
+ private DataTypesService:Sdc.Services.DataTypesService,
+ private $modalInstance:ng.ui.bootstrap.IModalServiceInstance,
+ private property: Models.PropertyModel,
+ private ValidationPattern:RegExp,
+ private PropertyNameValidationPattern: RegExp,
+ private CommentValidationPattern:RegExp,
+ private ValidationUtils:Sdc.Utils.ValidationUtils,
+ private component: Models.Components.Component,
+ private $filter:ng.IFilterService,
+ private ModalsHandler:Utils.ModalsHandler,
+ private filteredProperties: Array<Models.PropertyModel>,
+ private $timeout: ng.ITimeoutService) {
+
+ this.formState = angular.isDefined(property.name) ? Utils.Constants.FormState.UPDATE : Utils.Constants.FormState.CREATE;
+ this.initScope();
+ }
+
+ private initResource = ():void => {
+ this.$scope.editPropertyModel.property = new Sdc.Models.PropertyModel(this.property);
+ this.$scope.editPropertyModel.property.type = this.property.type? this.property.type: null;
+ this.setMaxLength();
+ // if (this.$scope.editPropertyModel.types.indexOf(this.property.type) === -1 && !this.$scope.isNew) {
+ // this.property.type = "string";
+ // }
+ };
+
+ private initEditPropertyModel = ():void => {
+ this.$scope.editPropertyModel = {
+ property: null,
+ types: Utils.Constants.PROPERTY_DATA.TYPES,
+ simpleTypes: Utils.Constants.PROPERTY_DATA.SIMPLE_TYPES,
+ sources: Utils.Constants.PROPERTY_DATA.SOURCES
+ };
+
+ this.initResource();
+ };
+
+ private initForNotSimpleType = ():void => {
+ let property = this.$scope.editPropertyModel.property;
+ this.$scope.isTypeDataType=this.DataTypesService.isDataTypeForPropertyType(this.$scope.editPropertyModel.property,this.$scope.dataTypes);
+ if(property.type && this.$scope.editPropertyModel.simpleTypes.indexOf(property.type)==-1){
+ if(!(property.value||property.defaultValue)) {
+ switch (property.type) {
+ case Utils.Constants.PROPERTY_TYPES.MAP:
+ this.$scope.myValue = {'':null};
+ break;
+ case Utils.Constants.PROPERTY_TYPES.LIST:
+ this.$scope.myValue = [];
+ break;
+ default:
+ this.$scope.myValue = {};
+ }
+ }else{
+ this.$scope.myValue = JSON.parse(property.value||property.defaultValue);
+ }
+ }
+ };
+
+ private setMaxLength = ():void => {
+ switch (this.$scope.editPropertyModel.property.type) {
+ case Utils.Constants.PROPERTY_TYPES.MAP:
+ case Utils.Constants.PROPERTY_TYPES.LIST:
+ this.$scope.maxLength = this.$scope.editPropertyModel.property.schema.property.type == Utils.Constants.PROPERTY_TYPES.JSON?
+ Utils.Constants.PROPERTY_VALUE_CONSTRAINTS.JSON_MAX_LENGTH:
+ Utils.Constants.PROPERTY_VALUE_CONSTRAINTS.MAX_LENGTH;
+ break;
+ case Utils.Constants.PROPERTY_TYPES.JSON:
+ this.$scope.maxLength = Utils.Constants.PROPERTY_VALUE_CONSTRAINTS.JSON_MAX_LENGTH;
+ break;
+ default:
+ this.$scope.maxLength = Utils.Constants.PROPERTY_VALUE_CONSTRAINTS.MAX_LENGTH;
+ }
+ };
+
+
+ private initScope = ():void => {
+
+ //scope properties
+ this.$scope.forms = {};
+ this.$scope.validationPattern = this.ValidationPattern;
+ this.$scope.propertyNameValidationPattern = this.PropertyNameValidationPattern;
+ this.$scope.commentValidationPattern = this.CommentValidationPattern;
+ this.$scope.isLoading = false;
+ this.$scope.isNew = (this.formState === Utils.Constants.FormState.CREATE);
+ this.$scope.isService = this.component.isService();
+ this.$scope.modalInstanceProperty = this.$modalInstance;
+ this.$scope.currentPropertyIndex = _.findIndex(this.filteredProperties, i=> i.name == this.property.name );
+ this.$scope.isLastProperty= this.$scope.currentPropertyIndex==(this.filteredProperties.length-1);
+
+ this.initEditPropertyModel();
+
+ this.DataTypesService.getAllDataTypes().then((response:any) => {
+ this.$scope.dataTypes = response;
+ delete response['tosca.datatypes.Root'];
+ this.$scope.nonPrimitiveTypes =_.filter(Object.keys(response),(type:string)=>{
+ return this.$scope.editPropertyModel.types.indexOf(type)==-1;
+ });
+ this.initForNotSimpleType();
+ }, (err)=> {});
+
+
+
+
+
+ //scope methods
+ this.$scope.save = (doNotCloseModal?:boolean):void => {
+ let property:Models.PropertyModel = this.$scope.editPropertyModel.property;
+ this.$scope.editPropertyModel.property.description = this.ValidationUtils.stripAndSanitize(this.$scope.editPropertyModel.property.description);
+ ////if read only - just closes the modal
+ if (this.$scope.editPropertyModel.property.readonly && !this.$scope.isPropertyValueOwner()) {
+ this.$modalInstance.close();
+ return;
+ }
+
+ this.$scope.isLoading = true;
+
+ let onPropertyFaild = (response):void => {
+ console.info('onFaild', response);
+ this.$scope.isLoading = false;
+ };
+
+ let onPropertySuccess = (propertyFromBE:Models.PropertyModel):void => {
+ console.info('onPropertyResourceSuccess : ', propertyFromBE);
+ this.$scope.isLoading = false;
+
+ if (!doNotCloseModal) {
+ this.$modalInstance.close();
+ } else {
+ this.$scope.forms.editForm.$setPristine();
+ this.$scope.editPropertyModel.property = new Models.PropertyModel();
+ }
+ };
+
+ //in case we have uniqueId we call update method
+ if (this.$scope.isPropertyValueOwner()) {
+ if(!this.$scope.editPropertyModel.property.simpleType && !this.$scope.isSimpleType(property.type)){
+ let myValueString:string = JSON.stringify(this.$scope.myValue);
+ property.value = myValueString;
+ }
+ this.component.updateInstanceProperty(property).then(onPropertySuccess, onPropertyFaild);
+ } else {
+ if(!this.$scope.editPropertyModel.property.simpleType && !this.$scope.isSimpleType(property.type)){
+ let myValueString:string = JSON.stringify(this.$scope.myValue);
+ property.defaultValue = myValueString;
+ }else{
+ this.$scope.editPropertyModel.property.defaultValue = this.$scope.editPropertyModel.property.value;
+ }
+ this.component.addOrUpdateProperty(property).then(onPropertySuccess, onPropertyFaild);
+ }
+ };
+
+
+ this.$scope.isPropertyValueOwner = ():boolean=> {
+ return this.component.isService() || !!this.component.selectedInstance;
+ };
+
+ this.$scope.getPrev = ():void=> {
+ this.property = this.filteredProperties[--this.$scope.currentPropertyIndex];
+ this.initResource();
+ this.initForNotSimpleType();
+ this.$scope.isLastProperty=false;
+ };
+
+ this.$scope.getNext = ():void=> {
+ this.property = this.filteredProperties[++this.$scope.currentPropertyIndex];
+ this.initResource();
+ this.initForNotSimpleType();
+ this.$scope.isLastProperty= this.$scope.currentPropertyIndex==(this.filteredProperties.length-1);
+ };
+
+ this.$scope.isSimpleType = (typeName:string):boolean=>{
+ return typeName && this.$scope.editPropertyModel.simpleTypes.indexOf(typeName)!=-1;
+ };
+
+ this.$scope.showSchema = () :boolean => {
+ return [Utils.Constants.PROPERTY_TYPES.LIST, Utils.Constants.PROPERTY_TYPES.MAP].indexOf(this.$scope.editPropertyModel.property.type) > -1;
+ };
+
+ this.$scope.getValidationPattern = (type:string):RegExp => {
+ return this.ValidationUtils.getValidationPattern(type);
+ };
+
+ this.$scope.validateIntRange = (value:string):boolean => {
+ return !value || this.ValidationUtils.validateIntRange(value);
+ };
+
+ this.$scope.close = ():void => {
+ this.$modalInstance.close();
+ };
+
+ // put default value when instance value is empty
+ this.$scope.onValueChange = ():void => {
+ if (!this.$scope.editPropertyModel.property.value) {
+ if (this.$scope.isPropertyValueOwner()) {
+ this.$scope.editPropertyModel.property.value = this.$scope.editPropertyModel.property.defaultValue;
+ }
+ }
+ };
+
+ // Add the done button at the footer.
+ this.$scope.footerButtons = [
+ {'name': 'Save', 'css': 'blue', 'callback': this.$scope.save },
+ {'name': 'Cancel', 'css': 'grey', 'callback':this.$scope.close }
+ ];
+
+ this.$scope.$watch("forms.editForm.$invalid", (newVal, oldVal) => {
+ this.$scope.footerButtons[0].disabled = this.$scope.forms.editForm.$invalid;
+ });
+
+ this.$scope.getDefaultValue = ():any => {
+ return this.$scope.isPropertyValueOwner() ? this.$scope.editPropertyModel.property.defaultValue : null;
+ };
+
+ this.$scope.onTypeChange = ():void => {
+ this.$scope.editPropertyModel.property.value = '';
+ this.$scope.editPropertyModel.property.defaultValue = '';
+ this.setMaxLength();
+ this.initForNotSimpleType();
+ };
+
+ this.$scope.onSchemaTypeChange = ():void => {
+ if(this.$scope.editPropertyModel.property.type==Utils.Constants.PROPERTY_TYPES.MAP){
+ this.$scope.myValue={'':null};
+ }else if(this.$scope.editPropertyModel.property.type==Utils.Constants.PROPERTY_TYPES.LIST){
+ this.$scope.myValue=[];
+ }
+ this.setMaxLength();
+ };
+
+ this.$scope.delete = (property:Models.PropertyModel):void => {
+ let onOk = ():void => {
+ this.component.deleteProperty(property.uniqueId).then(
+ this.$scope.close
+ );
+ };
+ let title:string = this.$filter('translate')("PROPERTY_VIEW_DELETE_MODAL_TITLE");
+ let message:string = this.$filter('translate')("PROPERTY_VIEW_DELETE_MODAL_TEXT", "{'name': '" + property.name + "'}");
+ this.ModalsHandler.openConfirmationModal(title, message, false).then(onOk);
+ };
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/forms/property-form/property-form-view.html b/catalog-ui/app/scripts/view-models/forms/property-form/property-form-view.html
new file mode 100644
index 0000000000..d593d47a77
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/forms/property-form/property-form-view.html
@@ -0,0 +1,219 @@
+<sdc-modal modal="modalInstanceProperty" type="classic" class="sdc-edit-property-container" buttons="footerButtons" header="{{isNew ? 'Add' : 'Update' }} Property" show-close-button="true" data-tests-id="sdc-edit-property-container">
+ <div class="sdc-modal-top-bar" data-ng-if="!isNew">
+ <div class="sdc-modal-top-bar-buttons">
+ <span ng-click="delete(editPropertyModel.property)" data-ng-class="{'disabled' : isPropertyValueOwner()||editPropertyModel.property.readonly}" class="sprite-new delete-btn" data-tests-id="delete_property" sdc-smart-tooltip="">Delete</span>
+ <span class="delimiter"></span>
+ <span data-ng-click="getPrev()" data-ng-class="{'disabled' : !currentPropertyIndex }" class="sprite-new left-arrow" data-tests-id="get-prev" sdc-smart-tooltip="">Previous</span>
+ <span data-ng-click="getNext()" data-ng-class="{'disabled' : isLastProperty }" class="sprite-new right-arrow" data-tests-id="get-next" sdc-smart-tooltip="">Next</span>
+ </div>
+ </div>
+
+ <div class="sdc-edit-property-form-container" >
+ <perfect-scrollbar scroll-y-margin-offset="0" include-padding="true" class="scrollbar-container">
+ <form novalidate class="w-sdc-form two-columns" name="forms.editForm" >
+
+ <div class="w-sdc-form-columns-wrapper">
+
+ <div class="w-sdc-form-column">
+
+ <!-- Name -->
+ <div class="i-sdc-form-item" data-ng-class="{error:(forms.editForm.propertyName.$dirty && forms.editForm.propertyName.$invalid)}">
+ <label class="i-sdc-form-label" ng-class="{'required': !isService}">Name</label>
+ <input class="i-sdc-form-input"
+ data-tests-id="propertyName"
+ data-ng-maxlength="50"
+ data-ng-disabled="!isNew || editPropertyModel.property.readonly"
+ maxlength="50"
+ data-ng-model="editPropertyModel.property.name"
+ type="text"
+ name="propertyName"
+ data-ng-pattern="propertyNameValidationPattern"
+ data-required
+ data-ng-model-options="{ debounce: 200 }"
+ autofocus />
+
+ <div class="input-error" data-ng-show="forms.editForm.propertyName.$dirty && forms.editForm.propertyName.$invalid">
+ <span ng-show="forms.editForm.propertyName.$error.required" translate="VALIDATION_ERROR_REQUIRED" translate-values="{'field': 'Property name' }"></span>
+ <span ng-show="forms.editForm.propertyName.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '128' }"></span>
+ <span ng-show="forms.editForm.propertyName.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span>
+ </div>
+
+ </div>
+
+ <!-- Input source -->
+ <!--div class="i-sdc-form-item" data-ng-class="{error:(forms.editForm.source.$dirty && forms.editForm.source.$invalid)}">
+ <label class="i-sdc-form-label required">Input Source</label>
+ <select class="i-sdc-form-select"
+ data-required
+ name="source"
+ data-ng-model="editPropertyModel.property.source"
+ data-ng-options="source for source in editPropertyModel.sources">
+ <option value="" >Choose Input Source</option>
+ </select>
+ <sdc-error-tooltip data-ng-show="forms.editForm.source.$dirty && forms.editForm.source.$invalid">
+ <span ng-show="forms.editForm.source.$error.required">source is required.</span>
+ </sdc-error-tooltip>
+ </div-->
+
+
+ </div>
+
+ <div class="w-sdc-form-column">
+ <div class="w-sdc-form-columns-wrapper">
+ <div class="w-sdc-form-column">
+ <!-- Type -->
+ <div class="i-sdc-form-item" data-ng-class="{error:(forms.editForm.type.$dirty && forms.editForm.type.$invalid)}">
+ <label class="i-sdc-form-label" ng-class="{'required': !isService}">Type</label>
+ <select class="i-sdc-form-select"
+ data-tests-id="propertyType"
+ data-required
+ data-ng-disabled="isPropertyValueOwner() || editPropertyModel.property.readonly"
+ name="type"
+ data-ng-change="onTypeChange()"
+ data-ng-model="editPropertyModel.property.type">
+ <option value="">Choose Type</option>
+ <option data-ng-repeat="type in editPropertyModel.types"
+ value="{{type}}">{{type}}</option>
+ <option data-ng-repeat="type in nonPrimitiveTypes"
+ value="{{type}}">{{type.replace("org.openecomp.datatypes.heat.","")}}</option>
+ </select>
+
+ <div class="input-error" data-ng-show="forms.editForm.type.$dirty && forms.editForm.type.$invalid">
+ <span ng-show="forms.editForm.type.$error.required" translate="VALIDATION_ERROR_REQUIRED" translate-values="{'field': 'Type' }"></span>
+ </div>
+ </div>
+ </div>
+ <div class="w-sdc-form-column" data-ng-if="showSchema()">
+ <!-- Entry Schema -->
+ <div class="i-sdc-form-item" data-ng-class="{error:(forms.editForm.schemaType.$dirty && forms.editForm.schemaType.$invalid)}">
+ <label class="i-sdc-form-label required">Entry Schema</label>
+ <select class="i-sdc-form-select"
+ data-required
+ data-tests-id="schema-type"
+ data-ng-disabled="isPropertyValueOwner() || editPropertyModel.property.readonly"
+ name="schemaType"
+ data-ng-change="onSchemaTypeChange()"
+ data-ng-model="editPropertyModel.property.schema.property.type">
+ <option value="">Choose Schema Type</option>
+ <option data-ng-repeat="type in editPropertyModel.simpleTypes"
+ value="{{type}}">{{type}}</option>
+ <option data-ng-repeat="type in nonPrimitiveTypes"
+ value="{{type}}">{{type.replace("org.openecomp.datatypes.heat.","")}}</option>
+ </select>
+
+ <div class="input-error" data-ng-show="forms.editForm.schemaType.$dirty && forms.editForm.schemaType.$invalid">
+ <span ng-show="forms.editForm.schemaType.$error.required" translate="VALIDATION_ERROR_REQUIRED" translate-values="{'field': 'Entry schema' }"></span>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <!-- Constraints by type -->
+ <div class="i-sdc-form-item" data-ng-if="false">
+ <label class="i-sdc-form-label required">Constraints by type</label>
+ <div>
+ Should be constraints by type(TBD)
+ </div>
+ </div>
+
+ </div>
+
+ </div>
+ <!-- Description -->
+ <div class="i-sdc-form-item" data-ng-class="{error:(forms.editForm.description.$dirty && forms.editForm.description.$invalid)}">
+ <label class="i-sdc-form-label">Description</label>
+ <textarea class="i-sdc-form-textarea"
+ data-ng-maxlength="256"
+ data-ng-disabled="isPropertyValueOwner() || editPropertyModel.property.readonly"
+ maxlength="256"
+ data-ng-pattern="commentValidationPattern"
+ name="description"
+ data-ng-model="editPropertyModel.property.description"
+ data-ng-model-options="{ debounce: 200 }"
+ data-tests-id="description"
+ ></textarea>
+
+ <div class="input-error" data-ng-show="forms.editForm.description.$dirty && forms.editForm.description.$invalid">
+ <span ng-show="forms.editForm.description.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '256' }"></span>
+ <span ng-show="forms.editForm.description.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span>
+ <span ng-show="forms.editForm.description.$error.required" translate="VALIDATION_ERROR_REQUIRED" translate-values="{'field': 'Description' }"></span>
+ </div>
+ </div>
+ <!-- Default value -->
+
+ <div data-ng-if="dataTypes" class="default-value-section i-sdc-form-item">
+ <label class="i-sdc-form-label">Default Value</label>
+ <div data-ng-if="isTypeDataType">
+ <fields-structure value-obj-ref="myValue"
+ type-name="editPropertyModel.property.type"
+ parent-form-obj="forms.editForm"
+ fields-prefix-name="currentPropertyIndex"
+ read-only="editPropertyModel.property.readonly && !isPropertyValueOwner()"
+ default-value="{{getDefaultValue()}}"
+ types="dataTypes"
+ expand-by-default="true"></fields-structure>
+
+ </div>
+ <div data-ng-if="!isTypeDataType" ng-switch="editPropertyModel.property.type">
+ <div ng-switch-when="map">
+ <type-map value-obj-ref="myValue"
+ schema-property="editPropertyModel.property.schema.property"
+ parent-form-obj="forms.editForm"
+ fields-prefix-name="currentPropertyIndex"
+ read-only="editPropertyModel.property.readonly && !isPropertyValueOwner()"
+ default-value="{{getDefaultValue()}}"
+ types="dataTypes"
+ max-length="maxLength"></type-map>
+ </div>
+ <div ng-switch-when="list">
+ <type-list value-obj-ref="myValue"
+ schema-property="editPropertyModel.property.schema.property"
+ parent-form-obj="forms.editForm"
+ fields-prefix-name="currentPropertyIndex"
+ read-only="editPropertyModel.property.readonly && !isPropertyValueOwner()"
+ default-value="{{getDefaultValue()}}"
+ types="dataTypes"
+ max-length="maxLength"></type-list>
+ </div>
+ <div ng-switch-default>
+ <div class="i-sdc-form-item" data-ng-class="{error:(forms.editForm.value.$dirty && forms.editForm.value.$invalid)}">
+ <input class="i-sdc-form-input"
+ data-tests-id="defaultvalue"
+ ng-if="!((editPropertyModel.property.simpleType||editPropertyModel.property.type) == 'boolean')"
+ data-ng-maxlength="maxLength"
+ data-ng-disabled="editPropertyModel.property.readonly && !isPropertyValueOwner()"
+ maxlength="{{maxLength}}"
+ data-ng-model="editPropertyModel.property.value"
+ type="text"
+ name="value"
+ data-ng-pattern="getValidationPattern((editPropertyModel.property.simpleType||editPropertyModel.property.type))"
+ data-ng-model-options="{ debounce: 200 }"
+ data-ng-change="!forms.editForm.value.$error.pattern && ('integer'==editPropertyModel.property.type && forms.editForm.value.$setValidity('pattern', validateIntRange(editPropertyModel.property.value)) || onValueChange())"
+ autofocus />
+ <select class="i-sdc-form-select"
+ data-tests-id="booleantype"
+ ng-if="(editPropertyModel.property.simpleType||editPropertyModel.property.type) == 'boolean'"
+ data-ng-disabled="editPropertyModel.property.readonly && !isPropertyValueOwner()"
+ name="value"
+ data-ng-change="onValueChange()"
+ data-ng-model="editPropertyModel.property.value">
+ <option value="true">true</option>
+ <option value="false">false</option>
+ </select>
+
+ <div class="input-error" data-ng-show="forms.editForm.value.$dirty && forms.editForm.value.$invalid">
+ <span ng-show="forms.editForm.value.$error.required" translate="VALIDATION_ERROR_REQUIRED" translate-values="{'field': 'Property' }"></span>
+ <span ng-show="forms.editForm.value.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '{{maxLength}}' }"></span>
+ <span ng-show="forms.editForm.value.$error.pattern" translate="PROPERTY_EDIT_PATTERN"></span>
+ <span ng-show="forms.editForm.value.$error.customValidation" translate="PROPERTY_EDIT_MAP_UNIQUE_KEYS"></span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <span class="w-sdc-form-note" data-ng-show="forms.editForm.$invalid && false" translate="LABEL_ALL_FIELDS_ARE_MANDATORY"></span>
+ </form>
+ </perfect-scrollbar>
+ </div>
+
+</sdc-modal>
diff --git a/catalog-ui/app/scripts/view-models/forms/property-form/property-form.less b/catalog-ui/app/scripts/view-models/forms/property-form/property-form.less
new file mode 100644
index 0000000000..15e30af4ee
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/forms/property-form/property-form.less
@@ -0,0 +1,63 @@
+.sdc-edit-property-container {
+ .scrollbar-container{
+ height: 415px;
+ width: 830px;
+ .perfect-scrollbar;
+ }
+
+ form{
+ width: 813px;
+ [name="description"]{
+ min-height:50px;
+ }
+ }
+
+ .sdc-modal-top-bar{
+ height: 40px;
+ .sdc-modal-top-bar-buttons {
+ float: right;
+
+ > span:not(.delimiter){
+ vertical-align: middle;
+ .hand;
+
+ &.sprite-new {
+ text-indent: 100%;
+ }
+ &.disabled, &:hover.disabled {
+ pointer-events: none;
+ }
+ }
+
+ .delete-btn{
+ margin-right: 6px;
+ }
+
+ .left-arrow{
+ margin-right: 8px;
+ }
+
+ .delimiter {
+ height: 20px;
+ width: 1px;
+ background-color: #959595;
+ display: inline-block;
+ vertical-align: middle;
+ margin-right: 10px;
+ }
+ }
+ }
+
+ .w-sdc-form-note {
+ .h_9;
+ display: block;
+ position: relative;
+ top: 13px;
+ }
+
+ .default-value-section{
+ border-top: solid 1px @main_color_a;
+ padding-top: 15px;
+ margin-top: 15px;
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/forms/resource-instance-name-form/resource-instance-name-model.ts b/catalog-ui/app/scripts/view-models/forms/resource-instance-name-form/resource-instance-name-model.ts
new file mode 100644
index 0000000000..b69bf4a2a6
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/forms/resource-instance-name-form/resource-instance-name-model.ts
@@ -0,0 +1,105 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../../references"/>
+
+module Sdc.ViewModels {
+ 'use strict';
+
+ interface IResourceInstanceViewModelScope extends ng.IScope {
+
+ componentInstanceModel: Sdc.Models.ComponentsInstances.ComponentInstance;
+ validationPattern: RegExp;
+ oldName:string;
+ isAlreadyPressed:boolean;
+ footerButtons: Array<any>;
+ forms:any;
+ modalInstanceName:ng.ui.bootstrap.IModalServiceInstance;
+
+ save(): void;
+ close(): void;
+ }
+
+ export class ResourceInstanceNameViewModel {
+
+ static '$inject' = [
+ '$scope',
+ 'ValidationPattern',
+ '$modalInstance',
+ 'ComponentInstanceFactory',
+ 'component'
+ ];
+
+
+ constructor(private $scope:IResourceInstanceViewModelScope,
+ private ValidationPattern:RegExp,
+ private $modalInstance:ng.ui.bootstrap.IModalServiceInstance,
+ private ComponentInstanceFactory:Utils.ComponentInstanceFactory,
+ private component:Models.Components.Component) {
+
+ this.initScope();
+ }
+
+
+ private initScope = ():void => {
+ this.$scope.forms = {};
+ this.$scope.validationPattern = this.ValidationPattern;
+ this.$scope.componentInstanceModel = Utils.ComponentInstanceFactory.createComponentInstance(this.component.selectedInstance);
+ this.$scope.oldName = this.component.selectedInstance.name;
+ this.$scope.modalInstanceName = this.$modalInstance;
+
+ this.$scope.isAlreadyPressed = false;
+
+
+ this.$scope.close = ():void => {
+ this.$modalInstance.dismiss();
+ };
+
+ this.$scope.save = ():void => {
+
+ let onFailed = () => {
+ this.$scope.isAlreadyPressed = true;
+ };
+
+ let onSuccess = (componentInstance:Models.ComponentsInstances.ComponentInstance) => {
+ this.$modalInstance.close();
+ this.$scope.isAlreadyPressed = false;
+ this.$scope.componentInstanceModel = componentInstance;
+ //this.component.name = componentInstance.name;//DE219124
+ this.component.selectedInstance.name = componentInstance.name;
+
+ };
+
+ this.$scope.isAlreadyPressed = true;
+ if (this.$scope.oldName != this.$scope.componentInstanceModel.name) {
+ this.component.updateComponentInstance(this.$scope.componentInstanceModel).then(onSuccess, onFailed);
+ }
+ };
+
+ this.$scope.footerButtons = [
+ {'name': 'OK', 'css': 'blue', 'callback': this.$scope.save, 'disabled': (!this.$scope.componentInstanceModel.name || this.$scope.componentInstanceModel.name === this.$scope.oldName) || this.$scope.isAlreadyPressed},
+ {'name': 'Cancel', 'css': 'grey', 'callback': this.$scope.close}
+ ];
+
+ this.$scope.$watch("forms.editNameForm.$invalid", (newVal, oldVal) => {
+ this.$scope.footerButtons[0].disabled = this.$scope.forms.editNameForm.$invalid;
+ });
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/forms/resource-instance-name-form/resource-instance-name-view.html b/catalog-ui/app/scripts/view-models/forms/resource-instance-name-form/resource-instance-name-view.html
new file mode 100644
index 0000000000..e04343adbd
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/forms/resource-instance-name-form/resource-instance-name-view.html
@@ -0,0 +1,72 @@
+<sdc-modal modal="modalInstanceName" type="classic" class="w-sdc-modal-resource-instance-name modal-type-confirmation" buttons="footerButtons" header="Instance Name" show-close-button="true">
+
+ <form novalidate class="w-sdc-form" name="forms.editNameForm">
+ <div class="i-sdc-form-item" data-ng-class="{error:(editNameForm.componentInstanceName.$dirty && editNameForm.resourceInstanceName.$invalid)}">
+ <label class="i-sdc-form-label required">Instance Name</label>
+ <input class="w-sdc-modal-resource-instance-input i-sdc-form-input"
+ name="componentInstanceName"
+ data-ng-maxlength="50"
+ data-ng-model="componentInstanceModel.name"
+ type="text"
+ data-required
+ data-ng-pattern="validationPattern"
+ maxlength="50"
+ autofocus
+ placeholder="Enter instance name..."
+ data-tests-id="instanceName"
+ />
+
+ <div class="input-error" data-ng-show="forms.editNameForm.componentInstanceName.$dirty && forms.editNameForm.componentInstanceName.$invalid">
+ <span ng-show="forms.editNameForm.componentInstanceName.$error.required" translate="VALIDATION_ERROR_REQUIRED" translate-values="{'field': 'Resource name' }"></span>
+ <span ng-show="forms.editNameForm.componentInstanceName.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '50' }"></span>
+ <span ng-show="forms.editNameForm.componentInstanceName.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span>
+ </div>
+
+ </div>
+ </form>
+
+</sdc-modal>
+
+
+
+<!--
+
+<div class="w-sdc-modal w-sdc-modal-resource-instance-name">
+ <header>
+ <div class="w-sdc-modal-head">
+ Instance Name
+ </div>
+ </header>
+ <div>
+ <form novalidate class="w-sdc-modal-body w-sdc-form" name="editNameForm">
+ <div class="i-sdc-form-item" data-ng-class="{error:(editNameForm.componentInstanceName.$dirty && editNameForm.resourceInstanceName.$invalid)}">
+ <label class="i-sdc-form-label required">Instance Name</label>
+ <input class="w-sdc-modal-resource-instance-input i-sdc-form-input"
+ name="componentInstanceName"
+ data-ng-maxlength="50"
+ data-ng-model="componentInstanceModel.name"
+ type="text"
+ data-required
+ data-ng-pattern="validationPattern"
+ maxlength="50"
+ autofocus
+ placeholder="Enter instance name..."
+ data-tests-id="instanceName"
+ />
+
+ <div class="input-error" data-ng-show="editNameForm.componentInstanceName.$dirty && editNameForm.componentInstanceName.$invalid">
+ <span ng-show="editNameForm.componentInstanceName.$error.required" translate="VALIDATION_ERROR_REQUIRED" translate-values="{'field': 'Resource name' }"></span>
+ <span ng-show="editNameForm.componentInstanceName.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '50' }"></span>
+ <span ng-show="editNameForm.componentInstanceName.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span>
+ </div>
+
+ </div>
+ </form>
+
+ <div class="w-sdc-modal-action">
+ <button class="w-sdc-btn-blue" data-ng-click="save()" type="button" data-ng-disabled="(!componentInstanceModel.name || componentInstanceModel.name === oldName) || isAlreadyPressed">Ok</button>
+ <button class="w-sdc-btn-dark-gray" data-ng-click="close()" type="button">Cancel</button>
+ </div>
+ </div>
+</div>
+-->
diff --git a/catalog-ui/app/scripts/view-models/forms/resource-instance-name-form/resource-instance-name.less b/catalog-ui/app/scripts/view-models/forms/resource-instance-name-form/resource-instance-name.less
new file mode 100644
index 0000000000..57698bef17
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/forms/resource-instance-name-form/resource-instance-name.less
@@ -0,0 +1,29 @@
+.w-sdc-modal-resource-instance-name {
+
+ .w-sdc-modal-body {
+ overflow: visible;
+ }
+
+ .w-sdc-modal-action {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ .w-sdc-modal-resource-instance-input {
+ .p_1;
+ border: solid 1px @color_p;
+ height: 45px;
+ padding: 0 20px;
+ margin: 0 auto 0 auto;
+ display: block;
+ }
+ .w-sdc-modal-body {
+ border-bottom: none;
+ }
+
+ .w-sdc-form .i-sdc-form-item.error::after {
+ top: 13px;
+ }
+
+}
diff --git a/catalog-ui/app/scripts/view-models/modals/confirmation-modal/confirmation-modal-view-model.ts b/catalog-ui/app/scripts/view-models/modals/confirmation-modal/confirmation-modal-view-model.ts
new file mode 100644
index 0000000000..f906593d8a
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/modals/confirmation-modal/confirmation-modal-view-model.ts
@@ -0,0 +1,96 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../../references"/>
+module Sdc.ViewModels {
+ 'use strict';
+
+ export interface IConfirmationModalModel {
+ title: string;
+ message: string;
+ showComment: boolean;
+ type: Utils.Constants.ModalType;
+ }
+
+ interface IConfirmationModalViewModelScope {
+ modalInstanceConfirmation:ng.ui.bootstrap.IModalServiceInstance;
+ confirmationModalModel: IConfirmationModalModel;
+ comment: any;
+ commentValidationPattern:RegExp;
+ editForm:ng.IFormController;
+ okButtonColor: string;
+ hideCancelButton: boolean;
+ ok(): any;
+ cancel(): void;
+ }
+
+ export class ConfirmationModalViewModel {
+
+ static '$inject' = ['$scope', '$modalInstance', 'confirmationModalModel', 'CommentValidationPattern', 'ValidationUtils', '$templateCache', '$modal'];
+
+ constructor(private $scope:IConfirmationModalViewModelScope,
+ private $modalInstance:ng.ui.bootstrap.IModalServiceInstance,
+ confirmationModalModel:IConfirmationModalModel,
+ private CommentValidationPattern: RegExp,
+ private ValidationUtils: Sdc.Utils.ValidationUtils,
+ private $templateCache:ng.ITemplateCacheService,
+ private $modal:ng.ui.bootstrap.IModalService) {
+
+ this.initScope(confirmationModalModel);
+ }
+
+ private initScope = (confirmationModalModel:IConfirmationModalModel):void => {
+ let self = this;
+ this.$scope.hideCancelButton = false;
+ this.$scope.modalInstanceConfirmation = this.$modalInstance;
+ this.$scope.confirmationModalModel = confirmationModalModel;
+ this.$scope.comment = {"text": ''};
+ this.$scope.commentValidationPattern = this.CommentValidationPattern;
+
+ this.$scope.ok = ():any => {
+ self.$modalInstance.close(this.ValidationUtils.stripAndSanitize(self.$scope.comment.text));
+ };
+
+ this.$scope.cancel = ():void => {
+ console.info('Cancel pressed on: ' + this.$scope.confirmationModalModel.title);
+ self.$modalInstance.dismiss();
+ };
+
+ // Set the OK button color according to modal type (standard, error, alert)
+ let _okButtonColor = 'blue'; // Default
+ switch (confirmationModalModel.type) {
+ case Sdc.Utils.Constants.ModalType.STANDARD:
+ _okButtonColor='blue';
+ break;
+ case Sdc.Utils.Constants.ModalType.ERROR:
+ _okButtonColor='red';
+ break;
+ case Sdc.Utils.Constants.ModalType.ALERT:
+ this.$scope.hideCancelButton = true;
+ _okButtonColor='grey';
+ break;
+ default:
+ _okButtonColor='blue';
+ break;
+ }
+ this.$scope.okButtonColor = _okButtonColor;
+
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/modals/confirmation-modal/confirmation-modal-view.html b/catalog-ui/app/scripts/view-models/modals/confirmation-modal/confirmation-modal-view.html
new file mode 100644
index 0000000000..09c27f8cd3
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/modals/confirmation-modal/confirmation-modal-view.html
@@ -0,0 +1,29 @@
+<sdc-modal modal="modalInstanceConfirmation" type="classic" class="w-sdc-modal-confirmation modal-type-{{confirmationModalModel.type}}" header="{{confirmationModalModel.title}}" show-close-button="true">
+ <form novalidate class="w-sdc-form" name="editForm">
+ <label class="i-sdc-form-label required w-sdc-modal-label" data-ng-bind-html="confirmationModalModel.message"></label>
+
+ <div class="i-sdc-form-item">
+ <textarea class="w-sdc-modal-body-comment"
+ data-tests-id="checkindialog"
+ autofocus="autofocus"
+ data-ng-show="confirmationModalModel.showComment===true"
+ data-ng-model="comment.text"
+ placeholder="Comment..."
+ maxlength="256"
+ data-required
+ name="comment1"
+ data-ng-pattern="commentValidationPattern"
+ data-ng-maxlength="256"></textarea>
+
+ <div class="input-error" data-ng-show="editForm.comment1.$dirty && editForm.comment1.$invalid">
+ <span ng-show="editForm.comment1.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span>
+ <span ng-show="editForm.comment1.$error.required" translate="VALIDATION_ERROR_REQUIRED" translate-values="{'field': 'Comment' }"></span>
+ </div>
+ </div>
+ </form>
+ <div class="w-sdc-modal-footer classic">
+ <button class="tlv-btn {{okButtonColor}}" data-tests-id="OK" data-ng-click="ok()" data-ng-disabled="confirmationModalModel.showComment===true && (!comment.text || comment.text && comment.text.length===0)">OK</button>
+ <button class="tlv-btn grey" data-ng-if="hideCancelButton===false" data-tests-id="Cancel" data-ng-click="cancel()" >Cancel</button>
+ <button class="tlv-btn blue add-property-add-another" data-ng-if="isNew" data-ng-click="saveAndAnother()" type="reset" data-ng-disabled="editForm.$invalid">Add Another</button>
+ </div>
+</sdc-modal>
diff --git a/catalog-ui/app/scripts/view-models/modals/confirmation-modal/confirmation-modal.less b/catalog-ui/app/scripts/view-models/modals/confirmation-modal/confirmation-modal.less
new file mode 100644
index 0000000000..666c41d5ed
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/modals/confirmation-modal/confirmation-modal.less
@@ -0,0 +1,30 @@
+.w-sdc-modal-confirmation {
+ form.w-sdc-form{
+ padding: 0;
+ }
+
+ .w-sdc-modal-body-content {
+ .b_6;
+ word-break: break-word;
+
+ }
+ .w-sdc-modal-body {
+ height: auto;
+ /* padding: 47px 60px 20px 60px; */
+ border-bottom: none;
+ }
+ .w-sdc-modal-body-content {
+ padding: 0;
+ }
+ .w-sdc-modal-body-comment {
+ width: 430px;
+ height: 127px;
+ border: solid 1px @color_e;
+ margin: 20px 0 0 0;
+ padding: 15px;
+ }
+ .w-sdc-modal-label {
+ .m_14_r;
+ text-align: left;
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/modals/email-modal/email-modal-view-model.ts b/catalog-ui/app/scripts/view-models/modals/email-modal/email-modal-view-model.ts
new file mode 100644
index 0000000000..6430a955a6
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/modals/email-modal/email-modal-view-model.ts
@@ -0,0 +1,117 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../../references"/>
+module Sdc.ViewModels {
+ 'use strict';
+
+ export interface IEmailModalModel_Email {
+ to: string;
+ subject: string;
+ message: string;
+ }
+
+ export interface IEmailModalModel_Data {
+ component: Models.Components.Component;
+ stateUrl: string;
+ }
+
+ export interface IEmailModalModel {
+ title: string;
+ email: IEmailModalModel_Email;
+ data: IEmailModalModel_Data;
+ }
+
+ interface IEmailModalViewModelScope {
+ modalInstanceEmail:ng.ui.bootstrap.IModalServiceInstance;
+ emailModalModel: IEmailModalModel;
+ submitInProgress:boolean;
+ commentValidationPattern:RegExp;
+ isLoading:boolean;
+ submit(): any;
+ cancel(): void;
+ validateField(field:any):boolean;
+ }
+
+ export class EmailModalViewModel {
+
+ static '$inject' = ['$scope', '$filter', 'sdcConfig', '$modalInstance', 'emailModalModel', 'ValidationUtils', 'CommentValidationPattern'];
+
+ constructor(private $scope:IEmailModalViewModelScope,
+ private $filter:ng.IFilterService,
+ private sdcConfig:Models.IAppConfigurtaion,
+ private $modalInstance:ng.ui.bootstrap.IModalServiceInstance,
+ private emailModalModel:IEmailModalModel,
+ private ValidationUtils: Sdc.Utils.ValidationUtils,
+ private CommentValidationPattern: RegExp) {
+
+ this.initScope(emailModalModel);
+ }
+
+ private initScope = (emailModalModel:IEmailModalModel):void => {
+ this.$scope.emailModalModel = emailModalModel;
+ this.$scope.submitInProgress=false;
+ this.$scope.commentValidationPattern = this.CommentValidationPattern;
+ this.$scope.modalInstanceEmail = this.$modalInstance;
+
+ this.$scope.submit = ():any => {
+
+ let onSuccess = (component:Models.Components.Component) => {
+ this.$scope.isLoading = false;
+ this.$scope.submitInProgress=false;
+ let link:string = encodeURI(this.sdcConfig.api.baseUrl + "?folder=Ready_For_Testing");
+ let outlook:string = this.$filter('translate')("EMAIL_OUTLOOK_MESSAGE", "{'to': '" + emailModalModel.email.to + "','subject': '" + emailModalModel.email.subject + "','message': '" + emailModalModel.email.message + "', 'entityNameAndVersion': '" + emailModalModel.email.subject + "','link': '" + link + "'}");
+ if(!this.sdcConfig.openSource) {
+ window.location.href=outlook; // Open outlook with the email to send
+ }
+ this.$modalInstance.close(component); // Close the dialog
+ };
+
+ let onError = () => {
+ this.$scope.isLoading = false;
+ this.$scope.submitInProgress=false;
+ this.$modalInstance.close(); // Close the dialog
+ };
+
+ // Submit to server
+ // Prevent from user pressing multiple times on submit.
+ if (this.$scope.submitInProgress===false) {
+ this.$scope.isLoading = true;
+ this.$scope.submitInProgress = true;
+ let comment:Models.AsdcComment = new Models.AsdcComment();
+ comment.userRemarks = emailModalModel.email.message;
+ emailModalModel.data.component.changeLifecycleState(emailModalModel.data.stateUrl, comment).then(onSuccess, onError);
+ }
+ };
+
+ this.$scope.cancel = ():void => {
+ this.$modalInstance.dismiss();
+ };
+
+ this.$scope.validateField = (field:any):boolean => {
+ if (field && field.$dirty && field.$invalid){
+ return true;
+ }
+ return false;
+ };
+ }
+
+
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/modals/email-modal/email-modal-view.html b/catalog-ui/app/scripts/view-models/modals/email-modal/email-modal-view.html
new file mode 100644
index 0000000000..82293a3091
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/modals/email-modal/email-modal-view.html
@@ -0,0 +1,79 @@
+<sdc-modal modal="modalInstanceEmail" type="classic" class="w-sdc-modal-email modal-type-standard" header="{{emailModalModel.title}}" show-close-button="true">
+ <loader data-display="isLoading"></loader>
+ <form novalidate class="w-sdc-form" name="editForm">
+ <div class="w-sdc-modal-body">
+
+ <div class="i-sdc-form-item" data-ng-class="{'error': validateField(editForm.to)}">
+ <label class="i-sdc-form-label col-sm-2">To</label>
+ <div class="col-sm-10">
+ <input class="i-sdc-form-input" type="text"
+ data-ng-model="emailModalModel.email.to"
+ data-ng-model-options="{ debounce: 500 }"
+ data-ng-maxlength="255"
+ data-required
+ name="to"
+ id="to"
+ data-ng-disabled="true"
+ />
+ </div>
+
+ <div class="input-error" data-ng-show="validateField(editForm.to)" alignToSelector="#to">
+ <span ng-show="editForm.to.$error.required" translate="VALIDATION_ERROR_REQUIRED" translate-values="{'field': 'To' }"></span>
+ <span ng-show="editForm.to.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '255' }"></span>
+ <span ng-show="editForm.to.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span>
+ </div>
+
+ </div>
+
+ <div class="i-sdc-form-item" data-ng-class="{'error': validateField(editForm.subject)}">
+ <label class="i-sdc-form-label col-sm-2">Subject</label>
+ <div class="col-sm-10">
+ <input class="i-sdc-form-input" type="text"
+ data-ng-model="emailModalModel.email.subject"
+ data-ng-model-options="{ debounce: 500 }"
+ data-ng-maxlength="255"
+ data-required
+ name="subject"
+ data-ng-disabled="true"
+ />
+ </div>
+
+ <div class="input-error" data-ng-show="validateField(editForm.subject)">
+ <span ng-show="editForm.subject.$error.required" translate="VALIDATION_ERROR_REQUIRED" translate-values="{'field': 'Subject' }"></span>
+ <span ng-show="editForm.subject.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '255' }"></span>
+ <span ng-show="editForm.subject.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span>
+ </div>
+
+ </div>
+
+ <div class="i-sdc-form-item" data-ng-class="{'error': validateField(editForm.message)}">
+ <label class="i-sdc-form-label required col-sm-2">Message</label>
+ <div class="col-sm-10">
+ <textarea class="w-sdc-modal-body-email"
+ data-ng-model="emailModalModel.email.message"
+ placeholder="{{'EMAIL_MODAL_MESSAGE' | translate }}"
+ data-required
+ name="message"
+ data-ng-pattern="commentValidationPattern"
+ maxlength="255"
+ data-tests-id="changeLifeCycleMessage"
+ data-ng-maxlength="255">
+ </textarea>
+
+ <div class="input-error" data-ng-show="validateField(editForm.message)">
+ <span ng-show="editForm.message.$error.required" translate="VALIDATION_ERROR_REQUIRED" translate-values="{'field': 'Message' }"></span>
+ <span ng-show="editForm.message.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '255' }"></span>
+ <span ng-show="editForm.message.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span>
+ </div>
+ </div>
+
+ </div>
+
+ </div>
+ </form>
+
+ <div class="w-sdc-modal-footer classic">
+ <button class="tlv-btn blue" data-tests-id="OK" data-ng-click="submit()" data-ng-disabled="editForm.$invalid">OK</button>
+ <button class="tlv-btn grey" data-tests-id="Cancel" data-ng-click="cancel()" >Cancel</button>
+ </div>
+</sdc-modal>
diff --git a/catalog-ui/app/scripts/view-models/modals/email-modal/email-modal.less b/catalog-ui/app/scripts/view-models/modals/email-modal/email-modal.less
new file mode 100644
index 0000000000..b946a097cd
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/modals/email-modal/email-modal.less
@@ -0,0 +1,56 @@
+.w-sdc-modal-email {
+
+ .w-sdc-modal-body {
+ border-bottom: none;
+ }
+
+ form.w-sdc-form{
+ padding: 0;
+
+ .i-sdc-form-item {
+ clear: both;
+ label {
+ min-height: 30px;
+ padding-top: 4px;
+ }
+
+ .col-sm-10 {
+ padding-right: 0;
+ }
+
+ }
+
+ .w-sdc-modal-body-email {
+ border-style: solid;
+ border-width: 1px;
+ border-color: @color_e;
+ box-sizing: border-box;
+ width: 100%;
+ height: 127px;
+ }
+
+ label {.m_14_m; text-align: left;}
+ input {.m_14_r;}
+ textarea {.m_14_r;}
+ /* I made the subject and to fields as input (for future use), but for now they look like labels: */
+ input:disabled {
+ .bg_c;
+ border: none;
+ }
+ }
+
+ .w-sdc-modal-action {
+ background-color: @main_color_p;
+ padding: 0 13px 0 0;
+ height: 90px;
+ line-height: 65px;
+
+ button {width: 174px;}
+ }
+
+ .w-sdc-form .i-sdc-form-item label.required::before {
+ position: absolute;
+ left: -13px;
+ }
+
+}
diff --git a/catalog-ui/app/scripts/view-models/modals/error-modal/error-403-view.html b/catalog-ui/app/scripts/view-models/modals/error-modal/error-403-view.html
new file mode 100644
index 0000000000..185fcce461
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/modals/error-modal/error-403-view.html
@@ -0,0 +1,4 @@
+<div class="sdc-error-403-container" >
+ <div class="sdc-error-403-container-title" translate="GENERAL_ERROR_403_TITLE"></div>
+ <div class="w-sdc-error-403-text w-sdc-form" translate="GENERAL_ERROR_403_DESCRIPTION" translate-values="{{ mailtoJson }}"></div>
+</div>
diff --git a/catalog-ui/app/scripts/view-models/modals/error-modal/error-view-model.ts b/catalog-ui/app/scripts/view-models/modals/error-modal/error-view-model.ts
new file mode 100644
index 0000000000..b8b2bfbbe7
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/modals/error-modal/error-view-model.ts
@@ -0,0 +1,43 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../../references"/>
+module Sdc.ViewModels {
+ 'use strict';
+
+ interface IErrorViewModelScope{
+ mailtoJson: any;
+ }
+
+ export class ErrorViewModel {
+
+ static '$inject' = ['$scope', 'Sdc.Services.CookieService', '$window', '$filter'];
+
+ constructor($scope:IErrorViewModelScope, cookieService:Services.CookieService, $window, $filter:ng.IFilterService){
+ let adminEmail:string = $filter('translate')('ADMIN_EMAIL');
+ let subjectPrefix:string = $filter('translate')('EMAIL_SUBJECT_PREFIX');
+ let userDetails = cookieService.getFirstName() + ' '+cookieService.getLastName() + ' ('+cookieService.getUserId() + ')';
+ let line = adminEmail+'?subject='+$window.encodeURIComponent(subjectPrefix+' '+userDetails);
+ $scope.mailtoJson = {
+ "mailto": line
+ };
+ }
+
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/modals/error-modal/error.less b/catalog-ui/app/scripts/view-models/modals/error-modal/error.less
new file mode 100644
index 0000000000..8297b5053d
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/modals/error-modal/error.less
@@ -0,0 +1,13 @@
+.sdc-error-403-container {
+ .bg_n;
+ width: 700px;
+ height: 400px;
+ margin: auto;
+ margin-top: 196px;
+
+ .w-sdc-error-403-text {
+ .q_11;
+ margin-top: 20px;
+ }
+
+}
diff --git a/catalog-ui/app/scripts/view-models/modals/message-modal/message-base-modal-model.ts b/catalog-ui/app/scripts/view-models/modals/message-modal/message-base-modal-model.ts
new file mode 100644
index 0000000000..26df780d25
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/modals/message-modal/message-base-modal-model.ts
@@ -0,0 +1,64 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../../references"/>
+module Sdc.ViewModels {
+ 'use strict';
+
+ export interface IMessageModalModel {
+ title: string;
+ message: string;
+ severity: Utils.Constants.SEVERITY;
+ }
+
+ export interface IMessageModalViewModelScope extends ng.IScope {
+ footerButtons: Array<any>;
+ messageModalModel: IMessageModalModel;
+ modalInstanceError:ng.ui.bootstrap.IModalServiceInstance;
+ ok(): void;
+ }
+
+ export class MessageModalViewModel {
+
+ constructor(private $baseScope:IMessageModalViewModelScope,
+ private $baseModalInstance:ng.ui.bootstrap.IModalServiceInstance,
+ private baseMessageModalModel:IMessageModalModel) {
+
+ this.initScope(baseMessageModalModel);
+ }
+
+ private initScope = (messageModalViewModel:IMessageModalModel):void => {
+
+ this.$baseScope.messageModalModel = messageModalViewModel;
+ this.$baseScope.modalInstanceError = this.$baseModalInstance;
+
+ this.$baseScope.ok = ():void => {
+ this.$baseModalInstance.close();
+ };
+
+ this.$baseScope.footerButtons = [
+ {
+ 'name': 'OK',
+ 'css': 'grey',
+ 'callback': this.$baseScope.ok
+ }
+ ];
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/modals/message-modal/message-client-modal/client-message-modal-view-model.ts b/catalog-ui/app/scripts/view-models/modals/message-modal/message-client-modal/client-message-modal-view-model.ts
new file mode 100644
index 0000000000..82afe11fe4
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/modals/message-modal/message-client-modal/client-message-modal-view-model.ts
@@ -0,0 +1,43 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../../../references"/>
+module Sdc.ViewModels {
+ 'use strict';
+
+ export interface IClientMessageModalModel extends IMessageModalModel {
+ }
+
+ export interface IClientMessageModalViewModelScope extends IMessageModalViewModelScope {
+ clientMessageModalModel: IClientMessageModalModel;
+ }
+
+ export class ClientMessageModalViewModel extends MessageModalViewModel {
+
+ static '$inject' = ['$scope', '$modalInstance', 'clientMessageModalModel'];
+
+ constructor(private $scope:IClientMessageModalViewModelScope,
+ private $modalInstance:ng.ui.bootstrap.IModalServiceInstance,
+ private clientMessageModalModel:IClientMessageModalModel) {
+
+ super($scope, $modalInstance, clientMessageModalModel);
+ }
+
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/modals/message-modal/message-client-modal/client-message-modal-view.html b/catalog-ui/app/scripts/view-models/modals/message-modal/message-client-modal/client-message-modal-view.html
new file mode 100644
index 0000000000..cfb0a35f69
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/modals/message-modal/message-client-modal/client-message-modal-view.html
@@ -0,0 +1,16 @@
+<sdc-modal modal="modalInstanceError"
+ type="classic"
+ class="w-sdc-modal modal-type-alert"
+ header="{{messageModalModel.title}}"
+ buttons="footerButtons"
+ show-close-button="true">
+
+ <perfect-scrollbar include-padding="true">
+ <div class="w-sdc-modal-icon w-sdc-modal-icon-{{messageModalModel.severity}}"></div>
+ <div class="w-sdc-modal-caption">
+ <div ng-bind-html="messageModalModel.message" data-tests-id="message"></div>
+ </div>
+ <!--<div class="w-sdc-modal-body-content" data-ng-bind-html="messageModalModel.message"></div>-->
+ </perfect-scrollbar>
+
+</sdc-modal>
diff --git a/catalog-ui/app/scripts/view-models/modals/message-modal/message-client-modal/client-message-modal.less b/catalog-ui/app/scripts/view-models/modals/message-modal/message-client-modal/client-message-modal.less
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/modals/message-modal/message-client-modal/client-message-modal.less
diff --git a/catalog-ui/app/scripts/view-models/modals/message-modal/message-server-modal/server-message-modal-view-model.ts b/catalog-ui/app/scripts/view-models/modals/message-modal/message-server-modal/server-message-modal-view-model.ts
new file mode 100644
index 0000000000..f7a0dcfabf
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/modals/message-modal/message-server-modal/server-message-modal-view-model.ts
@@ -0,0 +1,45 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../../../references"/>
+module Sdc.ViewModels {
+ 'use strict';
+
+ export interface IServerMessageModalModel extends IMessageModalModel {
+ status: string;
+ messageId: string;
+ }
+
+ export interface IServerMessageModalViewModelScope extends IMessageModalViewModelScope {
+ serverMessageModalModel: IServerMessageModalModel;
+ }
+
+ export class ServerMessageModalViewModel extends MessageModalViewModel {
+
+ static '$inject' = ['$scope', '$modalInstance', 'serverMessageModalModel'];
+
+ constructor(private $scope:IServerMessageModalViewModelScope,
+ private $modalInstance:ng.ui.bootstrap.IModalServiceInstance,
+ private serverMessageModalModel:IServerMessageModalModel) {
+
+ super($scope, $modalInstance, serverMessageModalModel);
+ }
+
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/modals/message-modal/message-server-modal/server-message-modal-view.html b/catalog-ui/app/scripts/view-models/modals/message-modal/message-server-modal/server-message-modal-view.html
new file mode 100644
index 0000000000..294dc76c4c
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/modals/message-modal/message-server-modal/server-message-modal-view.html
@@ -0,0 +1,17 @@
+<sdc-modal modal="modalInstanceError"
+ type="classic"
+ class="w-sdc-modal modal-type-error"
+ header="{{messageModalModel.title}}"
+ buttons="footerButtons"
+ show-close-button="true">
+
+ <perfect-scrollbar include-padding="true">
+ <div class="w-sdc-modal-icon w-sdc-modal-icon-{{messageModalModel.severity}}"></div>
+ <div class="w-sdc-modal-caption">
+ <div>Error code: {{messageModalModel.messageId}}</div>
+ <div>Status code: {{messageModalModel.status}}</div>
+ </div>
+ <div class="w-sdc-modal-body-content" data-ng-bind-html="messageModalModel.message" data-tests-id="message"></div>
+ </perfect-scrollbar>
+
+</sdc-modal>
diff --git a/catalog-ui/app/scripts/view-models/modals/message-modal/message-server-modal/server-message-modal.less b/catalog-ui/app/scripts/view-models/modals/message-modal/message-server-modal/server-message-modal.less
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/modals/message-modal/message-server-modal/server-message-modal.less
diff --git a/catalog-ui/app/scripts/view-models/modals/onboarding-modal/onboarding-modal-view-model.ts b/catalog-ui/app/scripts/view-models/modals/onboarding-modal/onboarding-modal-view-model.ts
new file mode 100644
index 0000000000..a6e85c4abc
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/modals/onboarding-modal/onboarding-modal-view-model.ts
@@ -0,0 +1,249 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../../references"/>
+module Sdc.ViewModels {
+ 'use strict';
+ import Resource = Sdc.Models.Components.Resource;
+ import ComponentFactory = Sdc.Utils.ComponentFactory;
+
+ interface IOnboardingModalViewModelScope {
+ modalOnboarding: ng.ui.bootstrap.IModalServiceInstance;
+ componentsList: Array<Models.Components.IComponent>;
+ tableHeadersList: Array<any>;
+ selectedComponent: Models.Components.Component;
+ componentFromServer: Models.Components.Component;
+ reverse: boolean;
+ sortBy: string;
+ searchBind: string;
+ okButtonText: string;
+ isCsarComponentExists: boolean;
+ user: Models.IUser;
+ isLoading: boolean;
+
+ doSelectComponent(component: Models.Components.Component): void;
+ doUpdateCsar(): void;
+ doImportCsar(): void;
+ sort(sortBy: string): void;
+ downloadCsar(packageId: string): void;
+ }
+
+ export class OnboardingModalViewModel {
+
+ static '$inject' = [
+ '$scope',
+ '$filter',
+ '$state',
+ 'sdcConfig',
+ '$modalInstance',
+ 'Sdc.Services.OnboardingService',
+ 'okButtonText',
+ 'currentCsarUUID',
+ 'Sdc.Services.CacheService',
+ 'FileUtils',
+ 'ComponentFactory',
+ 'ModalsHandler'
+ ];
+
+ constructor(private $scope: IOnboardingModalViewModelScope,
+ private $filter: ng.IFilterService,
+ private $state: any,
+ private sdcConfig: Models.IAppConfigurtaion,
+ private $modalInstance: ng.ui.bootstrap.IModalServiceInstance,
+ private onBoardingService: Sdc.Services.OnboardingService,
+ private okButtonText: string,
+ private currentCsarUUID: string,
+ private cacheService: Services.CacheService,
+ private fileUtils: Sdc.Utils.FileUtils,
+ private componentFactory: Utils.ComponentFactory,
+ private modalsHandler: Sdc.Utils.ModalsHandler) {
+
+ this.init();
+ }
+
+ /**
+ * Called from controller constructor, this will call onboarding service to get list
+ * of "mini" components (empty components created from CSAR).
+ * The list is inserted to componentsList on $scope.
+ * And then call initScope method.
+ */
+ private init = (): void => {
+ this.initOnboardingComponentsList();
+ };
+
+ private initScope = (): void => {
+
+ this.initSortedTableScope();
+ this.initModalScope();
+ this.$scope.sortBy = "name"; // Default sort by
+ this.$scope.user = this.cacheService.get('user');
+ this.$scope.okButtonText = this.okButtonText;
+
+ // Dismiss the modal and pass the "mini" component to workspace general page
+ this.$scope.doImportCsar = (): void => {
+ this.$modalInstance.dismiss();
+ this.$state.go('workspace.general', {
+ type: Utils.Constants.ComponentType.RESOURCE.toLowerCase(),
+ componentCsar: this.$scope.selectedComponent
+ });
+ };
+
+ this.$scope.doUpdateCsar = (): void => {
+ // In case user select on update the checkin and submit for testing buttons (in general page) should be disabled.
+ // to do that we need to pass to workspace.general state parameter to know to disable the buttons.
+ this.$modalInstance.close();
+ // Change the component version to the CSAR version we want to update.
+ /*(<Resource>this.$scope.componentFromServer).csarVersion = (<Resource>this.$scope.selectedComponent).csarVersion;
+ let component:Models.Components.Component = this.componentFactory.createComponent(this.$scope.componentFromServer);
+ this.$state.go('workspace.general', {vspComponent: component, disableButtons: true });*/
+ this.cacheService.set(Utils.Constants.CHANGE_COMPONENT_CSAR_VERSION_FLAG, (<Resource>this.$scope.selectedComponent).csarVersion);
+ this.$state.go('workspace.general', {
+ id: this.$scope.componentFromServer.uniqueId,
+ type: this.$scope.componentFromServer.componentType.toLowerCase(),
+ disableButtons: true
+ });
+ };
+
+ this.$scope.downloadCsar = (packageId: string): void => {
+ this.onBoardingService.downloadOnboardingCsar(packageId).then(
+ (file: any): void => {
+ if (file) {
+ this.fileUtils.downloadFile(file, packageId + '.zip');
+ }
+ }, (): void => {
+ let data: Sdc.ViewModels.IServerMessageModalModel = {
+ title: 'Download error',
+ message: "Error downloading file",
+ severity: Utils.Constants.SEVERITY.ERROR,
+ messageId: "",
+ status: ""
+ };
+ this.modalsHandler.openServerMessageModal(data);
+ }
+ );
+ };
+
+ // When the user select a row, set the component as selectedComponent
+ this.$scope.doSelectComponent = (component: Models.Components.Component): void => {
+
+ if (this.$scope.selectedComponent === component) {
+ // Collapse the item
+ this.$scope.selectedComponent = undefined;
+ return;
+ }
+
+ this.$scope.isLoading = true;
+ this.$scope.componentFromServer = undefined;
+ this.$scope.selectedComponent = component;
+
+ let onSuccess = (componentFromServer: Models.Components.Component): void => {
+ this.$scope.isLoading = false;
+ if (componentFromServer) {
+ this.$scope.componentFromServer = componentFromServer;
+ this.$scope.isCsarComponentExists = true;
+ } else {
+ this.$scope.componentFromServer = component;
+ this.$scope.isCsarComponentExists = false;
+ }
+ };
+
+ let onError = (): void => {
+ this.$scope.isLoading = false;
+ this.$scope.componentFromServer = component;
+ this.$scope.isCsarComponentExists = false;
+ };
+
+ this.onBoardingService.getComponentFromCsarUuid((<Resource>component).csarUUID).then(onSuccess, onError);
+ };
+
+ };
+
+ private initSortedTableScope = (): void => {
+ this.$scope.tableHeadersList = [
+ {title: 'Name', property: 'name'},
+ {title: 'Vendor', property: 'vendorName'},
+ {title: 'Category', property: 'categories'},
+ {title: 'Version', property: 'csarVersion'},
+ {title: '#', property: 'importAndUpdate'}
+ //{title: 'Date', property: 'componentDate'}
+ ];
+
+ this.$scope.sort = (sortBy: string): void => {
+ this.$scope.reverse = (this.$scope.sortBy === sortBy) ? !this.$scope.reverse : false;
+ this.$scope.sortBy = sortBy;
+ };
+ };
+
+ private initModalScope = (): void => {
+ // Enable the modal directive to close
+ this.$scope.modalOnboarding = this.$modalInstance;
+ };
+
+ private initOnboardingComponentsList = (): void => {
+ let onSuccess = (onboardingResponse: Array<Models.Components.IComponent>): void => {
+ initMaxVersionOfItemsInList(onboardingResponse);
+
+ if (this.currentCsarUUID) {
+ //this.$scope.componentsList = this.$filter('filter')(this.$scope.componentsList, {csarUUID: this.currentCsarUUID});
+ this.$scope.componentsList = this.$filter('filter')(this.$scope.componentsList,
+ (input): boolean => {
+ return input.csarUUID === this.currentCsarUUID;
+ }
+ );
+ }
+ this.initScope();
+ };
+
+ let onError = (): void => {
+ console.log("Error getting onboarding list");
+ this.initScope();
+ };
+
+ let initMaxVersionOfItemsInList = (onboardingResponse: Array<Models.Components.IComponent>): void => {
+ // Get only the latest version of each item
+ this.$scope.componentsList = [];
+
+ // Get all unique items from the list
+ let uniqueItems = _.uniqBy(onboardingResponse, 'packageId');
+
+ // Loop on all the items with unique packageId
+ _.each(uniqueItems, (item: any): void => {
+ // Find all the items that has same packageId
+ let ItemsFound: Array<Models.Components.IComponent> = _.filter(onboardingResponse, (inListItem: any): any => {
+ return inListItem.packageId === item.packageId;
+ });
+
+ // Loop on all the items with same packageId and find the max version.
+ let maxItem: any;
+ _.each(ItemsFound, (ItemFound: any): void => {
+ if (!maxItem) {
+ maxItem = ItemFound;
+ } else if (maxItem && parseInt(maxItem.csarVersion) < parseInt(ItemFound.csarVersion)) {
+ maxItem = ItemFound;
+ }
+ });
+ this.$scope.componentsList.push(maxItem);
+ });
+ };
+
+ this.onBoardingService.getOnboardingComponents().then(onSuccess, onError);
+ };
+
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/modals/onboarding-modal/onboarding-modal-view.html b/catalog-ui/app/scripts/view-models/modals/onboarding-modal/onboarding-modal-view.html
new file mode 100644
index 0000000000..246915212c
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/modals/onboarding-modal/onboarding-modal-view.html
@@ -0,0 +1,142 @@
+<sdc-modal modal="modalOnboarding" class="w-sdc-modal-onboarding w-sdc-classic-top-line-modal" buttons="footerButtons" header="Import VF" show-close-button="true">
+ <info-tooltip class="general-info-button" info-message-translate="ON_BOARDING_GENERAL_INFO "></info-tooltip>
+ <div class="title-wrapper">
+ <div>
+ <p class="sub-title">Select one of the software product component below:</p>
+ </div>
+
+ <div class="top-search">
+ <input type="text"
+ class="search-text"
+ placeholder="Search"
+ data-ng-model="searchBind"
+ data-tests-id="onboarding-search"
+ ng-model-options="{ debounce: 500 }" />
+ <span class="w-sdc-search-icon magnification"></span>
+ </div>
+ </div>
+
+ <div class="table-container-flex">
+ <div class="table" data-ng-class="{'view-mode': isViewMode()}">
+
+ <!-- Table headers -->
+ <div class="head flex-container">
+ <div class="table-header head-row hand flex-item" ng-repeat="header in tableHeadersList track by $index" data-ng-click="sort(header.property)" data-tests-id="{{header.title}}">{{header.title}}
+ <span data-ng-show="sortBy === header.property" class="table-header-sort-arrow" data-ng-class="{'down': reverse, 'up':!reverse}"> </span>
+ </div>
+ </div>
+
+ <!-- Table body -->
+ <div class="body">
+ <perfect-scrollbar suppress-scroll-x="true" scroll-y-margin-offset="0" include-padding="true" class="scrollbar-container">
+
+ <!-- In case the component list is empty -->
+ <div data-ng-if="!componentsList || componentsList.length===0" class="no-row-text">
+ There are no software product component to display
+ </div>
+
+ <!-- Loop on components list -->
+ <div data-ng-repeat-start="component in componentsList | filter: searchBind | orderBy:sortBy:reverse track by $index"
+ class="flex-container data-row"
+ data-ng-class="{'selected': component === selectedComponent}"
+ data-ng-click="doSelectComponent(component);"
+ data-tests-id="csar-row"
+ >
+
+ <!-- Name -->
+ <div class="table-col-general flex-item" sdc-smart-tooltip>
+ <span class="sprite table-arrow" data-ng-class="{'opened': component === selectedComponent}" data-tests-id="{{component.name}}"></span>
+ {{component.name}}
+ </div>
+
+ <!-- Vendor -->
+ <div class="table-col-general flex-item" data-tests-id="{{component.vendorName}}" sdc-smart-tooltip>
+ {{component.vendorName}}
+ </div>
+
+ <!-- Category -->
+ <div class="table-col-general flex-item" sdc-smart-tooltip>
+ {{component.categories[0].name}}&nbsp;{{component.categories[0].subcategories[0].name}}
+ </div>
+
+ <!-- Version -->
+ <div class="table-col-general flex-item" sdc-smart-tooltip>
+ {{component.csarVersion}}
+ </div>
+
+ <!-- Import And Update -->
+ <div class="table-col-general flex-item" sdc-smart-tooltip></div>
+
+ </div>
+
+ <div data-ng-repeat-end="" data-ng-if="component===selectedComponent" class="item-opened">
+
+ <div class="item-opened-description">
+ <div class="item-opened-description-title">VSP Description:</div>
+ {{component.description}}
+ </div>
+
+ <div class="item-opened-metadata1">
+ <div data-ng-if="isCsarComponentExists===true">
+ <div class="item-opened-metadata-title">VF'S Meta Data:</div>
+ <div><span class="th">Name:</span> {{componentFromServer.name}}</div>
+ <div><span class="th">Lifecycle:</span> {{componentFromServer.lifecycleState}}</div>
+ <div><span class="th">Creator:</span> {{componentFromServer.creatorFullName}}</div>
+ </div>
+ </div>
+
+ <div class="item-opened-metadata2">
+ <div data-ng-if="isCsarComponentExists===true">
+ <div class="item-opened-metadata-title">&nbsp;</div>
+ <div><span class="th">UUID:</span> {{componentFromServer.uuid}}</div>
+ <div><span class="th">Version:</span> {{componentFromServer.version}}</div>
+ <div><span class="th">Modifier:</span> {{componentFromServer.lastUpdaterFullName}}</div>
+ <div data-ng-if="componentFromServer.lifecycleState==='NOT_CERTIFIED_CHECKOUT' && componentFromServer.lastUpdaterUserId !== user.userId">
+ <span class="note">Designers cannot update a VSP if the VF is checked out by another user.</span>
+ </div>
+ <div data-ng-if="componentFromServer.lifecycleState==='READY_FOR_CERTIFICATION'">
+ <span class="note">Designers cannot update a VSP if the VF is in Ready for testing state.</span>
+ </div>
+ </div>
+ </div>
+
+ <div class="item-opened-metadata3">
+ <info-tooltip class="info-button" info-message-translate="{{isCsarComponentExists?'ON_BOARDING_UPDATE_INFO':'ON_BOARDING_IMPORT_INFO'}}" direction="left"></info-tooltip>
+ </div>
+
+ <div class="item-opened-icon">
+ <span data-ng-if="isCsarComponentExists!==true"
+ class="sprite-new import-file-btn"
+ data-ng-click="doImportCsar()"
+ uib-tooltip="Import VSP"
+ tooltip-class="uib-custom-tooltip"
+ tooltip-placement="bottom"
+ data-tests-id="import-csar"></span>
+
+ <span data-ng-if="isCsarComponentExists===true"
+ class="sprite-new refresh-file-btn"
+ uib-tooltip="Update VSP"
+ tooltip-class="uib-custom-tooltip"
+ tooltip-placement="bottom"
+ data-ng-class="{'disabled': (componentFromServer.lifecycleState==='NOT_CERTIFIED_CHECKOUT' && componentFromServer.lastUpdaterUserId!==user.userId) || componentFromServer.lifecycleState==='READY_FOR_CERTIFICATION'}"
+ data-ng-click="doUpdateCsar()"
+ data-tests-id="update-csar"></span>
+
+ <span data-ng-click="downloadCsar(component.packageId)"
+ class="sprite-new download-file-btn hand"
+ uib-tooltip="Download VSP"
+ tooltip-class="uib-custom-tooltip"
+ tooltip-placement="bottom"
+ data-tests-id="download-csar"></span>
+ </div>
+ <loader data-display="isLoading" relative="true" size="small"></loader>
+
+ </div>
+
+ </perfect-scrollbar>
+ </div><!-- End table body -->
+ </div><!-- End table -->
+ </div><!-- End table-container-flex -->
+ <div class="w-sdc-modal-footer classic"></div>
+
+</sdc-modal>
diff --git a/catalog-ui/app/scripts/view-models/modals/onboarding-modal/onboarding-modal.less b/catalog-ui/app/scripts/view-models/modals/onboarding-modal/onboarding-modal.less
new file mode 100644
index 0000000000..c745a86888
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/modals/onboarding-modal/onboarding-modal.less
@@ -0,0 +1,148 @@
+.w-sdc-modal-onboarding {
+
+ width: 100%;
+ display: inline-block;
+
+ .general-info-button{
+ position: relative;
+ top: -40px;
+ left: 86px;
+ float: left;
+ }
+
+ .title-wrapper {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-end;
+
+ .sub-title {
+ .m_14_r;
+ float:left;
+ }
+ }
+
+ .w-sdc-classic-btn {
+ float: right;
+ margin-bottom: 10px;
+ }
+
+ .table{
+ height: 472px;
+ margin-bottom: 0;
+ }
+
+ .table-container-flex {
+ margin-top: 10px;
+
+ .table {
+ .body {
+ .data-row + div.item-opened {
+ word-wrap: break-word;
+ display: flex;
+ justify-content: space-between;
+ padding: 10px 0;
+
+ .item-opened-description-title,
+ .item-opened-metadata-title {
+ .m_14_m;
+ }
+
+ .item-opened-description,
+ .item-opened-metadata1,
+ .item-opened-metadata2,
+ .item-opened-metadata3 {
+ .th { .m_14_m; }
+ flex-basis: 0;
+ overflow: hidden;
+ padding: 5px 15px;
+ }
+
+ .item-opened-description,
+ .item-opened-metadata3 {
+ border-right: 1px solid @main_color_o;
+ }
+
+ .item-opened-metadata2 {
+ word-break: break-word;
+ .note {
+ color: @func_color_q;
+ }
+ }
+
+ .item-opened-icon {
+ flex-basis: 0;
+ overflow: hidden;
+ padding: 5px 15px;
+ align-self: center;
+ }
+
+ .item-opened-description {flex-grow: 25;}
+ .item-opened-metadata1 {flex-grow: 25;}
+ .item-opened-metadata2 {flex-grow: 30;}
+ .item-opened-metadata3 {
+ flex-grow: 10;
+ .info-button{
+ float: right;
+ }
+ }
+ .item-opened-icon {flex-grow: 10;}
+ }
+ }
+ }
+
+ .flex-item:nth-child(1) {
+ flex-grow: 25;
+ .hand;
+ span.table-arrow {
+ margin-right: 7px;
+ }
+ }
+
+ .flex-item:nth-child(2) {flex-grow: 25;}
+ .flex-item:nth-child(3) {flex-grow: 30;}
+ .flex-item:nth-child(4) {flex-grow: 10; text-align: center; }
+ .flex-item:nth-child(5) {flex-grow: 10; }
+
+ }
+
+ .download-file-btn {
+ cursor: pointer;
+ margin-left: 4px;
+ }
+
+ .refresh-file-btn,
+ .import-file-btn {
+ cursor: pointer;
+ margin-left: 20px;
+ }
+
+ .top-search {
+ float: right;
+ position: relative;
+
+ input.search-text {
+ .border-radius(2px);
+ width: 245px;
+ height: 32px;
+ line-height: 32px;
+ border: 1px solid @main_color_o;
+ margin: 0;
+ outline: none;
+ text-indent: 10px;
+
+ &::-webkit-input-placeholder { font-style: italic; } /* Safari, Chrome and Opera */
+ &:-moz-placeholder { font-style: italic; } /* Firefox 18- */
+ &::-moz-placeholder { font-style: italic; } /* Firefox 19+ */
+ &:-ms-input-placeholder { font-style: italic; } /* IE 10+ */
+ &:-ms-input-placeholder { font-style: italic; } /* Edge */
+ }
+
+ .magnification {
+ position: absolute;
+ top: 10px;
+ right: 10px;
+ }
+
+ }
+
+}
diff --git a/catalog-ui/app/scripts/view-models/onboard-vendor/onboard-vendor-view-model.ts b/catalog-ui/app/scripts/view-models/onboard-vendor/onboard-vendor-view-model.ts
new file mode 100644
index 0000000000..c8be2b7361
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/onboard-vendor/onboard-vendor-view-model.ts
@@ -0,0 +1,148 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../references.ts"/>
+module Sdc.ViewModels {
+ 'use strict';
+
+ export class BreadcrumbsMenuItem {
+ key: string;
+ displayText: string;
+ }
+
+ export class BreadcrumbsMenu {
+ selectedKey: string;
+ menuItems: Array<BreadcrumbsMenuItem>;
+ }
+
+ export class BreadcrumbsPath {
+ selectedKeys: Array<string>;
+ }
+
+ export class VendorData {
+ breadcrumbs: BreadcrumbsPath;
+ }
+
+ export interface IOnboardVendorViewModelScope extends ng.IScope {
+ vendorData: VendorData;
+ onVendorEvent: Function;
+ topNavMenuModel: Array<Utils.MenuItemGroup>;
+ topNavRootMenu: Utils.MenuItemGroup;
+ user:Models.IUserProperties;
+ version:string;
+ }
+
+ export class OnboardVendorViewModel {
+ static '$inject' = [
+ '$scope',
+ '$q',
+ 'Sdc.Services.CacheService'
+ ];
+
+ private firstControlledTopNavMenu: Utils.MenuItemGroup;
+
+ constructor(
+ private $scope: IOnboardVendorViewModelScope,
+ private $q: ng.IQService,
+ private cacheService:Services.CacheService
+ ) {
+
+ this.$scope.vendorData = {
+ breadcrumbs: {
+ selectedKeys: []
+ }
+ };
+
+ this.$scope.version = this.cacheService.get('version');
+
+ this.$scope.onVendorEvent = (eventName:string, data:any): void => {
+ switch (eventName) {
+ case 'breadcrumbsupdated':
+ this.handleBreadcrumbsUpdate(data);
+ break;
+ }
+ };
+
+ this.$scope.topNavMenuModel = [];
+
+ this.$scope.user = this.cacheService.get('user');
+ }
+
+ updateBreadcrumbsPath = (selectedKeys: Array<string>): ng.IPromise<boolean> => {
+ let topNavMenuModel = this.$scope.topNavMenuModel;
+ let startIndex = topNavMenuModel.indexOf(this.firstControlledTopNavMenu);
+ if (startIndex === -1) {
+ startIndex = topNavMenuModel.length;
+ }
+ topNavMenuModel.splice(startIndex + selectedKeys.length);
+ this.$scope.vendorData = {
+ breadcrumbs: { selectedKeys: selectedKeys }
+ };
+
+ return this.$q.when(true);
+ };
+
+ handleBreadcrumbsUpdate(breadcrumbsMenus: Array<BreadcrumbsMenu>): void {
+ let selectedKeys = [];
+ let topNavMenus = breadcrumbsMenus.map((breadcrumbMenu, breadcrumbIndex) => {
+ let topNavMenu = new Utils.MenuItemGroup();
+ topNavMenu.menuItems = breadcrumbMenu.menuItems.map(menuItem =>
+ new Utils.MenuItem(
+ menuItem.displayText,
+ this.updateBreadcrumbsPath,
+ null,
+ null,
+ [selectedKeys.concat([menuItem.key])]
+ )
+ );
+ topNavMenu.selectedIndex = _.findIndex(
+ breadcrumbMenu.menuItems,
+ menuItem => menuItem.key === breadcrumbMenu.selectedKey
+ );
+ selectedKeys.push(breadcrumbMenu.selectedKey);
+ return topNavMenu;
+ });
+
+ let topNavMenuModel = this.$scope.topNavMenuModel;
+ let len = topNavMenuModel.length;
+ let startIndex = topNavMenuModel.indexOf(this.firstControlledTopNavMenu);
+ if (startIndex === -1) {
+ startIndex = len;
+ }
+ topNavMenuModel.splice(startIndex, len - startIndex);
+ topNavMenuModel.push.apply(topNavMenuModel, topNavMenus);
+ this.firstControlledTopNavMenu = topNavMenus[0];
+
+ if (startIndex === 1 && this.$scope.topNavRootMenu == null) {
+ let topNavRootMenu = topNavMenuModel[0];
+ let onboardItem = topNavRootMenu.menuItems[topNavRootMenu.selectedIndex];
+ let originalCallback = onboardItem.callback;
+ onboardItem.callback = (...args) => {
+ let ret = this.updateBreadcrumbsPath([]);
+ return originalCallback && originalCallback.apply(undefined, args) || ret;
+ };
+ this.$scope.topNavRootMenu = topNavRootMenu;
+ }
+
+ this.updateBreadcrumbsPath(selectedKeys);
+ }
+ }
+
+
+}
diff --git a/catalog-ui/app/scripts/view-models/onboard-vendor/onboard-vendor-view.html b/catalog-ui/app/scripts/view-models/onboard-vendor/onboard-vendor-view.html
new file mode 100644
index 0000000000..733e2d0cc0
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/onboard-vendor/onboard-vendor-view.html
@@ -0,0 +1,14 @@
+<div class="sdc-catalog-container">
+
+ <loader data-display="gui.isLoading"></loader>
+<!--
+ <ecomp-header menu-data="menuItems" version="{{version}}"></ecomp-header>
+-->
+
+ <div class="w-sdc-main-container">
+ <punch-out name="'onboarding/vendor'" data="vendorData" user="user" on-event="onVendorEvent"></punch-out>
+ </div>
+
+ <top-nav top-lvl-selected-index="2" search-bind="search.filterTerm" menu-model="topNavMenuModel" version="{{version}}" hide-search="true"></top-nav>
+
+</div>
diff --git a/catalog-ui/app/scripts/view-models/onboard-vendor/onboard-vendor.less b/catalog-ui/app/scripts/view-models/onboard-vendor/onboard-vendor.less
new file mode 100644
index 0000000000..2b43bbb321
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/onboard-vendor/onboard-vendor.less
@@ -0,0 +1,303 @@
+.sdc-catalog-container {
+
+ .i-sdc-categories-list-item {
+ font-weight: normal;
+ }
+
+ // Checkboxes
+ .i-sdc-designer-leftbar-section-content-ul {
+ padding: 0;
+ margin: 0;
+
+ .i-sdc-catalog-subcategories-checkbox {
+ padding: 0 0 0 20px;
+ margin: 0;
+
+ .i-sdc-catalog-grouping-checkbox {
+ padding: 0 0 0 20px;
+ margin: 0;
+ }
+
+ }
+
+ }
+
+ .i-sdc-designer-leftbar-section-content-li {
+ &:last-child {
+ .i-sdc-categories-list-item {
+ margin: 0;
+ }
+ }
+ }
+
+ .i-sdc-categories-list-item {
+ display: block;
+ //margin-bottom: 5px;
+ //padding-left: 15px;
+ //text-indent: -24px;
+ vertical-align: top;
+ font-weight: bold;
+ }
+
+ .i-sdc-subcategories-list-item {
+ display: block;
+ //padding-left: 20px;
+ vertical-align: top;
+ font-weight: normal;
+ margin: 0;
+ //text-indent: -10px;
+ }
+
+ /*Added by - Ikram */
+ .i-sdc-product-input,
+ .i-sdc-product-select {
+ border: 1px solid @border_color_f;
+ min-height: 30px;
+ padding: 0;
+ width: 100%;
+ margin: 1px 0;
+ background-color: #F2F2F2;
+ outline: none;
+
+ &:disabled {
+ .disabled;
+ }
+ optgroup{
+ color: @color_u;
+ option{
+ color: @color_b;
+ }
+ }
+ }
+
+ .i-sdc-categories-list-item-icon {
+ display: inline-block;
+ float: right;
+ position: relative;
+ right: -8px;
+ top: 6px;
+ }
+
+ .i-sdc-categories-list-item {
+ margin-top: 7px;
+ &.NOT_CERTIFIED_CHECKOUT,
+ &.NOT_CERTIFIED_CHECKIN {
+ .i-sdc-categories-list-item-icon {
+ background: url('../../../styles/images/sprites/sprite-global-old.png') no-repeat -53px -2889px;
+ width: 14px;
+ height: 14px;
+
+ }
+ }
+
+ &.CERTIFIED {
+ .i-sdc-categories-list-item-icon {
+ background: url('../../../styles/images/sprites/sprite-global-old.png') no-repeat -53px -3034px;
+ width: 14px;
+ height: 16px;
+ }
+ }
+
+ &.READY_FOR_CERTIFICATION {
+ .i-sdc-categories-list-item-icon {
+ background: url('../../../styles/images/sprites/sprite-global-old.png') no-repeat -53px -2985px;
+ width: 14px;
+ height: 16px;
+ }
+ }
+
+ &.CERTIFICATION_IN_PROGRESS {
+ .i-sdc-categories-list-item-icon {
+ background: url('../../../styles/images/sprites/sprite-global-old.png') no-repeat -53px -2934px;
+ width: 14px;
+ height: 16px;
+ }
+ }
+
+ &.DISTRIBUTED,
+ &.TBD {
+ .i-sdc-categories-list-item-icon {
+ background: url('../../../styles/images/sprites/sprite-global-old.png') no-repeat -43px -3087px;
+ width: 24px;
+ height: 14px;
+
+ }
+ }
+ }
+
+ .i-sdc-categories-list-input {
+ margin: 8px;
+
+ }
+
+ .i-sdc-subcategories-list-input {
+
+ margin: 8px;
+ }
+ .i-sdc-subcategories-list-input-container {
+ margin: 0px 0px 0px 20px;
+ padding: 2px;
+ }
+
+ .w-sdc-header-catalog-search-container {
+ display: table;
+ padding: 21px 0;
+ position: relative;
+
+ .w-sdc-designer-leftbar-search-input {
+ color: #000;
+ width: 300px;
+ }
+
+ // .magnification {
+ // .sprite;
+ // .sprite.magnification-glass;
+ // .hand;
+ // position: absolute;
+ // top: 40px;
+ // right: 42px;
+ // }
+ }
+
+ .w-sdc-catalog-main {
+ padding: 10px 12px;
+ }
+ .w-sdc-dashboard-catalog-header {
+ .b_9;
+ display: inline-block;
+ font-style: italic;
+ font-weight: bold;
+ padding-left: 10px;
+ }
+
+ .w-sdc-dashboard-catalog-header-order {
+ .b_9;
+ font-weight: 800;
+ }
+
+ .w-sdc-dashboard-catalog-sort {
+ .b_9;
+ font-weight: bold;
+ white-space:pre;
+ &:hover{
+ .hand;
+ text-decoration: none;
+ .a_9;
+ }
+ &.blue {
+ .a_9;
+ }
+ }
+
+ .w-sdc-catalog-sort-arrow{
+ display: inline-block;
+ &.up{
+ .b_9;
+ width: 0;
+ height: 0;
+ border-left: 5px solid transparent;
+ border-right: 5px solid transparent;
+ border-bottom: 5px solid ;
+ }
+ &.down{
+ .b_9;
+ width: 0;
+ height: 0;
+ border-left: 5px solid transparent;
+ border-right: 5px solid transparent;
+ border-top: 5px solid;
+ }
+ }
+
+
+
+
+ .w-sdc-dashboard-catalog-header-right{
+ float: right;
+ display: inline-block;
+ padding-right:34px;
+ }
+
+ .w-sdc-header-catalog-search-input {
+ width: 420px;
+ display: table-cell;
+ padding: 0 25px 1px 10px;
+ border: 1px solid #bcbcbc;
+ .border-radius(10px);
+ height: 30px;
+ margin: 10px 30px;
+ outline: none;
+ }
+
+ .sdc-catalog-type-filter-container {
+ margin-top: -1px;
+ }
+
+ .i-sdc-designer-leftbar-section-title {
+ text-transform: uppercase;
+ .l_14_m;
+ line-height: 30px;
+ }
+
+ .i-sdc-designer-leftbar-section-title-icon {
+ .hand;
+ .tlv-sprite;
+ .footer-close;
+ transition: .3s all;
+ margin-top: -4px;
+ }
+
+ .i-sdc-designer-leftbar-section-title-text {
+ margin-left: 20px;
+ }
+
+ .seperator-left,
+ .seperator-right {
+ border-right: solid 1px @color_m;
+ display: table-cell;
+ width: 2px;
+ }
+
+ // Rotate catalog left side arrows
+ .i-sdc-designer-leftbar-section-title.expanded .i-sdc-designer-leftbar-section-title-icon {
+ transform: rotate(180deg);
+ }
+
+ // Transform catalog left side sections
+ .i-sdc-designer-leftbar-section-title + .i-sdc-designer-leftbar-section-content {
+ max-height: 0px;
+ margin: 0 auto;
+ transition: all .3s;
+ overflow: hidden;
+ padding: 0 10px 0 18px;
+ }
+
+ .i-sdc-designer-leftbar-section-title.expanded + .i-sdc-designer-leftbar-section-content {
+ max-height: 9999px;
+ margin: 0 auto 1px;
+ transition: all .3s;
+ padding: 10px 18px 10px 18px;
+ overflow: hidden;
+ }
+
+}
+
+.w-sdc-search-icon{
+ position: absolute;
+ right: 40px;
+ top: 40px;
+ &.leftbar{
+ top: 19px;
+ right: 18px;
+ }
+ &.magnification {
+ .sprite;
+ .sprite.magnification-glass;
+ .hand;
+ }
+ &.cancel {
+ .sprite;
+ .sprite.clear-text;
+ .hand;
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/preloading/preloading-view.html b/catalog-ui/app/scripts/view-models/preloading/preloading-view.html
new file mode 100644
index 0000000000..c0512dd9ec
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/preloading/preloading-view.html
@@ -0,0 +1,9 @@
+<div class="sdc-loading-page">
+ <h1 class="caption1" translate="SIGN_IN_CAPTION"></h1>
+ <p class="caption2" translate="SIGN_IN_DESCRIPTION"></p>
+
+ <div class="load-container-wrapper">
+ <div class="load-container load2 animated fadeIn"><div class="loader">Loading...</div></div>
+ </div>
+
+</div>
diff --git a/catalog-ui/app/scripts/view-models/preloading/preloading-view.less b/catalog-ui/app/scripts/view-models/preloading/preloading-view.less
new file mode 100644
index 0000000000..b02ea54621
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/preloading/preloading-view.less
@@ -0,0 +1,107 @@
+/*
+.sdc-loading-page {
+
+ background-color: @main_color_l;
+ width: 100%;
+ height: 100%;
+
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+
+ h1 {
+ .c_5;
+ text-align: center;
+ }
+
+ p {
+ display: block;
+ .e_4;
+ }
+
+ .caption1, .caption2 {
+ visibility: hidden;
+ }
+
+ .load-container-wrapper {
+ position: relative;
+
+ .load-container {
+
+ @background_color: #000000;
+
+ .loader,
+ .loader:before,
+ .loader:after {
+ border-radius: 50%;
+ }
+ .loader:before,
+ .loader:after {
+ position: absolute;
+ content: '';
+ }
+ .loader:before {
+ width: 5.2em;
+ height: 10.2em;
+ background: @background_color;
+ border-radius: 10.2em 0 0 10.2em;
+ top: -0.1em;
+ left: -0.1em;
+ -webkit-transform-origin: 5.2em 5.1em;
+ transform-origin: 5.2em 5.1em;
+ -webkit-animation: load2 2s infinite ease 1.5s;
+ animation: load2 2s infinite ease 1.5s;
+ }
+ .loader {
+ color: #ffffff;
+ font-size: 11px;
+ text-indent: -99999em;
+ margin: 0 auto;
+ /!*margin: 55px auto;*!/
+ position: relative;
+ width: 10em;
+ height: 10em;
+ box-shadow: inset 0 0 0 1em;
+ -webkit-transform: translateZ(0);
+ -ms-transform: translateZ(0);
+ transform: translateZ(0);
+ }
+ .loader:after {
+ width: 5.2em;
+ height: 10.2em;
+ background: @background_color;
+ border-radius: 0 10.2em 10.2em 0;
+ top: -0.1em;
+ left: 5.1em;
+ -webkit-transform-origin: 0px 5.1em;
+ transform-origin: 0px 5.1em;
+ -webkit-animation: load2 2s infinite ease;
+ animation: load2 2s infinite ease;
+ }
+ @-webkit-keyframes load2 {
+ 0% {
+ -webkit-transform: rotate(0deg);
+ transform: rotate(0deg);
+ }
+ 100% {
+ -webkit-transform: rotate(360deg);
+ transform: rotate(360deg);
+ }
+ }
+ @keyframes load2 {
+ 0% {
+ -webkit-transform: rotate(0deg);
+ transform: rotate(0deg);
+ }
+ 100% {
+ -webkit-transform: rotate(360deg);
+ transform: rotate(360deg);
+ }
+ }
+ }
+
+ }
+
+}
+*/
diff --git a/catalog-ui/app/scripts/view-models/preloading/preloading-view.ts b/catalog-ui/app/scripts/view-models/preloading/preloading-view.ts
new file mode 100644
index 0000000000..7127b70e3c
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/preloading/preloading-view.ts
@@ -0,0 +1,47 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+module Sdc.ViewModels {
+ 'use strict';
+
+ interface IPreLoadingViewScope {
+ startZoomIn: boolean;
+ }
+
+ export class PreLoadingViewModel {
+
+ static '$inject' = ['$scope'];
+ constructor(private $scope:IPreLoadingViewScope){
+ this.init($scope);
+ }
+
+ private init = ($scope:IPreLoadingViewScope):void => {
+ this.animate($('.caption1'),'fadeInUp',400);
+ this.animate($('.caption2'),'fadeInUp',800);
+ };
+
+ private animate = (element:any, animation:string, when:number):void => {
+ window.setTimeout(()=>{
+ element.addClass("animated " + animation);
+ element[0].style="visibility: visible;";
+ },when);
+ };
+
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/support/support-view-model.ts b/catalog-ui/app/scripts/view-models/support/support-view-model.ts
new file mode 100644
index 0000000000..2142cffdda
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/support/support-view-model.ts
@@ -0,0 +1,38 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../references"/>
+
+module Sdc.ViewModels {
+ 'use strict';
+
+ interface ISupportViewModelScope {
+ version:string;
+ }
+
+ export class SupportViewModel{
+
+ static '$inject' = ['$scope','Sdc.Services.CacheService'];
+ constructor(private $scope:ISupportViewModelScope,
+ private cacheService:Services.CacheService){
+ this.$scope.version = this.cacheService.get('version');
+ }
+
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/support/support-view.html b/catalog-ui/app/scripts/view-models/support/support-view.html
new file mode 100644
index 0000000000..0e6d09ddd7
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/support/support-view.html
@@ -0,0 +1,33 @@
+<div class="full-height" >
+ <loader data-display="isLoading"></loader>
+ <div class="w-sdc-header">
+ <div class="w-sdc-header-logo">
+ <div class="w-sdc-header-logo-icon sprite logo"></div>
+ <a class="w-sdc-header-logo-link" data-ui-sref="dashboard">ASDC</a>
+ <div class="w-sdc-header-version"> v.{{version}}</div>
+ </div>
+ <div class="i-sdc-header-caption">Support</div>
+ <user-header-details ></user-header-details>
+ </div>
+ <div class="w-sdc-main-container">
+ <div class="w-sdc-left-sidebar">
+ <div class="w-sdc-left-sidebar-in-progress" >
+ <div class="i-sdc-left-sidebar-item category-title" data-ng-class="{'selectedLink':selectedLeftBarGroupLink === inProgressEnumVal}" data-ng-click="setSelectedEntitiesByEntityGroup(inProgressEnumVal)" >In Design ({{numOfCheckOutEntities+numOfCheckInEntities}})</div>
+ <div class="i-sdc-left-sidebar-item" data-ng-class="{'selectedLink':selectedLeftBarStateLink===notCertifiedCheckOutEnumVal}" data-ng-click="setSelectedEntitiesByEntityGroupAndEntityState(inProgressEnumVal, notCertifiedCheckOutEnumVal)" >Checked Out ({{numOfCheckOutEntities}})</div>
+ <div class="i-sdc-left-sidebar-item" data-ng-class="{'selectedLink':selectedLeftBarStateLink===notCertifiedCheckInEnumVal}" data-ng-click="setSelectedEntitiesByEntityGroupAndEntityState(inProgressEnumVal, notCertifiedCheckInEnumVal)" >Checked In ({{numOfCheckInEntities}})</div>
+ </div>
+ <div class="w-sdc-left-sidebar-following" >
+ <div class="i-sdc-left-sidebar-item category-title" data-ng-class="{'selectedLink':selectedLeftBarGroupLink===followingEnumVal}" data-ng-click="setSelectedEntitiesByEntityGroup(followingEnumVal)" >Completed Design ({{numOfReadyForCertificationEntities+numOfCertificationInProgressEntities+numOfCertifiedEntities}})</div>
+ <div class="i-sdc-left-sidebar-item" data-ng-class="{'selectedLink':selectedLeftBarStateLink===readyForCertificationEnumVal}" data-ng-click="setSelectedEntitiesByEntityGroupAndEntityState(followingEnumVal, readyForCertificationEnumVal)" >Ready For Certification ({{numOfReadyForCertificationEntities}})</div>
+ <div class="i-sdc-left-sidebar-item" data-ng-class="{'selectedLink':selectedLeftBarStateLink===certificationInProgressEnumVal}" data-ng-click="setSelectedEntitiesByEntityGroupAndEntityState(followingEnumVal, certificationInProgressEnumVal)" >Certification In Progress ({{numOfCertificationInProgressEntities}})</div>
+ <div class="i-sdc-left-sidebar-item" data-ng-class="{'selectedLink':selectedLeftBarStateLink===certifiedEnumVal}" data-ng-click="setSelectedEntitiesByEntityGroupAndEntityState(followingEnumVal, certifiedEnumVal)" >Certified ({{numOfCertifiedEntities}})</div>
+ </div>
+ <div class="w-sdc-left-sidebar-nav">
+ <div class="i-sdc-left-sidebar-nav-item catalog" data-ui-sref="catalog">Catalog</div>
+ <div class="i-sdc-left-sidebar-nav-item support" data-ui-sref="support">Support</div>
+ </div>
+ </div>
+
+
+ </div>
+</div>
diff --git a/catalog-ui/app/scripts/view-models/support/support.less b/catalog-ui/app/scripts/view-models/support/support.less
new file mode 100644
index 0000000000..8159e38320
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/support/support.less
@@ -0,0 +1,8 @@
+.w-sdc-left-sidebar-in-progress,
+.w-sdc-left-sidebar-following {
+ .b_7;
+}
+
+.w-sdc-left-sidebar-following {
+ padding: 13px 0;
+}
diff --git a/catalog-ui/app/scripts/view-models/tabs/general-tab.less b/catalog-ui/app/scripts/view-models/tabs/general-tab.less
new file mode 100644
index 0000000000..a8b4f5b9be
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/tabs/general-tab.less
@@ -0,0 +1,131 @@
+.sdc-general-tab {
+
+ display: flex;
+ min-height: 100%;
+ flex-flow: column;
+
+ .sdc-edit-icon {
+ .sprite;
+ .e-sdc-small-icon-pencil;
+ }
+ .sdc-general-tab-title {
+
+ .f-color.a;
+ .f-type._14_m;
+ padding: 0px 0px 15px 0px;
+ margin: 0px 20px 0px 20px;
+ border-bottom: 1px solid @main_color_o;
+ }
+
+ .sdc-general-tab-sub-title {
+
+ .f-color.a;
+ .f-type._14_m;
+ padding: 15px 20px 15px 20px;
+
+ }
+
+ //scrollbar
+ .general-tab-scrollbar-container {
+
+ .perfect-scrollbar;
+ width: 100%;
+ }
+
+ //plus minus expand collapse
+ .general-tab-expand-collapse {
+
+ &.expanded {
+ .expand-collapse-title {
+ .expand-collapse-title-icon {
+ .expand-collapse-minus-icon;
+
+ &:hover {
+ .expand-collapse-minus-icon.hover;
+ }
+ }
+ }
+ }
+
+ .expand-collapse-title {
+
+ padding: 8px 20px 4px 20px;
+ cursor: pointer;
+ &:hover {
+ background-color: @main_color_o;
+ }
+
+ .expand-collapse-title-icon {
+ .hand;
+ .sprite-new;
+ .expand-collapse-plus-icon;
+ &:hover {
+ .expand-collapse-plus-icon.hover;
+ }
+
+ }
+ .expand-collapse-title-text {
+ max-width: 225px;
+ display: inline-block;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ padding-left: 10px;
+ line-height: 15px;
+ }
+ }
+ .selected {
+ background-color: @main_color_a;
+ .f-color.p;
+ }
+
+ }
+
+ .expand-collapse-sub-title {
+ max-width: 190px;
+ display: inline-block;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ padding-left: 43px;
+
+ }
+
+ //resizable view
+ .resizable-container {
+
+ flex: 1 1 auto;
+ display: flex;
+ flex-direction: column;
+ height: 90%;
+
+ .resizable-section {
+ min-height: 50px;
+ flex: 1;
+ display: flex;
+ flex-flow: column;
+ &.resizable {
+ flex: 0 0 300px;
+ }
+ }
+
+ //this is the resizable icon custom design for the angular resizable directive
+ .rg-top {
+ span {
+ margin-top: -5px;
+ &:before {
+ border-top: 1px dotted @main_color_m;
+ content: '';
+ display: inline-block;
+ width: 39px;
+ height: 6px;
+ }
+
+ border-top: 1px dotted @main_color_m;
+ border-bottom: 1px dotted @main_color_m;
+ width: 39px;
+ height: 4px;
+ }
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/tabs/hierarchy/hierarchy-view-model.ts b/catalog-ui/app/scripts/view-models/tabs/hierarchy/hierarchy-view-model.ts
new file mode 100644
index 0000000000..bd59199eb4
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/tabs/hierarchy/hierarchy-view-model.ts
@@ -0,0 +1,99 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/**
+ * Created by obarda on 7/28/2016.
+ */
+/**
+ * Created by obarda on 4/4/2016.
+ */
+/// <reference path="../../../references"/>
+module Sdc.ViewModels {
+ 'use strict';
+ import Module = Sdc.Models.Module;
+
+ export interface IHierarchyScope extends ng.IScope {
+ component:Models.Components.Component;
+ selectedIndex: number;
+ selectedModule:Models.DisplayModule;
+ singleTab:Models.Tab;
+ templateUrl:string;
+ isLoading:boolean;
+
+ onModuleSelected(moduleId:string, selectedIndex: number):void;
+ onModuleNameChanged(module:Models.DisplayModule):void;
+ updateHeatName():void;
+ }
+
+ export class HierarchyViewModel {
+
+ static '$inject' = [
+ '$scope'
+ ];
+
+ constructor(private $scope:IHierarchyScope) {
+ this.$scope.component = this.$scope.singleTab.data;
+ this.$scope.isLoading = false;
+ this.initScopeMethods();
+ }
+
+ private initScopeMethods():void {
+
+ this.$scope.templateUrl = '/app/scripts/view-models/tabs/hierarchy/edit-module-name-popover.html';
+ this.$scope.onModuleSelected = (moduleId:string, selectedIndex: number):void => {
+
+ let onSuccess = (module:Models.DisplayModule) => {
+ console.log("Module Loaded: ", module);
+ this.$scope.selectedModule = module;
+ this.$scope.isLoading = false;
+ };
+
+ let onFailed = () => {
+ this.$scope.isLoading = false;
+ };
+
+ this.$scope.selectedIndex = selectedIndex;
+ if( !this.$scope.selectedModule || (this.$scope.selectedModule && this.$scope.selectedModule.uniqueId != moduleId)) {
+ this.$scope.isLoading = true;
+ this.$scope.component.getModuleForDisplay(moduleId).then(onSuccess, onFailed);
+ }
+ };
+
+ this.$scope.updateHeatName = () => {
+ this.$scope.isLoading = true;
+
+ let originalName:string = this.$scope.selectedModule.name;
+
+ let onSuccess = (module:Models.Module) => {
+ console.log("Module name updated:", module.name);
+ this.$scope.selectedModule.name = module.name;
+ this.$scope.isLoading = false;
+ };
+
+ let onFailed = () => {
+ this.$scope.isLoading = false;
+ this.$scope.selectedModule.name = originalName;
+ };
+
+ this.$scope.selectedModule.updateName();
+ this.$scope.component.updateGroupMetadata(new Models.DisplayModule(this.$scope.selectedModule)).then(onSuccess, onFailed);
+ };
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/tabs/hierarchy/hierarchy-view.html b/catalog-ui/app/scripts/view-models/tabs/hierarchy/hierarchy-view.html
new file mode 100644
index 0000000000..971105c191
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/tabs/hierarchy/hierarchy-view.html
@@ -0,0 +1,57 @@
+<div class="sdc-general-tab hierarchy-tab" ng-class="">
+ <loader data-display="isLoading" relative="true" size="medium"></loader>
+ <div class="sdc-general-tab-title" data-tests-id="tab-header" translate="HIERARCHY_TAB_TITLE"></div>
+ <div class="sdc-general-tab-sub-title" data-tests-id="tab-sub-header">{{component.name}}</div>
+
+ <div class="resizable-container">
+ <div class="resizable-section">
+ <perfect-scrollbar scroll-y-margin-offset="0" include-padding="true"
+ class="general-tab-scrollbar-container">
+
+ <expand-collapse expanded-selector=".hierarchy-module-member-list.{{$index}}"
+ class="general-tab-expand-collapse" is-close-on-init="true"
+ data-tests-id="hierarchy-module-{{$index}}"
+ data-ng-repeat-start="module in component.groups">
+ <div class="expand-collapse-title" data-tests-id="hierarchy-module-{{$index}}-title" ng-class="{'selected': selectedIndex === $index}" data-ng-click="onModuleSelected(module.uniqueId, $index)">
+ <div class="expand-collapse-title-icon"></div>
+ <span class="expand-collapse-title-text" data-ng-bind="module.name" tooltips
+ tooltip-content="{{module.name}}"></span>
+
+ </div>
+ </expand-collapse>
+
+ <div data-ng-repeat-end="" class="hierarchy-module-member-list {{$index}}">
+ <div ng-repeat="(memberName, value) in ::module.members track by $index">
+ <div class="expand-collapse-sub-title" tooltips tooltip-content="{{memberName}}">{{memberName}}</div>
+ </div>
+ </div>
+ </perfect-scrollbar>
+ </div>
+
+ <div resizable r-directions="['top']" r-flex="true" ng-if="selectedModule" class="resizable-section module-data-container" data-tests-id="selected-module-data">
+ <perfect-scrollbar scroll-y-margin-offset="0" include-padding="true"
+ class="general-tab-scrollbar-container">
+ <div class="module-data">
+ <div>
+ <div class="module-name module-text-overflow" data-tests-id="selected-module-name" tooltips tooltip-content="{{selectedModule.name}}">{{selectedModule.name}}</div>
+ <div class="edit-name-container">
+ <edit-name-popover header="Edit Module Name" direction="auto top" module="selectedModule" on-save="updateHeatName()" ng-class="{'disabled': isViewOnly}" class="sdc-edit-icon" data-tests-id="edit-name-popover-icon"></edit-name-popover>
+ </div>
+ </div>
+ <div class="module-text-overflow" data-tests-id="selected-module-group-uuid" tooltips tooltip-content="{{selectedModule.groupUUID}}"> Module ID: {{selectedModule.groupUUID}}</div>
+ <div class="module-text-overflow" data-tests-id="selected-module-is-base" tooltips tooltip-content="{{selectedModule.invariantUUID}}">Invariant UUID: {{selectedModule.invariantUUID}}</div>
+ <div data-tests-id="selected-module-version">Version: {{selectedModule.version}}</div>
+ <div data-tests-id="selected-module-is-base">IsBase: {{selectedModule.isBase}}</div>
+
+ </div>
+ <div ng-repeat="artifact in selectedModule.artifacts track by $index">
+ <div class="artifact-data">
+ <div class="artifact-name module-text-overflow" data-tests-id="selected-module-artifact-name" tooltips tooltip-content="{{artifact.artifactName}}">{{artifact.artifactName}}</div>
+ <div class="module-text-overflow" tooltips data-tests-id="selected-module-artifact-uuid" tooltip-content="{{artifact.artifactUUID}}"> UUID: {{artifact.artifactUUID}}</div>
+ <div data-tests-id="selected-module-artifact-version">Version: {{artifact.artifactVersion}}</div>
+ </div>
+ </div>
+ </perfect-scrollbar>
+ </div>
+ </div>
+</div>
diff --git a/catalog-ui/app/scripts/view-models/tabs/hierarchy/hierarchy.less b/catalog-ui/app/scripts/view-models/tabs/hierarchy/hierarchy.less
new file mode 100644
index 0000000000..5e8572678d
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/tabs/hierarchy/hierarchy.less
@@ -0,0 +1,71 @@
+.hierarchy-tab{
+
+ .hierarchy-module-list-container{
+ padding: 0px 20px 0px 20px;
+ }
+ .module-data-container{
+
+ width: 100%;
+ height: 100%;
+ background-color: #e6f6fb;
+ border: 1px solid #009fdb;
+ border-top: 4px solid #009fdb;
+ box-shadow: 0.3px 1px 2px rgba(24, 24, 25, 0.32);
+
+ .module-data {
+
+ .selectable;
+ .module-name {
+ .f-type._14_m;
+ width: 87%;
+ }
+ .f-type._14_r;
+ .f-color.a;
+ padding: 10px 0px 10px 0px;
+ margin: 0px 20px 0px 20px;
+ border-bottom: 1px solid rgba(0, 159, 219, 0.6);
+ }
+
+ .artifact-data{
+ .selectable;
+ .f-type._12_r;
+ .f-color.m;
+
+ padding: 10px 0px 10px 0px;
+ margin: 0px 20px 0px 20px;
+ border-bottom: 1px solid rgba(0, 159, 219, 0.6);
+ .artifact-name {
+ .f-type._14_r;
+ font-weight: bold;
+ }
+ }
+
+ .module-text-overflow {
+ max-width: 240px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ display: inline-block;
+ }
+ }
+
+
+ .hierarchy-module-member-list {
+ overflow: hidden;
+ }
+
+ .edit-name-container {
+ float: right;
+ border-left: 1px solid #5cc1e7;
+ height: 20px;
+ width: 12%;
+
+ .sdc-edit-icon {
+ float: right;
+ cursor: pointer;
+ position: relative;
+ top: 4px;
+ right: 5px;
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/tutorial-end/tutorial-end.html b/catalog-ui/app/scripts/view-models/tutorial-end/tutorial-end.html
new file mode 100644
index 0000000000..6e478fc471
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/tutorial-end/tutorial-end.html
@@ -0,0 +1,10 @@
+<div class="sdc-tutorial-end-page">
+ <perfect-scrollbar include-padding="true" class="sdc-tutorial-end-page-main">
+ <h2 translate="TUTORIAL_LAST_PAGE_TITLE"></h2>
+ <p class="sdc-tutorial-end-page-description1" translate="TUTORIAL_LAST_PAGE_TEXT"></p>
+ <div>
+ <button class="w-sdc-btn-blue" data-ui-sref="dashboard" type="button" translate="TUTORIAL_LAST_PAGE_BTN_ACTION_MY_DASHBOARD"></button>
+ <button class="w-sdc-btn-blue" data-ui-sref="catalog" type="button" translate="WELCOME_BTN_ACTION_CATALOG"></button>
+ </div>
+ </perfect-scrollbar>
+</div>
diff --git a/catalog-ui/app/scripts/view-models/tutorial-end/tutorial-end.less b/catalog-ui/app/scripts/view-models/tutorial-end/tutorial-end.less
new file mode 100644
index 0000000000..71648a4f86
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/tutorial-end/tutorial-end.less
@@ -0,0 +1,41 @@
+.sdc-tutorial-end-page {
+
+ .bg_s;
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: 999;
+ .opacity(0.8);
+ display: flex;
+ align-items: center;
+
+ background-image: url('../../../styles/images/welcome.png');
+ background-repeat: no-repeat;
+ background-position: bottom left;
+
+ .sdc-tutorial-end-page-main {
+ width: 600px;
+ height: 400px;
+ margin: 100px auto 100px auto;
+ padding: 80px;
+ }
+
+ h2 {
+ .t_15;
+ margin: 0;
+ }
+
+ .sdc-tutorial-end-page-description1 {
+ .c_2;
+ .opacity(0.8);
+ margin-top: 10px;
+ line-height: 22px;
+ }
+
+ .w-sdc-btn-blue {
+ margin: 40px 10px 0 0;
+ }
+
+}
diff --git a/catalog-ui/app/scripts/view-models/tutorial-end/tutorial-end.ts b/catalog-ui/app/scripts/view-models/tutorial-end/tutorial-end.ts
new file mode 100644
index 0000000000..411d3f8d24
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/tutorial-end/tutorial-end.ts
@@ -0,0 +1,42 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../references"/>
+module Sdc.ViewModels {
+ 'use strict';
+
+ interface ITutorialEndViewModelScope extends ng.IScope {}
+
+ export class TutorialEndViewModel {
+
+ static '$inject' = [
+ '$scope'
+ ];
+ constructor(
+ private $scope:ITutorialEndViewModelScope
+ ){
+ this.init();
+ }
+
+ private init = ():void => {
+
+ }
+
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/welcome/slide0.html b/catalog-ui/app/scripts/view-models/welcome/slide0.html
new file mode 100644
index 0000000000..48d37215a4
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/welcome/slide0.html
@@ -0,0 +1,50 @@
+<div class="slide" id="slide-0" data-ng-controller="Sdc.ViewModels.WelcomeStepsControllerViewModel">
+ <div class="asdc-welcome-frame frame-0">
+ <div class="asdc-welcome-header">
+ <a class="sprite-welcome logo" class="welcome-logo" ui-sref="dashboard"></a>
+ </div>
+
+ <div class="asdc-welcome-cover"></div>
+ <div class="asdc-whats-new">
+
+ <div class="news-items-row">
+ <div class="news-item-wrapper bg-1">
+ <div class="news-title" translate="WHATS_NEW_1_TITLE"></div>
+ <div class="news-body" translate="WHATS_NEW_1_BODY"></div>
+ </div>
+ <div class="news-item-wrapper bg-2">
+ <div class="news-title" translate="WHATS_NEW_2_TITLE"></div>
+ <div class="news-body" translate="WHATS_NEW_2_BODY"></div>
+ </div>
+ <div class="news-item-wrapper bg-3">
+ <div class="news-title" translate="WHATS_NEW_3_TITLE"></div>
+ <div class="news-body" translate="WHATS_NEW_3_BODY"></div>
+ </div>
+ <div class="news-item-wrapper bg-4">
+ <div class="news-title" translate="WHATS_NEW_4_TITLE"></div>
+ <div class="news-body" translate="WHATS_NEW_4_BODY"></div>
+ </div>
+ </div>
+
+ <div class="news-items-row">
+ <div class="news-item-wrapper bg-5">
+ <div class="news-title" translate="WHATS_NEW_5_TITLE"></div>
+ <div class="news-body" translate="WHATS_NEW_5_BODY"></div>
+ </div>
+ <div class="news-item-wrapper bg-6">
+ <div class="news-title" translate="WHATS_NEW_6_TITLE"></div>
+ <div class="news-body" translate="WHATS_NEW_6_BODY"></div>
+ </div>
+ <div class="news-item-wrapper bg-7">
+ <div class="news-title" translate="WHATS_NEW_7_TITLE"></div>
+ <div class="news-body" translate="WHATS_NEW_7_BODY"></div>
+ </div>
+ <div class="news-item-wrapper bg-8">
+ <div class="news-title" translate="WHATS_NEW_8_TITLE"></div>
+ <div class="news-body" translate="WHATS_NEW_8_BODY"></div>
+ </div>
+ </div>
+
+ </div>
+ </div>
+</div>
diff --git a/catalog-ui/app/scripts/view-models/welcome/slide1.html b/catalog-ui/app/scripts/view-models/welcome/slide1.html
new file mode 100644
index 0000000000..9252026a6b
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/welcome/slide1.html
@@ -0,0 +1,34 @@
+<div class="slide" id="slide-1" data-ng-controller="Sdc.ViewModels.WelcomeStepsControllerViewModel">
+ <div class="asdc-welcome-frame frame-01">
+ <div class="asdc-welcome-header">
+ <!--<a class="sprite-welcome logo" class="welcome-logo" ui-sref="dashboard"></a>-->
+ <!--<a class="whats-new" data-ng-click="gotoSlideIndex(0)">What`s New</a>-->
+ </div>
+
+ <!--<video id="asdc-welcome-video" class="asdc-welcome-video">&lt;!&ndash; autoplay loop muted &ndash;&gt;
+ <source ng-src="{{video_mp4}}" type="video/mp4" />
+ <source ng-src="{{video_ogg}}" type='video/ogg' />
+ </video>-->
+
+ <div class="asdc-welcome-cover"></div>
+ <div class="asdc-welcome-main">
+
+ <!--<div class="asdc-welcome-main-title">INNOVATIVE. RAPID. RELIABLE</div>
+ <div class="asdc-welcome-main-message">
+ AT&Ts leading collaborative network solution design platform
+ </div>
+
+ <div class="asdc-welcome-main-back-btn-ph">
+ <a class="asdc-welcome-main-back-btn" ui-sref="dashboard">Home</a>
+ </div>
+
+ <div class="asdc-welcome-video-icon">
+ <div class="asdc-welcome-video-icon-play sprite-welcome play" data-ng-click="onPlayVideo()"></div>
+ <div class="asdc-welcome-inner-circle"></div>
+ </div>-->
+
+ <h1>Welcome to SDC</h1>
+
+ </div>
+ </div>
+</div>
diff --git a/catalog-ui/app/scripts/view-models/welcome/slide2.html b/catalog-ui/app/scripts/view-models/welcome/slide2.html
new file mode 100644
index 0000000000..4329bf462d
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/welcome/slide2.html
@@ -0,0 +1,26 @@
+<div class="slide" id="slide-2">
+ <div class="asdc-welcome-frame frame-02">
+ <div class="asdc-welcome-slide-text-box">
+ <div class="asdc-welcome-slide-text-box-title">
+ Innovate <br>
+ network- design <br>
+ platform <br>
+ </div>
+ <div class="asdc-welcome-slide-text-box-content">
+ <p>
+ Adapt swiftly to the constant demands placed on <br> networks by ongoing technological advances.
+ </p>
+ <p>
+ Using ASDC’s innovative network-design platform,<br> quickly and easily create and share software
+ <br>
+ components.
+ </p>
+ </div>
+ </div>
+ <div class="asdc-welcome-slide-image-box">
+ <img src="styles/images/welcome/ss-01.png" alt="01" class="asdc-welcome-slide-image">
+ </div>
+ <div class="asdc-welcome-frame-shape"></div>
+ <div class="asdc-welcome-frame-connection"></div>
+ </div>
+</div>
diff --git a/catalog-ui/app/scripts/view-models/welcome/slide3.html b/catalog-ui/app/scripts/view-models/welcome/slide3.html
new file mode 100644
index 0000000000..dd5448beac
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/welcome/slide3.html
@@ -0,0 +1,27 @@
+<div class="slide" id="slide-3">
+ <div class="asdc-welcome-frame frame-03">
+
+ <div class="asdc-welcome-slide-text-box">
+ <div class="asdc-welcome-slide-text-box-title">
+ Enhance <br>
+ and extend
+ </div>
+ <div class="asdc-welcome-slide-text-box-content">
+ <p>
+ Blend, build and arrange resources and services in the <br> designer workspace.
+ </p>
+ <p>
+ Let your creativity lead the way to any number of <br> network solutions.
+ </p>
+ <p>
+ Then simply click on elements to customize and refine <br> their specific properties to match your needs.
+ </p>
+ </div>
+ </div>
+ <div class="asdc-welcome-slide-image-box">
+ <img src="styles/images/welcome/ss-02.png" alt="02" class="asdc-welcome-slide-image">
+ </div>
+ <div class="asdc-welcome-frame-shape"></div>
+ <div class="asdc-welcome-frame-connection"></div>
+ </div>
+</div>
diff --git a/catalog-ui/app/scripts/view-models/welcome/slide4.html b/catalog-ui/app/scripts/view-models/welcome/slide4.html
new file mode 100644
index 0000000000..1428ce5375
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/welcome/slide4.html
@@ -0,0 +1,29 @@
+<div class="slide" id="slide-4">
+ <div class="asdc-welcome-frame frame-04">
+ <div class="asdc-welcome-slide-text-box">
+ <div class="asdc-welcome-slide-text-box-title">
+ Share <br>
+ and Collaborate
+ </div>
+ <div class="asdc-welcome-slide-text-box-content">
+ <p>
+ Graphically arranged, the Catalog is an easily <br> searchable collection of resources, services and
+ <br> products that provides you with a variety of <br> network elements.
+ </p>
+ <p>
+ Benefit from these assets by using them as <br> building blocks to form any number of network
+ <br> solutions.
+ </p>
+ <p>
+ After being certified for release, share and <br> collaborate with others as your solution is
+ <br> automatically included in the catalog.
+ </p>
+ </div>
+ </div>
+ <div class="asdc-welcome-slide-image-box">
+ <img src="styles/images/welcome/ss-03.png" alt="03" class="asdc-welcome-slide-image">
+ </div>
+ <div class="asdc-welcome-frame-shape"></div>
+ <div class="asdc-welcome-frame-connection"></div>
+ </div>
+</div>
diff --git a/catalog-ui/app/scripts/view-models/welcome/slide5.html b/catalog-ui/app/scripts/view-models/welcome/slide5.html
new file mode 100644
index 0000000000..913573c8fc
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/welcome/slide5.html
@@ -0,0 +1,27 @@
+<div class="slide" id="slide-5">
+ <div class="asdc-welcome-frame frame-05">
+ <div class="asdc-welcome-slide-text-box">
+ <div class="asdc-welcome-slide-text-box-title">
+ Fast, efficient <br>
+ and reliable
+ </div>
+ <div class="asdc-welcome-slide-text-box-content">
+ <p>
+ ASDC is a platform built around a simple error-free
+ <br>process for resource and service design and distribution.
+ <br>
+ <br>An integrated certification process makes ASDC a safe
+ <br>and reliable environment to experiment, review and test
+ <br>your work.
+ <br>
+ <br>Once approved your solution is ready to be used in
+ <br>the real-world.
+ </p>
+
+ </div>
+
+ </div>
+ <div class="asdc-welcome-frame-shape"></div>
+
+ </div>
+</div>
diff --git a/catalog-ui/app/scripts/view-models/welcome/slide6.html b/catalog-ui/app/scripts/view-models/welcome/slide6.html
new file mode 100644
index 0000000000..22006f7f82
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/welcome/slide6.html
@@ -0,0 +1,26 @@
+<div class="slide" id="slide-6">
+ <div class="asdc-welcome-frame frame-06">
+
+ <div class="asdc-welcome-cover"></div>
+ <div class="asdc-welcome-main">
+
+ <div class="asdc-welcome-main-title">ASDC</div>
+ <div class="asdc-welcome-main-message">
+ jump starting your network solutions.
+ </div>
+ <div class="asdc-welcome-main-back-btn-ph">
+ <a class="asdc-welcome-main-back-btn" ui-sref="dashboard">Home</a>
+ </div>
+ </div>
+
+ <div class="asdc-welcome-footer">
+ © 2016 AT&T Intellectual Property.© 2016 AT&T Intellectual Property. link. This link will open a new
+ window This link will open a new window All rights reserved.
+ <br/>
+ AT&T, Globe logo, Mobilizing Your World and DIRECTV are registered trademarks of AT&T Intellectual
+ Property and/or AT&T affiliated companies. All other marks are the property of their respective owners.
+
+ </div>
+
+ </div>
+</div>
diff --git a/catalog-ui/app/scripts/view-models/welcome/welcome-steps-controller.ts b/catalog-ui/app/scripts/view-models/welcome/welcome-steps-controller.ts
new file mode 100644
index 0000000000..816afcf2d2
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/welcome/welcome-steps-controller.ts
@@ -0,0 +1,74 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../references"/>
+module Sdc.ViewModels {
+ 'use strict';
+
+ export interface IWelcomeStepsController {
+ video_mp4: string;
+ video_ogg: string;
+ onPlayVideo: Function;
+ }
+
+ export class WelcomeStepsControllerViewModel {
+
+ static '$inject' = [
+ '$scope',
+ '$sce',
+ 'sdcConfig',
+ '$state',
+ '$filter'
+ ];
+
+ constructor(
+ private $scope:IWelcomeStepsController,
+ private $sce:any,
+ private sdcConfig: Models.IAppConfigurtaion,
+ private $state:ng.ui.IStateService,
+ private $filter:ng.IFilterService
+ ){
+ this.init();
+ this.initScope();
+ }
+
+ private init = ():void => {
+
+ };
+
+ private initScope = ():void => {
+
+ this.$scope.onPlayVideo = ():void => {
+ //console.log("onPlayVideo");
+ $("#sdc-page-scroller").removeClass("animated fadeIn");
+ $("#sdc-page-scroller").addClass("animated fadeOut");
+ window.setTimeout(()=>{$("#sdc-page-scroller").css("display","none");},500);
+
+ $("#sdc-welcome-video-wrapper").removeClass("animated fadeOut");
+ $("#sdc-welcome-video-wrapper").addClass("animated fadeIn");
+ window.setTimeout(()=>{$("#sdc-welcome-video-wrapper").css("display","block");},0);
+
+ let videoElement:any = $("#asdc-welcome-video")[0];
+ videoElement.play();
+ };
+
+ };
+
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/welcome/welcome-view.html b/catalog-ui/app/scripts/view-models/welcome/welcome-view.html
new file mode 100644
index 0000000000..ba41e88a4e
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/welcome/welcome-view.html
@@ -0,0 +1,22 @@
+<div class="sdc-welcome-new-page">
+
+ <!--<div id="sdc-welcome-video-wrapper">
+ <div class="asdc-welcome-video-close sprite-welcome close" data-ng-click="onCloseVideoButton()"></div>
+ <video id="asdc-welcome-video" class="asdc-welcome-video">
+ <source ng-src="{{video_mp4}}" type="video/mp4" />
+ <source ng-src="{{video_ogg}}" type='video/ogg' />
+ </video>
+ </div>-->
+
+ <div class="os-welcome">Welcome to SDC</div>
+
+ <!--<sdc-page-scroll id="sdc-page-scroller"
+ start-slide-index="1"
+ slides-data="slides"
+ show-nav="true"
+ show-close-button="true"
+ close-button-callback="onCloseButton">
+
+ </sdc-page-scroll>
+-->
+</div>
diff --git a/catalog-ui/app/scripts/view-models/welcome/welcome-view.ts b/catalog-ui/app/scripts/view-models/welcome/welcome-view.ts
new file mode 100644
index 0000000000..0a0c923481
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/welcome/welcome-view.ts
@@ -0,0 +1,267 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../references"/>
+module Sdc.ViewModels {
+ 'use strict';
+
+ export interface IWelcomeViewMode {
+ slides:Array<Sdc.Directives.SlideData>;
+ onCloseButton():void;
+ onCloseVideoButton():void;
+
+ video_mp4: string;
+ video_ogg: string;
+ }
+
+ export class WelcomeViewModel {
+
+ firstLoad:boolean = true;
+ alreadyAnimated:Array<number> = [];
+
+ static '$inject' = [
+ '$scope',
+ '$sce',
+ 'sdcConfig',
+ '$state',
+ '$filter'
+ ];
+
+ constructor(
+ private $scope:IWelcomeViewMode,
+ private $sce:any,
+ private sdcConfig: Models.IAppConfigurtaion,
+ private $state:ng.ui.IStateService,
+ private $filter:ng.IFilterService
+ ){
+ /*this.init();
+ this.initScope();
+ window.setTimeout(():void => {
+ this.loadImages(():void=> {
+ window.setTimeout(():void =>{
+ $(".sdc-welcome-new-page").addClass("animated fadeIn");
+ this.animateGeneral();
+ this.animate1();
+ },1000);
+ });
+ },0);*/
+ }
+
+ private initScope = ():void => {
+
+ this.$scope.onCloseButton = ():void => {
+ //console.log("onCloseButton");
+ this.$state.go("dashboard", {});
+ };
+
+ this.$scope.onCloseVideoButton = ():void => {
+ //console.log("onCloseVideoButton");
+ $("#sdc-page-scroller").removeClass("animated fadeOut");
+ $("#sdc-page-scroller").addClass("animated fadeIn");
+ window.setTimeout(()=>{$("#sdc-page-scroller").css("display","block");},0);
+
+ $("#sdc-welcome-video-wrapper").removeClass("animated fadeIn");
+ $("#sdc-welcome-video-wrapper").addClass("animated fadeOut");
+ window.setTimeout(()=>{$("#sdc-welcome-video-wrapper").css("display","none");},500);
+
+ let videoElement:any = $("#asdc-welcome-video")[0];
+ videoElement.pause();
+ };
+
+ let url: string = this.sdcConfig.api.welcome_page_video_url;
+
+ this.$scope.video_mp4 = this.$sce.trustAsResourceUrl(url + ".mp4");
+ this.$scope.video_ogg = this.$sce.trustAsResourceUrl(url + ".ogg");
+
+ };
+
+ private init = ():void => {
+ let viewModelsHtmlBasePath:string = '/app/scripts/view-models/';
+ this.$scope.slides = [
+ {"url": viewModelsHtmlBasePath + 'welcome/slide0.html', "id": "slide-0", "index": 0, "callback": () => {this.animate0();}},
+ {"url": viewModelsHtmlBasePath + 'welcome/slide1.html', "id": "slide-1", "index": 1, "callback": () => {}},
+ {"url": viewModelsHtmlBasePath + 'welcome/slide2.html', "id": "slide-2", "index": 2, "callback": () => {this.animate2();}},
+ {"url": viewModelsHtmlBasePath + 'welcome/slide3.html', "id": "slide-3", "index": 3, "callback": () => {this.animate3();}},
+ {"url": viewModelsHtmlBasePath + 'welcome/slide4.html', "id": "slide-4", "index": 4, "callback": () => {this.animate4();}},
+ {"url": viewModelsHtmlBasePath + 'welcome/slide5.html', "id": "slide-5", "index": 5, "callback": () => {this.animate5();}},
+ {"url": viewModelsHtmlBasePath + 'welcome/slide6.html', "id": "slide-6", "index": 6, "callback": () => {this.animate6();}}
+ ];
+
+ $('body').keyup((e):void=> {
+ if (e.keyCode == 27) { // escape key maps to keycode `27`
+ this.$state.go('dashboard');
+ }
+ });
+ };
+
+ private animateGeneral = ():void => {
+ //console.log("animateGeneral");
+
+ /*// Animate the right navigation
+ if (this.firstLoad===true) {
+ //TODO: Israel
+ //TweenLite.from('.page-nav', 2, {x: "100px", delay: 2});
+ }
+ */
+
+ this.firstLoad = false;
+ };
+
+ /*private loadImages = (callback: Function):void => {
+ let src = $('#slide-1 .asdc-welcome-frame').css('background-image');
+ let url = src.match(/\((.*?)\)/)[1].replace(/('|")/g,'');
+
+ let img = new Image();
+ img.onload = function() {
+ callback();
+ //alert('image loaded');
+ };
+ img.src = url;
+ /!*if (img.complete){
+ callback;
+ }*!/
+ };*/
+
+ private animate = (element:any, animation:string, when:number):void => {
+ window.setTimeout(()=>{
+ element.addClass("animated " + animation);
+ if (element[0]) {
+ element[0].style = "visibility: visible;";
+ }
+ },when);
+ };
+
+ private hide = (element:any, animation:string, animationToHide:string, when:number):void => {
+ element.addClass("animated " + animation);
+ element[0].style="visibility: hidden;";
+ };
+
+ private animate0 = ():void => {
+ if (this.alreadyAnimated.indexOf(0)!==-1){
+ return;
+ } else {
+ this.alreadyAnimated.push(0);
+ }
+ //console.log("slide 0 - animate");
+ this.animate($('#slide-0 .bg-1'),'fadeInDown',500);
+ this.animate($('#slide-0 .bg-2'),'fadeInDown',1000);
+ this.animate($('#slide-0 .bg-3'),'fadeInDown',1500);
+ this.animate($('#slide-0 .bg-4'),'fadeInDown',2000);
+
+ this.animate($('#slide-0 .bg-5'),'fadeInDown',2500);
+ this.animate($('#slide-0 .bg-6'),'fadeInDown',3000);
+ this.animate($('#slide-0 .bg-7'),'fadeInDown',3500);
+ this.animate($('#slide-0 .bg-8'),'fadeInDown',4000);
+ };
+
+ private animate1 = ():void => {
+ if (this.alreadyAnimated.indexOf(1)!==-1){
+ return;
+ } else {
+ this.alreadyAnimated.push(1);
+ }
+ //console.log("slide 1 - animate");
+
+ this.animate($('#slide-1 .asdc-welcome-main-title'),'fadeInUp',1000);
+ this.animate($('#slide-1 .asdc-welcome-main-message'),'fadeInUp',2000);
+
+ this.animate($('#slide-1 .asdc-welcome-main-back-btn'),'fadeIn',3000);
+
+ this.animate($('#slide-1 .asdc-welcome-video-icon'),'zoomIn',3000);
+ this.animate($('#slide-1 .asdc-welcome-inner-circle'),'zoomIn',3000);
+
+ this.animate($('.welcome-nav'),'slideInRight',2000);
+ };
+
+ private animate2 = ():void => {
+ if (this.alreadyAnimated.indexOf(2)!==-1){
+ return;
+ } else {
+ this.alreadyAnimated.push(2);
+ }
+ //console.log("slide 2 - animate");
+ this.animate($('#slide-2 .asdc-welcome-frame-shape'),'zoomIn',500);
+ this.animate($('#slide-2 .asdc-welcome-slide-text-box-content'),'fadeInUp',2000);
+ this.animate($('#slide-2 .asdc-welcome-slide-text-box-title'),'fadeInUp',1000);
+ };
+
+ private animate3 = ():void => {
+ if (this.alreadyAnimated.indexOf(3)!==-1){
+ return;
+ } else {
+ this.alreadyAnimated.push(3);
+ }
+ //console.log("slide 3 - animate");
+ this.animate($('#slide-3 .asdc-welcome-frame-shape'),'zoomIn',500);
+ this.animate($('#slide-3 .asdc-welcome-slide-text-box-content'),'fadeInUp',2000);
+ this.animate($('#slide-3 .asdc-welcome-slide-text-box-title'),'fadeInUp',1000);
+ };
+
+ private animate4 = ():void => {
+ if (this.alreadyAnimated.indexOf(4)!==-1){
+ return;
+ } else {
+ this.alreadyAnimated.push(4);
+ }
+ //console.log("slide 4 - animate");
+ this.animate($('#slide-4 .asdc-welcome-frame-shape'),'zoomIn',500);
+ this.animate($('#slide-4 .asdc-welcome-slide-text-box-content'),'fadeInUp',2000);
+ this.animate($('#slide-4 .asdc-welcome-slide-text-box-title'),'fadeInUp',1000);
+ };
+
+ private animate5 = ():void => {
+ if (this.alreadyAnimated.indexOf(5)!==-1){
+ return;
+ } else {
+ this.alreadyAnimated.push(5);
+ }
+ //console.log("slide 5 - animate");
+ this.animate($('#slide-5 .asdc-welcome-frame-shape'),'zoomIn',500);
+ this.animate($('#slide-5 .asdc-welcome-slide-text-box-content'),'fadeInUp',2000);
+ this.animate($('#slide-5 .asdc-welcome-slide-text-box-title'),'fadeInUp',1000);
+ };
+
+ private animate6 = ():void => {
+ if (this.alreadyAnimated.indexOf(6)!==-1){
+ return;
+ } else {
+ this.alreadyAnimated.push(6);
+ }
+ //console.log("slide 6 - animate");
+ this.animate($('#slide-6 .asdc-welcome-main-message'),'fadeInUp',2000);
+ this.animate($('#slide-6 .asdc-welcome-main-title'),'fadeInUp',1000);
+ this.animate($('#slide-6 .asdc-welcome-main-back-btn'),'fadeInUp',3000);
+ };
+
+ private animateCss = (element:JQuery, animationName:string):void => {
+ let animationEnd = 'webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend';
+ element.addClass('animated ' + animationName).one(animationEnd, function() {
+ element.removeClass('animated ' + animationName);
+ });
+ };
+
+ private unAnimateCss = (element:JQuery, animationName:string):void => {
+ let animationEnd = 'webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend';
+ element.addClass('animated ' + animationName).one(animationEnd, function() {
+ element.removeClass('animated ' + animationName);
+ });
+ };
+
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/wizard/ReadMe.txt b/catalog-ui/app/scripts/view-models/wizard/ReadMe.txt
new file mode 100644
index 0000000000..ca5cfb988a
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/wizard/ReadMe.txt
@@ -0,0 +1,54 @@
+import-asset wizard
+========================================================
+
+What should be done in each wizard step:
+----------------------------------------
+
+1. Each step scope should extend IAssetCreationStepScope (this way you can call methods on the wizard scope).
+ Example:
+ export interface IGeneralStepScope extends IAssetCreationStepScope {
+
+ }
+
+2. Each step class should implements IAssetCreationStep
+ Example: export class GeneralStepViewModel implements IAssetCreationStep {
+
+ }
+
+3. Add the method: public save = (callback:Function):void => {}
+ The method should perform the save and call: callback(true); in case of success, or callback(false); in case of error.
+ Example:
+ var onSuccess:Function = (resourceProperties:Services.IResourceResource) => {
+ this.$scope.setAngularResourceOfResource(resourceProperties);
+ var resourceObj = new Sdc.Models.Resource(resourceProperties);
+ this.$scope.setEntity(resourceObj);
+ this.$scope.latestEntityName = (resourceProperties.resourceName);
+ callback(true);
+ };
+
+4. Add the first line after the constructor: this.$scope.registerChild(this);
+ This will register the current step reference in the wizard.
+
+5. Each step can get and set angular $resource of resource from the wizard.
+ // Will be called from each step to get current entity.
+ this.$scope.getAngularResourceOfResource = ():Services.IResourceResource => {
+ return this.resourceProperties;
+ };
+
+ // Will be called from each step after save to update the resource.
+ this.$scope.setAngularResourceOfResource = (resourceProperties:Services.IResourceResource):void => {
+ this.resourceProperties = resourceProperties;
+ this.fillAssetNameAndType();
+ };
+
+ Note: after success save, set setAngularResourceOfResource in the wizard (see example in step 3).
+
+6. The wizard needs to know if the step is valid (to know if to show next button), I used the following to update the wizard:
+ this.$scope.$watch("editForm.$valid", function(newVal, oldVal){
+ this.$scope.setValidState(newVal);
+ });
+
+ Note: in case there is no save for the step, and the step is always valid, call: this.$scope.setValidState(true);
+
+
+
diff --git a/catalog-ui/app/scripts/view-models/wizard/artifact-deployment-step/artifact-deployment-step.html b/catalog-ui/app/scripts/view-models/wizard/artifact-deployment-step/artifact-deployment-step.html
new file mode 100644
index 0000000000..97817d59f2
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/wizard/artifact-deployment-step/artifact-deployment-step.html
@@ -0,0 +1,137 @@
+<div class="artifact-deployment-step" data-ng-controller="Sdc.ViewModels.Wizard.ArtifactDeploymentStepViewModel">
+
+ <div data-tests-id="add-deployment-artifact-button" data-tests-id="addGrey" class="w-sdc-classic-btn gray" data-ng-click="addOrUpdate({})">Add</div>
+
+ <div class="table-container-flex">
+
+ <div class="table">
+
+ <div class="head flex-container">
+ <div class="table-header head-row hand flex-item" data-ng-repeat="header in tableHeadersList track by $index" data-ng-click="sort(header.property)">{{header.title}}
+ <span data-ng-if="sortBy === header.property" class="table-header-sort-arrow" data-ng-class="{'down': reverse, 'up':!reverse}"> </span>
+ </div>
+ <div class="table-no-text-header head-row flex-item"></div>
+ </div>
+
+ <form class="body" name="editForm">
+
+ <perfect-scrollbar scroll-y-margin-offset="0" include-padding="true" class="scrollbar-container">
+
+ <!-- Artifact row -->
+ <div ng-if="noArtifactsToShow()" class="no-row-text" translate="DEPLOYMENT_ARTIFACT_NO_ARTIFACTS_TO_DISPLAY"></div>
+ <div data-ng-repeat-start="artifact in artifacts | orderBy:sortBy:reverse track by $index"
+ class="flex-container data-row"
+ data-ng-class="{'selected': artifact.selected || undefined==artifact.selected && updateInProgress}"
+ data-ng-if="artifact.esId">
+
+ <div class="table-col-general flex-item" data-ng-click="update(artifact)">
+ <loader data-display="isLoading"></loader>
+ <span class="sprite table-arrow" data-tests-id="{{artifact.artifactDisplayName}}" data-ng-class="{'opened': artifact.selected || undefined==artifact.selected && updateInProgress}"></span>
+ {{artifact.artifactDisplayName}}
+ </div>
+
+ <div class="table-col-general flex-item" data-tests-id="{{artifact.artifactType}}">
+ {{artifact.artifactType}}
+ </div>
+ <div class="table-col-general flex-item" data-tests-id="{{artifact.timeout}}">
+ {{artifact.timeout? artifact.timeout:''}}
+ </div>
+
+ <div class="table-btn-col flex-item" sdc-keyboard-events>
+ <button class="table-edit-btn" data-ng-click="addOrUpdate(artifact)"></button>
+ <button class="table-delete-btn" data-ng-click="delete(artifact)"> </button>
+ </div>
+ </div>
+ <div data-ng-repeat-end="" data-ng-if="artifact.selected || undefined==artifact.selected && updateInProgress" class="w-sdc-form item-opened">
+ <!-- Artifact panel opened -->
+
+ <!-- Description field -->
+ <div class="w-sdc-form-item" ng-form="descriptionForm" data-ng-class="{error:(descriptionForm.$dirty && descriptionForm.$invalid)}">
+ <label class="i-sdc-env-form-label required">Description</label>
+ <textarea class="i-sdc-form-textarea {{$index}}"
+ data-ng-maxlength="256"
+ maxlength="256"
+ data-ng-required="true"
+ name="description"
+ data-ng-model="artifact.description"
+ data-ng-model-options="{ debounce: 200 }"
+ data-ng-pattern="getValidationPattern('string')"
+ data-tests-id="description">
+ </textarea>
+
+ <div class="input-error" data-ng-show="descriptionForm.$dirty && descriptionForm.$invalid">
+ <span ng-show="descriptionForm.$error.required" translate="ADD_ARTIFACT_ERROR_DESCRIPTION_REQUIRED"></span>
+ <span ng-show="descriptionForm.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '256' }"></span>
+ <span ng-show="descriptionForm.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span>
+ </div>
+
+ </div>
+
+ <!-- Parameters in 2 columns -->
+ <div class="w-sdc-form-columns-wrapper" data-ng-if="artifact.heatParameters">
+ <!-- Left column -->
+ <div class="w-sdc-form-column">
+ <div class="i-sdc-form-item" ng-form="parameterForm" data-ng-repeat="parameter in artifact.heatParameters.slice(0, artifact.heatParameters.length%2+artifact.heatParameters.length/2) | orderBy: 'name' track by $index">
+ <label class="i-sdc-env-form-label" data-ng-class="{required:parameter.defaultValue}" tooltip-side="top" sdc-smart-tooltip>{{parameter.name +' (' + parameter.type + ')'}}</label>
+ <span class="parameter-description" tooltips tooltip-side="top" tooltip-content="{{parameter.description}}">?</span>
+ <input class="i-sdc-form-input" data-ng-class="{error:(parameterForm.currentValue.$invalid)}"
+ data-ng-model-options="{ debounce: 200 }"
+ data-ng-model="parameter.currentValue"
+ type="text"
+ name="currentValue"
+ data-ng-pattern="getValidationPattern(parameter.type, 'heat')"
+ data-ng-required="parameter.defaultValue"
+ data-ng-change="'json'==parameter.type && parameterForm.currentValue.$setValidity('pattern', validateJson(parameter.currentValue))"
+ data-ng-blur="!parameterForm.currentValue.$error.pattern && resetValue(parameter)"
+ />
+
+ <div class="input-error" data-ng-show="parameterForm.currentValue.$invalid">
+ <span ng-show="parameterForm.currentValue.$error.required" translate="VALIDATION_ERROR_REQUIRED" translate-values="{'field': 'Value'}"></span>
+ <span ng-show="parameterForm.currentValue.$error.pattern && parameter.type==='string'" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span>
+ <span ng-show="parameterForm.currentValue.$error.pattern && !(parameter.type==='string')" translate="VALIDATION_ERROR_TYPE" translate-values="{'type': '{{parameter.type}}'}"></span>
+ </div>
+ </div>
+ </div>
+
+ <!-- Right column -->
+ <div class="w-sdc-form-column">
+ <div class="i-sdc-form-item" ng-form="parameterForm" data-ng-repeat="parameter in artifact.heatParameters.slice(artifact.heatParameters.length%2+artifact.heatParameters.length/2) | orderBy: 'name' track by $index">
+ <label class="i-sdc-env-form-label" data-ng-class="{required:parameter.defaultValue}" tooltip-side="top" sdc-smart-tooltip>{{parameter.name +' (' + parameter.type + ')'}}</label>
+ <span class="parameter-description" tooltips tooltip-side="top" tooltip-content="{{parameter.description}}">?</span>
+ <input class="i-sdc-form-input" data-ng-class="{error:(parameterForm.currentValue.$invalid)}"
+ data-ng-model-options="{ debounce: 200 }"
+ data-ng-model="parameter.currentValue"
+ type="text"
+ name="currentValue"
+ data-ng-pattern="getValidationPattern(parameter.type, 'heat')"
+ data-ng-required="parameter.defaultValue"
+ data-ng-change="'json'==parameter.type && parameterForm.currentValue.$setValidity('pattern', validateJson(parameter.currentValue))"
+ data-ng-blur="!parameterForm.currentValue.$error.pattern && resetValue(parameter)"
+ />
+
+ <div class="input-error" data-ng-show="parameterForm.currentValue.$invalid">
+ <span ng-show="parameterForm.currentValue.$error.required" translate="VALIDATION_ERROR_REQUIRED" translate-values="{'field': 'Value'}"></span>
+ <span ng-show="parameterForm.currentValue.$error.pattern && parameter.type==='string'" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span>
+ <span ng-show="parameterForm.currentValue.$error.pattern && !(parameter.type==='string')" translate="VALIDATION_ERROR_TYPE" translate-values="{'type': '{{parameter.type}}'}"></span>
+ </div>
+ </div>
+ </div>
+
+ </div><!-- Close: Parameters in 2 columns -->
+ </div><!-- Close: Artifact panel opened -->
+
+ <!-- Add artifacts buttons -->
+ <button class="add-button" data-ng-repeat="artifact in artifacts track by $index"
+ data-ng-show="!artifact.esId"
+ data-tests-id="{{artifact.artifactDisplayName}}"
+ translate="DEPLOYMENT_ARTIFACT_BUTTON_ADD_HEAT"
+ translate-values="{'name': '{{artifact.artifactDisplayName}}'}"
+ data-ng-click="addOrUpdate(artifact)"></button>
+
+ <!-- Top add button -->
+ <button class="add-button" translate="DEPLOYMENT_ARTIFACT_BUTTON_ADD_OTHER" data-ng-click="addOrUpdate({})"></button>
+ </perfect-scrollbar>
+ </form>
+ </div>
+ </div>
+</div>
diff --git a/catalog-ui/app/scripts/view-models/wizard/artifact-deployment-step/artifact-deployment-step.less b/catalog-ui/app/scripts/view-models/wizard/artifact-deployment-step/artifact-deployment-step.less
new file mode 100644
index 0000000000..043fba3277
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/wizard/artifact-deployment-step/artifact-deployment-step.less
@@ -0,0 +1,107 @@
+.artifact-deployment-step {
+
+ .table-container-flex .table .body .data-row + div.item-opened {
+ padding: 10px 40px 10px 30px;
+ }
+
+ .w-sdc-classic-btn {
+ float: right;
+ margin-bottom: 10px;
+ }
+
+ .table {
+ height:412px;
+ margin-bottom: 0;
+ }
+
+ .parameter-description {
+ .circle(18px, @color_p);
+ content: '?';
+ line-height: 18px;
+ vertical-align: middle;
+ margin-left: 5px;
+ cursor: default;
+ display: inline-block;
+ position: absolute;
+ top: 16px;
+ }
+
+ .table-container-flex {
+
+ margin-top: 0px;
+
+ .flex-item:nth-child(1) {
+ flex-grow: 15;
+ padding: 5px 4px;
+ .hand;
+ span.table-arrow {
+ margin-right: 7px;
+
+ }
+ }
+
+ .flex-item:nth-child(2) {
+ padding: 5px 4px;
+ flex-grow: 6;
+ }
+
+ .flex-item:nth-child(3) {
+ padding: 5px 4px;
+ flex-grow: 9;
+ }
+
+ .flex-item:nth-child(4) {
+ padding: 5px 4px;
+ flex-grow: 3;
+ padding-top: 10px;
+ }
+ }
+ .w-sdc-form{
+ text-align: left;
+
+ .w-sdc-env-params{
+ border-top: 1px solid #cdcdcd;
+ margin: 25px 0 10px 0;
+ }
+
+ .i-sdc-form-textarea {
+ border: 1px solid @color_e;
+ min-height: 60px;
+ padding: 10px 13px;
+ width: 100%;
+ resize: none;
+
+ &:disabled {
+ .disabled;
+ }
+ }
+
+ .w-sdc-form-item {
+ &.error {
+ .i-sdc-form-input,
+ .i-sdc-form-select,
+ .i-sdc-form-textarea {
+ border-color: @color_h;
+ outline: none;
+ box-sizing: border-box;
+ }
+ }
+ }
+
+ .i-sdc-env-form-label{
+ .p_9;
+ overflow: hidden;
+ max-width: 245px;
+ text-overflow: ellipsis;
+ display: inline-block;
+ white-space: nowrap;
+ margin-top: 14px;
+
+ &.required::before {
+ color: #f33;
+ content: '*';
+ margin-right: 4px;
+ }
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/wizard/artifact-deployment-step/artifact-deployment-step.ts b/catalog-ui/app/scripts/view-models/wizard/artifact-deployment-step/artifact-deployment-step.ts
new file mode 100644
index 0000000000..80f145b9b1
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/wizard/artifact-deployment-step/artifact-deployment-step.ts
@@ -0,0 +1,228 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../../references"/>
+module Sdc.ViewModels.Wizard {
+ 'use strict';
+ import ArtifactModel = Sdc.Models.ArtifactModel;
+
+ interface IArtifactDeploymentStepViewModelScope extends IWizardCreationStepScope {
+ tableHeadersList: Array<any>;
+ reverse: boolean;
+ sortBy:string;
+ component: Models.Components.Component;
+ artifacts: Array<Models.ArtifactModel>;
+ editForm:ng.IFormController;
+ isLoading:boolean;
+ artifactDescriptions:any;
+ updateInProgress:boolean;
+
+ addOrUpdate(artifact:Models.ArtifactModel): void;
+ update(artifact:Models.ArtifactModel): void;
+ delete(artifact:Models.ArtifactModel): void;
+ sort(sortBy:string): void;
+ noArtifactsToShow():boolean;
+ getValidationPattern(validationType:string, parameterType?:string):RegExp;
+ validateJson(json:string):boolean;
+ resetValue(parameter:any):void;
+ }
+
+ export class ArtifactDeploymentStepViewModel implements IWizardCreationStep {
+
+ static '$inject' = [
+ '$scope',
+ '$filter',
+ '$modal',
+ '$templateCache',
+ 'ValidationUtils',
+ 'ModalsHandler'
+ ];
+
+ constructor(
+ private $scope:IArtifactDeploymentStepViewModelScope,
+ private $filter:ng.IFilterService,
+ private $modal:ng.ui.bootstrap.IModalService,
+ private $templateCache:ng.ITemplateCacheService,
+ private validationUtils: Sdc.Utils.ValidationUtils,
+ private ModalsHandler: Utils.ModalsHandler
+ ){
+ this.$scope.registerChild(this);
+ this.$scope.setValidState(true);
+ this.initScope();
+ }
+
+ private initDescriptions = ():void =>{
+ this.$scope.artifactDescriptions = {};
+ _.forEach(this.$scope.component.deploymentArtifacts,(artifact:Models.ArtifactModel):void => {
+ this.$scope.artifactDescriptions[artifact.artifactLabel] = artifact.description;
+ });
+ };
+
+
+ private setArtifact = (artifact:Models.ArtifactModel):void =>{
+ if(artifact.heatParameters) {
+ artifact.heatParameters.forEach((parameter:any):void => {
+ if (!parameter.currentValue && parameter.defaultValue) {
+ parameter.currentValue = parameter.defaultValue;
+ } else if ("" === parameter.currentValue) {
+ parameter.currentValue = null;
+ }
+ });
+ }
+ if(!artifact.description || !this.$scope.getValidationPattern('string').test(artifact.description)){
+ artifact.description = this.$scope.artifactDescriptions[artifact.artifactLabel];
+ }
+ };
+
+ private updateAll = ():void =>{
+ let artifacts:Array<Models.ArtifactModel>= [];
+ _.forEach(this.$scope.component.deploymentArtifacts,(artifact:Models.ArtifactModel): void => {
+ if(artifact.selected) {
+ this.setArtifact(artifact);
+ artifacts.push(artifact);
+ }
+ });
+ this.$scope.component.updateMultipleArtifacts(artifacts);
+ };
+
+
+
+ private initScope = (): void => {
+ let self = this;
+ this.$scope.isLoading = false;
+ this.$scope.updateInProgress = false;
+ this.$scope.component = this.$scope.getComponent();
+ this.initDescriptions();
+ this.$scope.artifacts = <ArtifactModel[]>_.values(this.$scope.component.deploymentArtifacts);
+
+
+ this.$scope.tableHeadersList = [
+ {title:'Name', property: 'artifactDisplayName'},
+ {title:'Type', property: 'artifactType'},
+ {title:'Deployment timeout', property: 'timeout'}
+ ];
+
+ this.$scope.sort = (sortBy:string):void => {
+ this.$scope.reverse = (this.$scope.sortBy === sortBy) ? !this.$scope.reverse : false;
+ this.$scope.sortBy = sortBy;
+ };
+
+ this.$scope.getValidationPattern = (validationType:string, parameterType?:string):RegExp => {
+ return this.validationUtils.getValidationPattern(validationType, parameterType);
+ };
+
+ this.$scope.validateJson = (json:string):boolean => {
+ if(!json){
+ return true;
+ }
+ return this.validationUtils.validateJson(json);
+ };
+
+
+ this.$scope.addOrUpdate = (artifact:Models.ArtifactModel): void => {
+ artifact.artifactGroupType = 'DEPLOYMENT';
+ let artifactCopy = new Models.ArtifactModel(artifact);
+ this.ModalsHandler.openWizardArtifactModal(artifactCopy, this.$scope.component).then(() => {
+ this.$scope.artifactDescriptions[artifactCopy.artifactLabel]= artifactCopy.description;
+ this.$scope.artifacts = <ArtifactModel[]>_.values(this.$scope.component.deploymentArtifacts);
+ })
+ };
+
+ this.$scope.noArtifactsToShow = ():boolean =>{
+ return !_.some(this.$scope.artifacts, 'esId');
+ };
+
+ this.$scope.resetValue = (parameter:any):void => {
+ if(!parameter.currentValue && parameter.defaultValue){
+ parameter.currentValue = parameter.defaultValue;
+ }
+ else if('boolean'==parameter.type){
+ parameter.currentValue = parameter.currentValue.toUpperCase();
+ }
+ };
+
+
+ this.$scope.$watch('editForm.$valid', ():void => {
+ if(this.$scope.editForm) {
+ this.$scope.setValidState(this.$scope.editForm.$valid);
+ }
+ });
+
+ this.$scope.update = (artifact:Models.ArtifactModel):void =>{
+ if(false == this.$scope.isLoading) {
+ if(artifact.selected) {
+ this.$scope.isLoading = true;
+ this.$scope.updateInProgress = true;
+ let onSuccess = (responseArtifact:Models.ArtifactModel):void => {
+ this.$scope.artifactDescriptions[responseArtifact.artifactLabel] = responseArtifact.description;
+ this.$scope.artifacts = <ArtifactModel[]>_.values(this.$scope.component.deploymentArtifacts);
+ this.$scope.isLoading = false;
+ this.$scope.updateInProgress = false;
+ artifact.selected = !artifact.selected;
+ };
+
+ let onFailed = (error:any):void => {
+ console.log('Delete artifact returned error:', error);
+ this.$scope.isLoading = false;
+ this.$scope.updateInProgress = false;
+ artifact.selected = !artifact.selected;
+ };
+
+ this.setArtifact(artifact);
+ this.$scope.component.addOrUpdateArtifact(artifact).then(onSuccess, onFailed);
+ } else {
+ artifact.selected = !artifact.selected;
+ }
+ }
+ };
+
+ this.$scope.delete = (artifact:Models.ArtifactModel):void => {
+ let onOk = ():void => {
+ this.$scope.isLoading = true;
+ let onSuccess = ():void => {
+ this.$scope.isLoading = false;
+ this.$scope.artifacts = <ArtifactModel[]>_.values(this.$scope.component.deploymentArtifacts);
+ };
+
+ let onFailed = (error:any):void => {
+ this.$scope.isLoading = false;
+ console.log('Delete artifact returned error:', error);
+ };
+
+ this.$scope.component.deleteArtifact(artifact.uniqueId, artifact.artifactLabel).then(onSuccess, onFailed);
+ };
+
+ let title:string = self.$filter('translate')("ARTIFACT_VIEW_DELETE_MODAL_TITLE");
+ let message:string = self.$filter('translate')("ARTIFACT_VIEW_DELETE_MODAL_TEXT", "{'name': '" + artifact.artifactDisplayName + "'}");
+ this.ModalsHandler.openConfirmationModal(title, message, false).then(onOk);
+ };
+ };
+
+ public save = (callback:Function):void => {
+ this.updateAll();
+ this.$scope.setComponent(this.$scope.component);
+ callback(true);
+ };
+
+ public back = (callback:Function):void => {
+ this.$scope.setComponent(this.$scope.component);
+ callback(true);
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/wizard/artifact-form-step/artifact-form-step-view-model.ts b/catalog-ui/app/scripts/view-models/wizard/artifact-form-step/artifact-form-step-view-model.ts
new file mode 100644
index 0000000000..459729c179
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/wizard/artifact-form-step/artifact-form-step-view-model.ts
@@ -0,0 +1,304 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../../references"/>
+module Sdc.ViewModels.Wizard {
+ 'use strict';
+ import Resource = Sdc.Models.Components.Resource;
+
+ export interface IEditArtifactStepModel {
+ artifactResource: Models.ArtifactModel;
+ artifactTypes: Array<string>;
+ artifactsFormList: any;
+ artifactFile:any;
+ }
+
+ export interface IArtifactResourceFormStepViewModelScope extends ng.IScope {
+ editForm:ng.IFormController;
+ forms:any;
+ footerButtons: Array<any>;
+ isNew: boolean;
+ isPlaceHolderArtifact:boolean;
+ isLoading: boolean;
+ validationPattern: RegExp;
+ urlValidationPattern: RegExp;
+ labelValidationPattern: RegExp;
+ integerValidationPattern: RegExp;
+ commentValidationPattern: RegExp;
+ artifactType: string;
+ artifactGroupType:string;
+ editArtifactResourceModel: IEditArtifactStepModel;
+ defaultHeatTimeout: number;
+ validExtensions: any;
+ originalArtifactName: string;
+ modalInstanceArtifact:ng.ui.bootstrap.IModalServiceInstance;
+ selectedArtifact:string;
+
+ fileExtensions():string;
+ save(): void;
+ close(): void;
+ changeArtifact(selectedArtifact:string):void;
+ getOptions(): Array<string>;
+ removeInputLabel(): void;
+ fileUploadRequired():string;
+ isDeploymentHeat():boolean;
+ setDefaultTimeout():void;
+ getFormTitle():string;
+ }
+
+ export class ArtifactResourceFormStepViewModel {
+
+ static '$inject' = [
+ '$scope',
+ '$modalInstance',
+ 'artifact',
+ 'component',
+ 'Sdc.Services.CacheService',
+ 'ValidationPattern',
+ 'UrlValidationPattern',
+ 'LabelValidationPattern',
+ 'IntegerValidationPattern',
+ 'CommentValidationPattern',
+ 'ValidationUtils',
+ 'ArtifactsUtils',
+ '$state',
+ '$modal',
+ '$templateCache'
+ ];
+
+ private formState:Utils.Constants.FormState;
+ private entityId:string;
+
+ constructor(private $scope:IArtifactResourceFormStepViewModelScope,
+ private $modalInstance:ng.ui.bootstrap.IModalServiceInstance,
+ private artifact:Models.ArtifactModel,
+ private component:Models.Components.Component,
+ private cacheService:Services.CacheService,
+ private ValidationPattern:RegExp,
+ private UrlValidationPattern:RegExp,
+ private LabelValidationPattern:RegExp,
+ private IntegerValidationPattern:RegExp,
+ private CommentValidationPattern:RegExp,
+ private ValidationUtils:Sdc.Utils.ValidationUtils,
+ private ArtifactsUtils:Sdc.Utils.ArtifactsUtils,
+ private $state:any,
+ private $modal:ng.ui.bootstrap.IModalService,
+ private $templateCache:ng.ITemplateCacheService) {
+
+
+ this.entityId = this.component.uniqueId;
+ this.formState = angular.isDefined(artifact.artifactLabel) ? Utils.Constants.FormState.UPDATE : Utils.Constants.FormState.CREATE;
+
+ this.initScope();
+ this.initEditArtifactResourceModel();
+ this.initComponent();
+ this.initArtifactTypes();
+ }
+
+ private initEditArtifactResourceModel = ():void => {
+ this.$scope.editArtifactResourceModel = {
+ artifactResource: null,
+ artifactTypes: null,
+ artifactsFormList: {},
+ artifactFile: {}
+ }
+ };
+
+ private initComponent = ():void => {
+ this.$scope.editArtifactResourceModel.artifactResource = this.artifact;
+ this.$scope.originalArtifactName = this.artifact.artifactName;
+ let artifacts:any = Utils.Constants.ArtifactGroupType.INFORMATION === this.artifact.artifactGroupType?
+ this.component.artifacts : this.component.deploymentArtifacts;
+ this.$scope.editArtifactResourceModel.artifactsFormList = _.pick(artifacts, (artifact:Models.ArtifactModel)=> {
+ return artifact.artifactLabel && !artifact.esId;
+ });
+ this.$scope.editArtifactResourceModel.artifactFile.filename= this.artifact.artifactName?this.artifact.artifactName:'';
+
+ if(this.artifact.artifactLabel){//this is edit mode
+ this.$scope.editArtifactResourceModel.artifactsFormList[this.artifact.artifactLabel]= this.artifact;
+ this.$scope.selectedArtifact = this.artifact.artifactDisplayName;
+ }
+ };
+
+ private initArtifactTypes = ():void => {
+ let artifactTypes:any = this.cacheService.get('UIConfiguration');
+
+ if (Utils.Constants.ArtifactGroupType.INFORMATION === this.artifact.artifactGroupType) {
+ this.$scope.editArtifactResourceModel.artifactTypes = artifactTypes.artifacts.other.map((element:any)=> {
+ return element.name;
+ });
+ _.remove(this.$scope.editArtifactResourceModel.artifactTypes, (item:string)=> {
+ return _.has(Utils.Constants.ArtifactType.THIRD_PARTY_RESERVED_TYPES, item) ||
+ _.has(Utils.Constants.ArtifactType.TOSCA, item);
+ })
+ }else if(Utils.Constants.ArtifactGroupType.DEPLOYMENT === this.artifact.artifactGroupType) {
+
+ this.$scope.validExtensions = artifactTypes.artifacts.deployment.resourceDeploymentArtifacts;
+ if(this.$scope.validExtensions) {
+ this.$scope.editArtifactResourceModel.artifactTypes = Object.keys(this.$scope.validExtensions);
+ }
+ this.$scope.defaultHeatTimeout = artifactTypes.defaultHeatTimeout;
+
+ if(!this.$scope.isPlaceHolderArtifact) {
+ _.remove(this.$scope.editArtifactResourceModel.artifactTypes, (item:string)=> {
+ return Utils.Constants.ArtifactType.HEAT == item.substring(0,4) ||
+ _.has(Utils.Constants.ArtifactType.THIRD_PARTY_RESERVED_TYPES, item);
+ })
+ }
+
+ if(this.component.isResource() && (<Resource>this.component).isCsarComponent()) {
+ _.remove(this.$scope.editArtifactResourceModel.artifactTypes, (item:string) => {
+ return this.ArtifactsUtils.isLicenseType(item);
+ })
+ }
+ }
+ };
+
+ private initScope = ():void => {
+
+ this.$scope.modalInstanceArtifact = this.$modalInstance;
+ this.$scope.validationPattern = this.ValidationPattern;
+ this.$scope.urlValidationPattern = this.UrlValidationPattern;
+ this.$scope.labelValidationPattern = this.LabelValidationPattern;
+ this.$scope.integerValidationPattern = this.IntegerValidationPattern;
+ this.$scope.commentValidationPattern = this.CommentValidationPattern;
+ this.$scope.isLoading = false;
+ this.$scope.isPlaceHolderArtifact = true;
+ this.$scope.isNew = (this.formState === Utils.Constants.FormState.CREATE);
+ this.$scope.artifactGroupType = this.artifact.artifactGroupType;
+ this.$scope.selectedArtifact = '?';
+
+ this.$scope.fileExtensions = ():string => {
+ let type:string = this.$scope.editArtifactResourceModel.artifactResource.artifactType;
+ return type && this.$scope.validExtensions && this.$scope.validExtensions[type].acceptedTypes ?
+ this.$scope.validExtensions[type].acceptedTypes.join(',') : "";
+ };
+
+ this.$scope.removeInputLabel = ():void => {
+ this.$scope.isPlaceHolderArtifact = true;
+ };
+
+ this.$scope.fileUploadRequired = ():string => {
+ if (this.$scope.isNew===false){
+ return 'false'; // This is edit mode
+ } else {
+ return 'true';
+ }
+ };
+
+ this.$scope.isDeploymentHeat = ():boolean =>{
+ return Utils.Constants.ArtifactGroupType.DEPLOYMENT === this.artifact.artifactGroupType
+ && this.$scope.editArtifactResourceModel.artifactResource
+ && this.$scope.editArtifactResourceModel.artifactResource.artifactType
+ && Utils.Constants.ArtifactType.HEAT === this.$scope.editArtifactResourceModel.artifactResource.artifactType.substring(0,4);
+ };
+
+ this.$scope.getFormTitle =(): string =>{
+ let title:string = this.artifact.esId? 'Update':'Add';
+ if (Utils.Constants.ArtifactGroupType.DEPLOYMENT === this.artifact.artifactGroupType) {
+ title += ' Deployment';
+ }
+ title += ' Artifact';
+ return title;
+ };
+
+ this.$scope.setDefaultTimeout = ():void => {
+ if(!this.$scope.editArtifactResourceModel.artifactResource.timeout) {
+ this.$scope.editArtifactResourceModel.artifactResource.timeout = this.$scope.defaultHeatTimeout;
+ }
+ };
+
+ this.$scope.changeArtifact = (selectedArtifact:string):void => {
+ let tempArtifact:Models.ArtifactModel = this.$scope.editArtifactResourceModel.artifactResource;
+ this.$scope.editArtifactResourceModel.artifactResource = null;
+
+ if (selectedArtifact && selectedArtifact != '' && selectedArtifact != '?') {
+ let artifactResource = <Models.ArtifactModel>_.find(this.$scope.editArtifactResourceModel.artifactsFormList,{'artifactDisplayName':selectedArtifact});
+ this.$scope.editArtifactResourceModel.artifactResource = new Sdc.Models.ArtifactModel(artifactResource);
+ this.$scope.originalArtifactName = this.$scope.editArtifactResourceModel.artifactResource.artifactName;
+ this.$scope.isPlaceHolderArtifact = true;
+ if(this.$scope.isDeploymentHeat()){
+ this.$scope.setDefaultTimeout();
+ }
+ } else if (selectedArtifact === "") {
+ //this.$scope.editArtifactResourceModel.artifactFile = {};
+ this.$scope.editArtifactResourceModel.artifactResource = <Models.ArtifactModel>{};
+ this.$scope.editArtifactResourceModel.artifactResource.artifactGroupType = this.$scope.artifactGroupType;
+ this.$scope.isPlaceHolderArtifact = false;
+ }
+
+ if (_.size(this.$scope.editArtifactResourceModel.artifactFile) && this.$scope.editArtifactResourceModel.artifactResource) {
+ this.$scope.editArtifactResourceModel.artifactResource.artifactName = this.$scope.editArtifactResourceModel.artifactFile.filename;
+ }
+ if(tempArtifact && tempArtifact.description != ''){
+ this.$scope.editArtifactResourceModel.artifactResource.description = tempArtifact.description;
+ }
+
+ this.initArtifactTypes();
+ this.$scope.isNew = true;
+
+ };
+
+
+ this.$scope.save = ():void => {
+ this.$scope.isLoading = true;
+ this.$scope.editArtifactResourceModel.artifactResource.description =
+ this.ValidationUtils.stripAndSanitize(this.$scope.editArtifactResourceModel.artifactResource.description);
+
+ if (this.$scope.editArtifactResourceModel.artifactFile) {
+ this.$scope.editArtifactResourceModel.artifactResource.payloadData = this.$scope.editArtifactResourceModel.artifactFile.base64;
+ this.$scope.editArtifactResourceModel.artifactResource.artifactName = this.$scope.editArtifactResourceModel.artifactFile.filename;
+ }
+
+ let onFailed = (response) => {
+ this.$scope.isLoading = false;
+ console.info('onFaild', response);
+ this.$scope.editArtifactResourceModel.artifactResource.esId = undefined;
+ };
+
+ let onSuccess = () => {
+ this.$scope.isLoading = false;
+ this.$scope.originalArtifactName = "";
+ this.$modalInstance.close();
+
+ };
+
+ this.component.addOrUpdateArtifact(this.$scope.editArtifactResourceModel.artifactResource).then(onSuccess, onFailed);
+ };
+
+
+ this.$scope.close = ():void => {
+ this.$modalInstance.dismiss();
+ };
+
+ //new form layout for import asset
+ this.$scope.forms = {};
+ this.$scope.footerButtons = [
+ {'name': this.artifact.esId ? 'Update' : 'Add', 'css': 'blue', 'callback': this.$scope.save},
+ {'name': 'Cancel', 'css': 'grey', 'callback': this.$scope.close}
+ ];
+
+ this.$scope.$watch("forms.editForm.$invalid", (newVal, oldVal) => {
+ this.$scope.footerButtons[0].disabled = this.$scope.forms.editForm.$invalid;
+ });
+
+ }
+
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/wizard/artifact-form-step/artifact-form-step-view.html b/catalog-ui/app/scripts/view-models/wizard/artifact-form-step/artifact-form-step-view.html
new file mode 100644
index 0000000000..2643b99c20
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/wizard/artifact-form-step/artifact-form-step-view.html
@@ -0,0 +1,147 @@
+<sdc-modal modal="modalInstanceArtifact" type="classic" class="sdc-add-artifact" buttons="footerButtons" header="{{getFormTitle()}}" show-close-button="true">
+
+ <loader data-display="isLoading"></loader>
+
+ <form novalidate class="w-sdc-form two-columns" name="forms.editForm">
+
+ <!--------------------- ARTIFACT FILE -------------------->
+ <div class="i-sdc-form-item">
+ <label class="required">Upload File</label>
+ <file-upload id="fileUploadElement"
+ form-element="forms.editForm"
+ element-required="{{fileUploadRequired()}}"
+ element-name="myArtifactFile"
+ file-model="editArtifactResourceModel.artifactFile"
+ extensions="{{fileExtensions()}}"
+ data-ng-class="{'error': forms.editForm.myArtifactFile.$dirty && forms.editForm.myArtifactFile.$invalid}"></file-upload>
+
+ <div class="input-error-file-upload" data-ng-show="forms.editForm.myArtifactFile.$dirty && forms.editForm.myArtifactFile.$invalid">
+ <span ng-show="forms.editForm.myArtifactFile.$error.required && !forms.editForm.myArtifactFile.$error.emptyFile" translate="ADD_ARTIFACT_ERROR_FILE_REQUIRED"></span>
+ <span ng-show="forms.editForm.myArtifactFile.$error.maxsize" translate="VALIDATION_ERROR_MAX_FILE_SIZE"></span>
+ <span ng-if="'DEPLOYMENT' === artifactGroupType" ng-show="forms.editForm.myArtifactFile.$error.filetype" translate="ADD_ARTIFACT_ERROR_VALID_EXTENSIONS" translate-values="{'extensions': '{{fileExtensions()}}'}"></span>
+ <span ng-show="forms.editForm.myArtifactFile.$error.emptyFile" translate="VALIDATION_ERROR_EMPTY_FILE"></span>
+ </div>
+ </div>
+ <!--------------------- ARTIFACT FILE -------------------->
+
+ <div class="w-sdc-form-columns-wrapper">
+ <div class="w-sdc-form-column">
+
+ <div class="i-sdc-form-item" data-ng-class="{error:(forms.editForm.placeHolder.$dirty && forms.editForm.placeHolder.$invalid)}" data-ng-show="isPlaceHolderArtifact">
+ <label class="i-sdc-form-label required">Artifact</label>
+ <select class="i-sdc-form-select"
+ name="placeHolder"
+ data-ng-disabled="!isNew"
+ data-ng-model="selectedArtifact"
+ data-ng-change="changeArtifact(selectedArtifact)"
+ data-tests-id="selectArtifact">
+ <option disabled value="?">Select Artifact</option>
+ <option data-ng-repeat="(key,value) in editArtifactResourceModel.artifactsFormList">{{value.artifactDisplayName}}</option>
+ <option value="">Create New Artifact</option>
+
+ </select>
+
+ <div class="input-error" data-ng-show="forms.editForm.placeHolder.$dirty && forms.editForm.placeHolder.$invalid">
+ <span ng-show="forms.editForm.placeHolder.$error.required" translate="ADD_ARTIFACT_ERROR_TYPE_REQUIRED"></span>
+ </div>
+ </div>
+
+ <div class="i-sdc-form-item" data-ng-class="{error:(forms.editForm.artifactLabel.$dirty && forms.editForm.artifactLabel.$invalid)}" data-ng-show="!isPlaceHolderArtifact">
+ <label class="i-sdc-form-label required">Artifact Label</label>
+ <input class="i-sdc-form-input"
+ data-ng-maxlength="25"
+ data-ng-model="editArtifactResourceModel.artifactResource.artifactLabel"
+ type="text"
+ name="artifactLabel"
+ data-required
+ data-ng-model-options="{ debounce: 200 }"
+ data-ng-pattern="labelValidationPattern"
+ maxlength="25"
+ data-ng-disabled="!isNew || editArtifactResourceModel.artifactResource.mandatory"
+ data-tests-id="artifactLabel"
+ autofocus/>
+ <span class="w-sdc-icon-cancel" data-ng-click="selectedArtifact='?'; removeInputLabel()"></span>
+
+ <div class="input-error" data-ng-show="forms.editForm.artifactLabel.$dirty && forms.editForm.artifactLabel.$invalid">
+ <span ng-show="forms.editForm.artifactLabel.$error.required" translate="ADD_ARTIFACT_ERROR_LABEL_REQUIRED"></span>
+ <span ng-show="forms.editForm.artifactLabel.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '25' }"></span>
+ <span ng-show="forms.editForm.artifactLabel.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span>
+ </div>
+ </div>
+
+ <div class="i-sdc-form-item" data-ng-class="{error:(forms.editForm.timeout.$dirty && forms.editForm.timeout.$invalid)}"
+ data-ng-if="isDeploymentHeat()">
+ <label class="i-sdc-form-label">Deployment Timeout (minutes)</label>
+ <input class="i-sdc-form-input"
+ data-ng-maxlength="25"
+ data-ng-model="editArtifactResourceModel.artifactResource.timeout"
+ type="number"
+ name="timeout"
+ min="1"
+ max="2147483647"
+ data-ng-model-options="{ debounce: 200 }"
+ data-ng-pattern="integerValidationPattern"
+ data-ng-init="setDefaultTimeout()"
+ data-ng-change="setDefaultTimeout()"
+ maxlength="25"
+ data-tests-id="timeout"/>
+
+ <div class="input-error" data-ng-show="forms.editForm.timeout.$dirty && forms.editForm.timeout.$invalid">
+ <span ng-show="forms.editForm.timeout.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '25' }"></span>
+ <span ng-show="forms.editForm.timeout.$error.pattern" translate="ADD_ARTIFACT_ERROR_TIMEOUT_PATTERN"></span>
+ <span ng-show="forms.editForm.timeout.$error.min" translate="ADD_ARTIFACT_ERROR_TIMEOUT_MIN"></span>
+ </div>
+ </div>
+ <div class="i-sdc-form-item" data-ng-class="{error:(forms.editForm.type.$dirty && forms.editForm.type.$invalid)}">
+ <label class="i-sdc-form-label required">Type</label>
+ <select class="i-sdc-form-select"
+ data-required
+ name="type"
+ data-ng-disabled="!isNew || editArtifactResourceModel.artifactResource.mandatory || '?'==selectedArtifact"
+ data-ng-model="editArtifactResourceModel.artifactResource.artifactType"
+ data-ng-options="type as type for type in editArtifactResourceModel.artifactTypes track by type | uppercase"
+ data-tests-id="artifacttype">
+ <option value="">Choose Type</option>
+ </select>
+
+ <div class="input-error" data-ng-show="forms.editForm.type.$dirty && forms.editForm.type.$invalid">
+ <span ng-show="forms.editForm.type.$error.required" translate="ADD_ARTIFACT_ERROR_TYPE_REQUIRED"></span>
+ </div>
+
+ </div>
+ </div>
+
+ <div class="w-sdc-form-column">
+
+ <div class="i-sdc-form-item"
+ data-ng-class="{error:(forms.editForm.description.$dirty && forms.editForm.description.$invalid)}">
+ <label class="i-sdc-form-label required">Description</label>
+ <textarea class="i-sdc-form-textarea"
+ data-ng-maxlength="256"
+ maxlength="256"
+ data-required
+ name="description"
+ data-ng-model="editArtifactResourceModel.artifactResource.description"
+ data-ng-model-options="{ debounce: 200 }"
+ data-ng-pattern="commentValidationPattern"
+ data-tests-id="description"
+ >
+ </textarea>
+
+ <div class="input-error" data-ng-show="forms.editForm.description.$dirty && forms.editForm.description.$invalid">
+ <span ng-show="forms.editForm.description.$error.required" translate="ADD_ARTIFACT_ERROR_DESCRIPTION_REQUIRED"></span>
+ <span ng-show="forms.editForm.description.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '256' }"></span>
+ <span ng-show="forms.editForm.description.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span>
+ </div>
+
+ </div>
+ </div>
+ </div>
+
+ <span class="w-sdc-form-note" data-ng-show="forms.editForm.$invalid && false" translate="LABEL_ALL_FIELDS_ARE_MANDATORY"></span>
+
+ </form>
+
+</sdc-modal>
+
+
diff --git a/catalog-ui/app/scripts/view-models/wizard/artifact-form-step/artifact-form-step.less b/catalog-ui/app/scripts/view-models/wizard/artifact-form-step/artifact-form-step.less
new file mode 100644
index 0000000000..a189c12d62
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/wizard/artifact-form-step/artifact-form-step.less
@@ -0,0 +1,45 @@
+.sdc-add-artifact {
+
+ .w-sdc-form-note {
+ .h_9;
+ display: block;
+ position: relative;
+ top: 13px;
+ }
+
+ .w-sdc-form {
+
+ .i-sdc-form-textarea{
+ min-height: 95px;
+ }
+
+ .i-sdc-form-item.upload input[type="file"] {
+ display: none
+ }
+
+ .w-sdc-icon-cancel {
+ position: absolute;
+ right: 7px;
+ top: 33px;
+ .sprite;
+ .sprite.small-x-btn-black;
+ .hand;
+ }
+ }
+
+ .artifact-info {
+ text-align: left;
+ color: rgb(140, 140, 140);
+ font-size: 13px;
+ margin-top: -40px;
+ margin-bottom: 5px;
+ width: 600px;
+ min-height: initial;
+
+ span {
+ color: #666666;
+ padding-left: 4px;
+ }
+ }
+
+}
diff --git a/catalog-ui/app/scripts/view-models/wizard/artifact-information-step/artifact-information-step.html b/catalog-ui/app/scripts/view-models/wizard/artifact-information-step/artifact-information-step.html
new file mode 100644
index 0000000000..ea4566561c
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/wizard/artifact-information-step/artifact-information-step.html
@@ -0,0 +1,48 @@
+<div class="artifact-step" data-ng-controller="Sdc.ViewModels.Wizard.ArtifactInformationStepViewModel">
+ <div data-tests-id="add-information-artifact-button" data-tests-id="addGrey" class="w-sdc-classic-btn gray" data-ng-click="addOrUpdate({})" type="button">Add </div>
+ <div class="table-container-flex">
+ <div class="table">
+ <div class="head flex-container">
+ <div class="table-header head-row hand flex-item" ng-repeat="header in tableHeadersList track by $index" data-ng-click="sort(header.property)">{{header.title}}
+ <span data-ng-show="sortBy === header.property" class="table-header-sort-arrow" data-ng-class="{'down': reverse, 'up':!reverse}"> </span>
+ </div>
+ <div class="table-no-text-header head-row flex-item"></div>
+ </div>
+ <div class="body">
+ <perfect-scrollbar suppress-scroll-x="true" scroll-y-margin-offset="0" include-padding="true" class="scrollbar-container">
+ <div data-ng-if="showNoArtifactMessage()" class="no-row-text">
+ There are no information artifacts to display
+ </div>
+ <div data-ng-repeat-start="artifact in artifacts| orderBy:sortBy:reverse track by $index"
+ class="flex-container data-row"
+ data-ng-class="{'selected': artifact.selected}"
+ data-ng-if="artifact.esId">
+
+ <div class="table-col-general flex-item" data-ng-click="artifact.selected = !artifact.selected">
+ <span class="sprite table-arrow" data-ng-class="{'opened': artifact.selected}" data-tests-id="{{artifact.artifactDisplayName}}"></span>
+ {{artifact.artifactDisplayName}}
+ </div>
+
+ <div class="table-col-general flex-item" data-ng-click="artifact.selected = !artifact.selected" data-tests-id="{{artifact.artifactType}}">
+ {{artifact.artifactType}}
+ </div>
+
+ <div class="table-btn-col flex-item" sdc-keyboard-events>
+ <button class="table-edit-btn" data-ng-click="addOrUpdate(artifact)"></button>
+ <button class="table-delete-btn" data-ng-click="delete(artifact)"> </button>
+ </div>
+ </div>
+ <div data-ng-repeat-end="" data-ng-if="artifact.selected" class="item-opened" data-ng-bind="artifact.description"></div>
+ <button class="add-button" data-ng-repeat="artifact in artifacts track by $index"
+ data-ng-show="!artifact.esId"
+ data-tests-id="{{artifact.artifactDisplayName}}"
+ translate="DEPLOYMENT_ARTIFACT_BUTTON_ADD_HEAT"
+ translate-values="{'name': '{{artifact.artifactDisplayName}}'}"
+ data-ng-click="addOrUpdate(artifact)"></button>
+ <button class="add-button" translate="DEPLOYMENT_ARTIFACT_BUTTON_ADD_OTHER"
+ data-ng-click="addOrUpdate({})"></button>
+ </perfect-scrollbar>
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/catalog-ui/app/scripts/view-models/wizard/artifact-information-step/artifact-information-step.less b/catalog-ui/app/scripts/view-models/wizard/artifact-information-step/artifact-information-step.less
new file mode 100644
index 0000000000..b0600ca483
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/wizard/artifact-information-step/artifact-information-step.less
@@ -0,0 +1,50 @@
+.artifact-step {
+
+ .w-sdc-classic-btn {
+ float: right;
+ margin-bottom: 10px;
+ }
+
+ .table{
+ height: 412px;
+ margin-bottom: 0;
+ }
+
+ .table-container-flex {
+ margin-top: 0px;
+
+ .item-opened{
+ word-wrap: break-word;
+ }
+
+
+ .flex-item:nth-child(1) {
+ padding: 5px 4px;
+ flex-grow: 15;
+ .hand;
+ span.table-arrow {
+ margin-right: 7px;
+ }
+ }
+
+ .flex-item:nth-child(2) {
+ padding: 5px 4px;
+ flex-grow: 6;
+ }
+
+ .flex-item:nth-child(3) {
+ padding: 5px 4px;
+ flex-grow: 3;
+ padding-top: 10px;
+ }
+
+ .flex-item:nth-child(4) {
+ padding: 5px 4px;
+ flex-grow: 1;
+ }
+
+ }
+
+}
+
+
diff --git a/catalog-ui/app/scripts/view-models/wizard/artifact-information-step/artifact-information-step.ts b/catalog-ui/app/scripts/view-models/wizard/artifact-information-step/artifact-information-step.ts
new file mode 100644
index 0000000000..f6e25a53a3
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/wizard/artifact-information-step/artifact-information-step.ts
@@ -0,0 +1,168 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../../references"/>
+module Sdc.ViewModels.Wizard {
+ 'use strict';
+ import ArtifactModel = Sdc.Models.ArtifactModel;
+
+ export interface IArtifactInformationStepScope extends IWizardCreationScope {
+ artifacts: Array<Models.ArtifactModel>;
+ tableHeadersList: Array<any>;
+ artifactType: string;
+ isResourceInstance:boolean;
+ downloadFile:Models.IFileDownload;
+ isLoading:boolean;
+ sortBy:string;
+ reverse:boolean;
+ component:Models.Components.Component;
+
+ getTitle(): string;
+ addOrUpdate(artifact:Models.ArtifactModel): void;
+ delete(artifact:Models.ArtifactModel): void;
+ download(artifact:Models.ArtifactModel): void;
+ clickArtifactName(artifact:any):void;
+ openEditEnvParametersModal(artifactResource: Models.ArtifactModel):void;
+ sort(sortBy:string): void;
+ showNoArtifactMessage():boolean;
+ }
+
+ export class ArtifactInformationStepViewModel implements IWizardCreationStep {
+
+ static '$inject' = [
+ '$scope',
+ '$filter',
+ '$modal',
+ '$templateCache',
+ 'Sdc.Services.SharingService',
+ '$state',
+ 'sdcConfig',
+ 'ModalsHandler'
+ ];
+
+ constructor(private $scope:IArtifactInformationStepScope,
+ private $filter:ng.IFilterService,
+ private $modal:ng.ui.bootstrap.IModalService,
+ private $templateCache:ng.ITemplateCacheService,
+ private sharingService:Sdc.Services.SharingService,
+ private $state:any,
+ private sdcConfig:Models.IAppConfigurtaion,
+ private ModalsHandler: Utils.ModalsHandler) {
+ this.$scope.registerChild(this);
+ this.$scope.setValidState(true);
+ this.initScope();
+ }
+
+
+ private getMappedObjects():any {
+ return {
+ normal: this.$scope.component.artifacts
+ };
+ }
+
+ private initScope = ():void => {
+ let self = this;
+ this.$scope.isLoading = false;
+ this.$scope.sortBy = 'artifactDisplayName';
+ this.$scope.reverse = false;
+
+ this.$scope.artifactType = 'normal';
+ this.$scope.getTitle = ():string => {
+ return this.$filter("resourceName")(this.$scope.component.name) + ' Artifacts';
+
+ };
+
+ this.$scope.tableHeadersList = [
+ {title: 'Name', property: 'artifactDisplayName'},
+ {title: 'Type', property: 'artifactType'}
+ ];
+
+
+ this.$scope.component = this.$scope.getComponent();
+ this.$scope.artifacts = <ArtifactModel[]>_.values(this.$scope.component.artifacts);
+
+
+ this.$scope.sort = (sortBy:string):void => {
+ this.$scope.reverse = (this.$scope.sortBy === sortBy) ? !this.$scope.reverse : false;
+ this.$scope.sortBy = sortBy;
+ };
+
+
+ this.$scope.addOrUpdate = (artifact:Models.ArtifactModel):void => {
+ artifact.artifactGroupType = 'INFORMATIONAL';
+ this.ModalsHandler.openWizardArtifactModal(artifact, this.$scope.getComponent()).then(() => {
+ this.$scope.artifacts = <ArtifactModel[]>_.values(this.$scope.component.artifacts);
+ });
+ };
+
+ this.$scope.showNoArtifactMessage = ():boolean => {
+ let artifacts:any = [];
+ artifacts = _.filter(this.$scope.artifacts, (artifact:Models.ArtifactModel)=> {
+ return artifact.esId;
+ });
+
+ if (artifacts.length === 0) {
+ return true;
+ }
+ return false;
+ }
+
+ this.$scope.delete = (artifact:Models.ArtifactModel):void => {
+
+ let onOk = ():void => {
+ this.$scope.isLoading = true;
+ let onSuccess = ():void => {
+ this.$scope.isLoading = false;
+ this.$scope.artifacts = <ArtifactModel[]>_.values(this.$scope.component.artifacts);
+ };
+
+ let onFailed = (error:any):void => {
+ console.log('Delete artifact returned error:', error);
+ this.$scope.isLoading = false;
+ };
+
+ this.$scope.component.deleteArtifact(artifact.uniqueId, artifact.artifactLabel).then(onSuccess, onFailed);
+ };
+
+ let title:string = this.$filter('translate')("ARTIFACT_VIEW_DELETE_MODAL_TITLE");
+ let message:string = this.$filter('translate')("ARTIFACT_VIEW_DELETE_MODAL_TEXT", "{'name': '" + artifact.artifactDisplayName + "'}");
+ this.ModalsHandler.openConfirmationModal(title, message, false).then(onOk);
+ };
+
+ this.$scope.clickArtifactName = (artifact:any) => {
+ if ('deployment' !== this.$scope.artifactType || 'HEAT' !== artifact.artifactType || !artifact.esId) {
+ this.$scope.addOrUpdate(artifact);
+ }
+
+ };
+
+ }
+
+ public save = (callback:Function):void => {
+ this.$scope.setComponent(this.$scope.component);
+ callback(true);
+ }
+
+ public back = (callback:Function):void => {
+ callback(true);
+ }
+
+ }
+
+}
diff --git a/catalog-ui/app/scripts/view-models/wizard/general-step/general-step.html b/catalog-ui/app/scripts/view-models/wizard/general-step/general-step.html
new file mode 100644
index 0000000000..db975caf47
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/wizard/general-step/general-step.html
@@ -0,0 +1,270 @@
+<div include-padding="true" class="sdc-wizard-general-step">
+ <div ng-controller="Sdc.ViewModels.Wizard.GeneralStepViewModel">
+ <form novalidate class="w-sdc-form" name="editForm">
+ <div class="w-sdc-form-section-container">
+
+ <!--------------------- IMPORT TOSCA FILE -------------------->
+ <file-upload id="fileUploadElement"
+ ng-if="!isCreate"
+ element-name="fileElement"
+ element-disabled="{{!isNew}}"
+ form-element="editForm"
+ file-model="model.tosca"
+ extensions="{{toscaFileExtensions}}"
+ data-ng-class="{'error': !editForm.fileElement.$valid || !model.tosca.filename}"></file-upload>
+
+ <div class="input-error-file-upload" data-ng-show="!isCreate && (!editForm.fileElement.$valid || !model.tosca.filename)">
+ <!-- editForm.fileElement.$error.required <== Can not use this, because the browse is done from outside for the first time -->
+ <span ng-show="!model.tosca.filename && !editForm.fileElement.$error.emptyFile" translate="NEW_SERVICE_RESOURCE_ERROR_TOSCA_FILE_REQUIRED"></span><!-- Required -->
+ <span ng-show="editForm.fileElement.$error.emptyFile" translate="VALIDATION_ERROR_EMPTY_FILE"></span>
+ <span ng-show="editForm.fileElement.$error.filetype" translate="NEW_SERVICE_RESOURCE_ERROR_VALID_TOSCA_EXTENSIONS" translate-values="{'extensions': '{{toscaFileExtensions}}' }"></span>
+ </div>
+ <!--------------------- IMPORT TOSCA FILE -------------------->
+
+ <div class="w-sdc-form-columns-wrapper">
+
+ <div class="w-sdc-form-column">
+
+ <!--------------------- NAME -------------------->
+ <div class="i-sdc-form-item" data-ng-class="{'error': validateField(editForm.componentName)}">
+ <label class="i-sdc-form-label required">Name</label>
+ <input class="i-sdc-form-input"
+ name="componentName"
+ data-ng-init="isNew && validateName(true)"
+ data-ng-change="validateName()"
+ data-ng-maxlength="{{component.isProduct()?'25':'50'}}"
+ maxlength="{{component.isProduct()?'25':'50'}}"
+ data-ng-minlength="{{component.isProduct()?'4':'0'}}"
+ minlength="{{component.isProduct()?'4':'0'}}"
+ data-ng-model="model.name"
+ type="text"
+ data-required
+ data-ng-model-options="{ debounce: 500 }"
+ data-ng-pattern="validation.validationPattern"
+ data-ng-disabled="model.isAlreadyCertified"
+ data-tests-id="name"
+ autofocus
+ />
+
+ <div class="input-error" data-ng-show="validateField(editForm.componentName)">
+ <span ng-show="editForm.componentName.$error.required" translate="NEW_SERVICE_RESOURCE_ERROR_RESOURCE_NAME_REQUIRED"></span>
+ <span ng-show="editForm.componentName.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '50' }"></span>
+ <span ng-show="editForm.componentName.$error.minlength" translate="VALIDATION_ERROR_MIN_LENGTH" translate-values="{'min': '4' }"></span>
+ <span ng-show="editForm.componentName.$error.nameExist" translate="NEW_SERVICE_RESOURCE_ERROR_NAME_EXISTS"></span>
+ <span ng-show="editForm.componentName.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span>
+ </div>
+ </div>
+ <!--------------------- NAME -------------------->
+
+ <!--------------------- FULL NAME -------------------->
+ <div ng-if="component.isProduct()" class="i-sdc-form-item" data-ng-class="{'error': validateField(editForm.fullName)}">
+ <label class="i-sdc-form-label required">Full Name</label>
+ <input class="i-sdc-form-input"
+ name="fullName"
+ data-ng-change="validateName()"
+ data-ng-maxlength="100"
+ maxlength="100"
+ data-ng-minlength="4"
+ minlength="4"
+ data-ng-model="model.fullName"
+ type="text"
+ data-required
+ data-ng-model-options="{ debounce: 500 }"
+ data-ng-pattern="validation.validationPattern"
+ data-tests-id="fullName"
+ autofocus
+ />
+
+ <div class="input-error" data-ng-show="validateField(editForm.fullName)">
+ <span ng-show="editForm.fullName.$error.required" translate="NEW_SERVICE_RESOURCE_ERROR_RESOURCE_NAME_REQUIRED"></span>
+ <span ng-show="editForm.fullName.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '50' }"></span>
+ <span ng-show="editForm.fullName.$error.minlength" translate="VALIDATION_ERROR_MIN_LENGTH" translate-values="{'min': '4' }"></span>
+ <span ng-show="editForm.fullName.$error.nameExist" translate="NEW_SERVICE_RESOURCE_ERROR_NAME_EXISTS"></span>
+ <span ng-show="editForm.fullName.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span>
+ </div>
+ </div>
+ <!--------------------- NAME -------------------->
+
+ <!--------------------- DESCRIPTION -------------------->
+ <div class="i-sdc-form-item"
+ data-ng-class="{'error': validateField(editForm.description)}">
+ <label class="i-sdc-form-label required">Description</label>
+ <textarea class="i-sdc-form-textarea description"
+ name="description"
+ data-ng-maxlength="1024"
+ data-required
+ data-ng-model="model.description"
+ data-ng-model-options="{ debounce: 500 }"
+ data-ng-pattern="validation.commentValidationPattern"
+ maxlength="1024"
+ data-tests-id="description"></textarea>
+ <!-- placeholder="Description here..." -->
+
+ <div class="input-error" data-ng-show="validateField(editForm.description)">
+ <span ng-show="editForm.description.$error.required" translate="NEW_SERVICE_RESOURCE_ERROR_RESOURCE_DESCRIPTION_REQUIRED"></span>
+ <span ng-show="editForm.description.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '1024' }"></span>
+ <span ng-show="editForm.description.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span>
+ </div>
+ </div>
+ <!--------------------- DESCRIPTION -------------------->
+
+ <!--------------------- CATEGORIES -------------------->
+ <div class="i-sdc-form-item"
+ data-ng-class="{'error': validateField(editForm.category)}"
+ data-ng-if="categories && categories.length && !component.isProduct()">
+ <label class="i-sdc-form-label required">Category</label>
+ <select class="i-sdc-form-select"
+ data-required
+ name="category"
+ data-ng-change="setIconToDefault()"
+ data-ng-disabled="model.isAlreadyCertified"
+ data-ng-model="model.category"
+ data-tests-id="selectGeneralCategory"
+ >
+ <option value="">Select category</option>
+ <optgroup ng-if="component.isResource()" data-ng-repeat="mainCategory in categories | orderBy:['name']" label="{{mainCategory.name}}" data-tests-id="{{mainCategory.name}}">
+ <option data-ng-repeat="subCategory in mainCategory.subcategories track by $index"
+ data-ng-selected="model.category===calculateUnique(mainCategory.name,subCategory.name)"
+ data-tests-id="{{subCategory.name}}"
+ value="{{calculateUnique(mainCategory.name,subCategory.name)}}">{{subCategory.name}}
+
+ </option>
+ </optgroup>
+ <option ng-if="component.isService()" data-ng-repeat="mainCategory in categories | orderBy:['name']"
+ data-ng-selected="model.category===mainCategory.name"
+ value="{{mainCategory.name}}"
+ data-tests-id="{{mainCategory.name}}">{{mainCategory.name}}</option>
+ </select>
+
+ <div class="input-error" data-ng-show="validateField(editForm.category)">
+ <span ng-show="editForm.category.$error.required" translate="NEW_SERVICE_RESOURCE_ERROR_CATEGORY_REQUIRED"></span>
+ </div>
+ </div>
+ <!--------------------- CATEGORIES -------------------->
+
+ <!--------------------- VENDOR NAME -------------------->
+ <div ng-if="component.isResource()" class="i-sdc-form-item" data-ng-class="{'error': validateField(editForm.vendorName)}">
+ <label class="i-sdc-form-label required">Vendor</label>
+ <input class="i-sdc-form-input" type="text"
+ data-ng-model="model.vendorName"
+ data-ng-model-options="{ debounce: 500 }"
+ data-ng-maxlength="25"
+ data-required
+ ng-click="oldValue = model.vendorName"
+ name="vendorName"
+ data-ng-change="onVendorNameChange(oldValue)"
+ data-ng-pattern="validation.vendorValidationPattern"
+ maxlength="25"
+ data-ng-disabled="model.isAlreadyCertified"
+ data-tests-id="vendorName"
+ />
+
+ <div class="input-error" data-ng-show="validateField(editForm.vendorName)">
+ <span ng-show="editForm.vendorName.$error.required" translate="NEW_SERVICE_RESOURCE_ERROR_VENDOR_NAME_REQUIRED"></span>
+ <span ng-show="editForm.vendorName.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '25' }"></span>
+ <span ng-show="editForm.vendorName.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span>
+ </div>
+
+ </div>
+
+ <!--------------------- VENDOR NAME -------------------->
+
+ <!--------------------- VENDOR RELEASE -------------------->
+ <div ng-if="component.isResource()"
+ class="i-sdc-form-item"
+ data-ng-class="{'error': validateField(editForm.vendorRelease)}">
+ <label class="i-sdc-form-label required">Vendor Release</label>
+ <input class="i-sdc-form-input" type="text"
+ data-ng-model="model.vendorRelease"
+ data-ng-model-options="{ debounce: 500 }"
+ data-ng-maxlength="25"
+ data-required
+ name="vendorRelease"
+ data-ng-pattern="validation.vendorValidationPattern"
+ maxlength="25"
+ data-tests-id="vendorRelease"
+ />
+
+ <div class="input-error" data-ng-show="validateField(editForm.vendorRelease)">
+ <span ng-show="editForm.vendorRelease.$error.required" translate="NEW_SERVICE_RESOURCE_ERROR_VENDOR_RELEASE_REQUIRED"></span>
+ <span ng-show="editForm.vendorRelease.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '128' }"></span>
+ <span ng-show="editForm.vendorRelease.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span>
+ </div>
+ </div>
+ <!--------------------- VENDOR RELEASE -------------------->
+
+
+
+ </div><!-- Close w-sdc-form-column -->
+
+ <div class="w-sdc-form-column">
+
+ <!--------------------- RESOURCE TAGS -------------------->
+ <div class="i-sdc-form-item" data-ng-class="{'error': validateField(editForm.tags)}">
+ <label class="i-sdc-form-label">Tags</label>
+
+ <sdc-tags form-element="editForm" element-name="tags" max-tags="20" class="i-sdc-form-item-tags" tags="model.tags" pattern="validation.tagValidationPattern" special-tag="model.name"></sdc-tags>
+
+ <div class="input-error" data-ng-show="validateField(editForm.tags)">
+ <span ng-show="editForm.tags.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span>
+ </div>
+ </div>
+ <!--------------------- RESOURCE TAGS -------------------->
+
+ <!--------------------- CONTACT ID -------------------->
+ <div class="i-sdc-form-item" data-ng-class="{'error': validateField(editForm.contactId)}">
+ <label class="i-sdc-form-label required" translate="GENERAL_LABEL_CONTACT_ID"></label>
+ <input class="i-sdc-form-input disabled" type="text"
+ data-ng-model="model.userId"
+ data-required
+ name="contactId"
+ data-ng-pattern="validation.contactIdValidationPattern"
+ data-ng-model-options="{ debounce: 500 }"
+ data-tests-id="userId"
+ maxlength="50"
+ />
+
+ <div class="input-error" data-ng-show="validateField(editForm.contactId)">
+ <span ng-show="editForm.contactId.$error.required" translate="NEW_SERVICE_RESOURCE_ERROR_CONTACT_REQUIRED"></span>
+ <span ng-show="editForm.contactId.$error.pattern" translate="NEW_SERVICE_RESOURCE_ERROR_CONTACT_NOT_VALID"></span>
+ </div>
+ </div>
+ <!--------------------- CONTACT ID -------------------->
+
+ <!--------------------- PROJECT CODE -------------------->
+ <div class="i-sdc-form-item" data-ng-if="!component.isResource()"
+ data-ng-class="{'error': validateField(editForm.projectCode)}">
+ <label class="i-sdc-form-label required" translate="GENERAL_LABEL_PROJECT_CODE"></label>
+ <input class="i-sdc-form-input" type="text"
+ data-ng-model="model.projectCode"
+ data-ng-model-options="{ debounce: 500 }"
+ data-ng-maxlength="128"
+ data-required
+ name="projectCode"
+ data-ng-pattern="validation.projectCodeValidationPattern"
+ maxlength="50"
+ data-tests-id="projectCode"
+ />
+
+ <div class="input-error" data-ng-show="validateField(editForm.projectCode)">
+ <span ng-show="editForm.projectCode.$error.required" translate="NEW_SERVICE_RESOURCE_ERROR_PROJECT_CODE_REQUIRED"></span>
+ <span ng-show="editForm.projectCode.$error.pattern" translate="NEW_SERVICE_RESOURCE_ERROR_PROJECT_CODE_NOT_VALID"></span>
+ </div>
+ </div>
+ <!--------------------- VENDOR RELEASE -------------------->
+
+
+ </div><!-- Close w-sdc-form-column -->
+
+ </div><!-- Close w-sdc-form-column -->
+
+ <div class="w-sdc-form-messages-wrapper">
+ <span class="w-sdc-form-messages-msg" data-ng-show="isSaved"><span class="w-sdc-form-messages-msg-v"></span>Your resource has been saved</span>
+ </div>
+
+ </div><!-- Close w-sdc-form-section-container -->
+
+ </form>
+
+ </div>
+</div>
diff --git a/catalog-ui/app/scripts/view-models/wizard/general-step/general-step.less b/catalog-ui/app/scripts/view-models/wizard/general-step/general-step.less
new file mode 100644
index 0000000000..700997a423
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/wizard/general-step/general-step.less
@@ -0,0 +1,34 @@
+.sdc-wizard-general-step {
+
+ .w-sdc-form {
+ padding: 0;
+
+ .w-sdc-form-section-container {
+ text-align: center;
+ }
+
+ .i-sdc-form-item {
+ &.upload {
+ margin-top: 0;
+ width: auto;
+ padding: 10px;
+ }
+ }
+
+ .template-desc {
+ border: 1px dashed @border_color_f;
+ height: 130px;
+ overflow: hidden;
+ padding: 10px 6px 6px 6px;
+ margin-top: 10px;
+ }
+
+ .sdc-tag .tag {
+ max-width: 225px;
+ }
+
+ }
+
+}
+
+
diff --git a/catalog-ui/app/scripts/view-models/wizard/general-step/general-step.ts b/catalog-ui/app/scripts/view-models/wizard/general-step/general-step.ts
new file mode 100644
index 0000000000..74c681e433
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/wizard/general-step/general-step.ts
@@ -0,0 +1,381 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../../references"/>
+module Sdc.ViewModels.Wizard {
+ import ISubCategory = Sdc.Models.ISubCategory;
+ import IMainCategory = Sdc.Models.IMainCategory;
+ 'use strict';
+
+ /*
+ * TODO: The template (derived from is not necessary here).
+ * Need to delete it from all remarks.
+ * */
+
+ export class UIModel {
+ tosca:Sdc.Directives.FileUploadModel;
+ name:string;
+ description:string;
+ vendorName:string;
+ vendorRelease:string;
+ category:string;
+ tags:Array<string>;
+ userId:string;
+ icon:string;
+ projectCode:string;
+ fullName:string;
+ isAlreadyCertified:boolean;
+ }
+
+ export class Validation {
+ validationPattern: RegExp;
+ contactIdValidationPattern: RegExp;
+ tagValidationPattern: RegExp;
+ vendorValidationPattern: RegExp;
+ commentValidationPattern: RegExp;
+ projectCodeValidationPattern: RegExp;
+ }
+
+ export interface IGeneralStepScope extends IWizardCreationStepScope {
+ model:UIModel;
+ validation:Validation;
+ editForm:ng.IFormController;
+ component: Models.Components.Component;
+ categories: Array<IMainCategory>;
+ latestComponentName:string;
+ latestCategoryId: string;
+ latestVendorName: string;
+ isNew:boolean;
+ toscaFileExtensions:any;
+ isCreate:boolean;
+
+ onToscaFileChange():void
+ validateField(field:any):boolean;
+ validateName(isInit:boolean): void;
+ calculateUnique(mainCategory:string, subCategory:string):string; // Build unique string from main and sub category
+ calculatedTagsMaxLength():number;
+ setIconToDefault():void;
+ onVendorNameChange(oldVendorName: string): void;
+ }
+
+ export class GeneralStepViewModel implements IWizardCreationStep {
+
+ static '$inject' = [
+ '$scope',
+ 'Sdc.Services.CacheService',
+ 'ValidationPattern',
+ 'ContactIdValidationPattern',
+ 'TagValidationPattern',
+ 'VendorValidationPattern',
+ 'CommentValidationPattern',
+ 'ValidationUtils',
+ 'sdcConfig',
+ 'ComponentFactory',
+ 'ProjectCodeValidationPattern'
+ ];
+
+ constructor(private $scope:IGeneralStepScope,
+ private cacheService:Services.CacheService,
+ private ValidationPattern:RegExp,
+ private ContactIdValidationPattern:RegExp,
+ private TagValidationPattern:RegExp,
+ private VendorValidationPattern:RegExp,
+ private CommentValidationPattern: RegExp,
+ private ValidationUtils: Sdc.Utils.ValidationUtils,
+ private sdcConfig:Models.IAppConfigurtaion,
+ private ComponentFactory: Sdc.Utils.ComponentFactory,
+ private ProjectCodeValidationPattern:RegExp
+ ) {
+
+ this.$scope.registerChild(this);
+ this.initScopeValidation();
+ this.initScopeMethods();
+ this.initScope();
+ this.$scope.isCreate = this.$scope.data.importFile === undefined;
+ }
+
+ private initScopeValidation = (): void => {
+ this.$scope.validation = new Validation();
+ this.$scope.validation.validationPattern = this.ValidationPattern;
+ this.$scope.validation.contactIdValidationPattern = this.ContactIdValidationPattern;
+ this.$scope.validation.tagValidationPattern = this.TagValidationPattern;
+ this.$scope.validation.vendorValidationPattern = this.VendorValidationPattern;
+ this.$scope.validation.commentValidationPattern = this.CommentValidationPattern;
+ this.$scope.validation.projectCodeValidationPattern = this.ProjectCodeValidationPattern;
+ };
+
+ private initScope = ():void => {
+
+ // Init UIModel
+ this.$scope.model = new UIModel();
+
+ // Init categories
+ if(this.$scope.data.componentType === Utils.Constants.ComponentType.RESOURCE){
+ this.$scope.categories = this.cacheService.get('resourceCategories');
+ }
+ if (this.$scope.data.componentType === Utils.Constants.ComponentType.SERVICE) {
+ this.$scope.categories = this.cacheService.get('serviceCategories');
+ }
+
+ this.$scope.model.category='';
+
+ //init file extenstions
+ this.$scope.toscaFileExtensions = this.sdcConfig.toscaFileExtension;
+
+ // Init Tosca import file
+ if (this.$scope.data.importFile) {
+ this.$scope.model.tosca = this.$scope.data.importFile;
+ }
+
+ // Case insert or update
+ this.$scope.component = this.$scope.getComponent();
+ if ( this.$scope.component!==undefined){
+ // Update mode
+
+ //this.$scope.latestCategoryId = this.$scope.component[0].uniqueId;
+ //this.$scope.latestVendorName = this.$scope.component.vendorName;
+ this.$scope.latestComponentName = this.$scope.component.name;
+ this.$scope.isNew=false;
+ this.resource2ModelUi(this.$scope.component);
+ } else {
+ // Create mode
+ this.$scope.isNew=true;
+ this.$scope.model.tags=[]; // Init tags
+ this.$scope.model.userId = this.cacheService.get("user").userId; // Fill user ID from logged in user
+ this.$scope.model.icon = Utils.Constants.DEFAULT_ICON; // Set the default icon
+ this.$scope.component = this.ComponentFactory.createEmptyComponent(this.$scope.data.componentType);
+ }
+ };
+
+ private initScopeMethods = ():void => {
+
+ this.$scope.validateField = (field:any):boolean => {
+ if (field && field.$dirty && field.$invalid){
+ return true;
+ }
+ return false;
+ };
+
+ this.$scope.validateName = (isInit:boolean):void => {
+ if (isInit===undefined){isInit=false;}
+
+ let name = this.$scope.model.name;
+ if (!name || name===""){
+ if (this.$scope.editForm
+ && this.$scope.editForm["componentName"]
+ && this.$scope.editForm["componentName"].$error){
+
+ // Clear the error name already exists
+ this.$scope.editForm["componentName"].$setValidity('nameExist', true);
+ }
+
+ return;
+ }
+ let subtype:string = Utils.Constants.ComponentType.RESOURCE == this.$scope.data.componentType?
+ this.$scope.data.importFile? 'VFC':'VF' : undefined;
+
+ let onFailed = (response) => {
+ //console.info('onFaild', response);
+ //this.$scope.isLoading = false;
+ };
+
+ let onSuccess = (validation:Models.IValidate) => {
+ this.$scope.editForm["componentName"].$setValidity('nameExist', validation.isValid);
+ };
+
+ if (isInit){
+ // When page is init after update
+ if (this.$scope.model.name !== this.$scope.latestComponentName){
+ if(!this.$scope.component.isProduct()) {//TODO remove when backend is ready
+ this.$scope.component.validateName(name, subtype).then(onSuccess, onFailed);
+ }
+ }
+ } else {
+ // Validating on change (has debounce)
+ if (this.$scope.editForm
+ && this.$scope.editForm["componentName"]
+ && this.$scope.editForm["componentName"].$error
+ && !this.$scope.editForm["componentName"].$error.pattern
+ && this.$scope.model.name !== this.$scope.latestComponentName
+ ) {
+ if(!this.$scope.component.isProduct()) { //TODO remove when backend is ready
+ this.$scope.component.validateName(name, subtype).then(onSuccess, onFailed);
+ }
+ } else if (this.$scope.model.name === this.$scope.latestComponentName) {
+ // Clear the error
+ this.$scope.editForm["componentName"].$setValidity('nameExist', true);
+ }
+ }
+ };
+
+ this.$scope.calculateUnique = (mainCategory:string, subCategory:string):string => {
+ let uniqueId: string = mainCategory;
+ if(subCategory) {
+ uniqueId += "_#_" + subCategory; // Set the select category combobox to show the selected category.
+ }
+ return uniqueId;
+ };
+
+ // Notify the parent if this step valid or not.
+ this.$scope.$watch("editForm.$valid", (newVal, oldVal) => {
+ //console.log("editForm validation: " + newVal);
+ this.$scope.setValidState(newVal);
+ });
+
+ this.$scope.setIconToDefault = ():void => {
+ this.$scope.model.icon = Utils.Constants.DEFAULT_ICON;
+ };
+
+ this.$scope.onVendorNameChange = (oldVendorName: string):void => {
+ if(this.$scope.component.icon === oldVendorName) {
+ this.$scope.setIconToDefault();
+ }
+ };
+ };
+
+ public save = (callback:Function):void => {
+ this.modelUi2Resource();
+
+ let onFailed = (response) => {
+ callback(false);
+ };
+
+ let onSuccess = (component:Models.Components.Component) => {
+ this.$scope.component = component;
+ this.$scope.setComponent(this.$scope.component);
+ this.$scope.latestComponentName = (component.name);
+ callback(true);
+ };
+
+ try {
+ //Send the form with attached tosca file.
+ if (this.$scope.isNew===true) {
+ this.ComponentFactory.createComponentOnServer(this.$scope.component).then(onSuccess, onFailed);
+ } else {
+ this.$scope.component.updateComponent().then(onSuccess, onFailed);
+ }
+ }catch(e){
+ //console.log("ERROR: Error in updating/creating component: " + e);
+ callback(false);
+ }
+
+ };
+
+ public back = (callback:Function):void => {
+ callback(true);
+ }
+
+ // Fill the resource properties object with data from UIModel
+ private modelUi2Resource = ():void => {
+
+ this.$scope.component.name = this.$scope.model.name;
+ this.$scope.component.description = this.ValidationUtils.stripAndSanitize(this.$scope.model.description);
+ this.$scope.component.vendorName = this.$scope.model.vendorName;
+ this.$scope.component.vendorRelease = this.$scope.model.vendorRelease;
+ this.$scope.component.tags = angular.copy(this.$scope.model.tags);
+ this.$scope.component.tags.push(this.$scope.model.name);
+ this.$scope.component.contactId = this.$scope.model.userId;
+ this.$scope.component.icon = this.$scope.model.icon;
+
+ if(this.$scope.component.isResource()) {
+ (<Models.Components.Resource>this.$scope.component).resourceType = "VF";
+
+ // Handle the tosca file
+ if (this.$scope.model.tosca && this.$scope.isNew) {
+ (<Models.Components.Resource>this.$scope.component).payloadData = this.$scope.model.tosca.base64;
+ (<Models.Components.Resource>this.$scope.component).payloadName = this.$scope.model.tosca.filename;
+ }
+
+ this.$scope.component.categories = this.convertCategoryStringToOneArray();
+ }
+
+ if(this.$scope.component.isProduct()) {
+ this.$scope.component.projectCode = this.$scope.model.projectCode;
+ // Handle the tosca file
+ this.$scope.component.categories = undefined;
+ (<Models.Components.Product>this.$scope.component).contacts = new Array<string>();
+ (<Models.Components.Product>this.$scope.component).contacts.push(this.$scope.component.contactId);
+ (<Models.Components.Product>this.$scope.component).fullName = this.$scope.model.fullName;
+ }
+
+ if(this.$scope.component.isService()) {
+ this.$scope.component.projectCode = this.$scope.model.projectCode;
+ this.$scope.component.categories = this.convertCategoryStringToOneArray();
+ }
+ };
+
+ // Fill the UIModel from data from resource properties
+ private resource2ModelUi = (component: Models.Components.Component):void => {
+ this.$scope.model.name = component.name;
+ this.$scope.model.description = component.description;
+ this.$scope.model.vendorName = component.vendorName;
+ this.$scope.model.vendorRelease = component.vendorRelease;
+ this.$scope.model.tags = _.reject(component.tags, (item)=>{return item===component.name});
+ this.$scope.model.userId = component.contactId;
+ this.$scope.model.icon = component.icon;
+ this.$scope.model.projectCode = component.projectCode;
+ this.$scope.model.isAlreadyCertified = component.isAlreadyCertified();
+
+ if(!this.$scope.component.isProduct()) {
+ this.$scope.model.category = this.convertCategoryOneArrayToString(component.categories);
+ }
+
+ if(component.isProduct()) {
+ this.$scope.model.fullName = (<Models.Components.Product>component).fullName;
+
+ }
+
+ };
+
+ // Convert category string MainCategory_#_SubCategory to Array with one item (like the server except)
+ private convertCategoryStringToOneArray = ():Array<Models.IMainCategory> => {
+ let tmp = this.$scope.model.category.split("_#_");
+ let mainCategory = tmp[0];
+ let subCategory = tmp[1];
+
+ // Find the selected category and add the relevant sub category.
+ let selectedMainCategory:IMainCategory = <Models.IMainCategory>_.find(this.$scope.categories, function (item) {
+ return item["name"] === mainCategory
+ });
+ let mainCategoryClone = jQuery.extend(true, {}, selectedMainCategory);
+ if(subCategory) {
+ mainCategoryClone['subcategories'] = [{
+ "name": subCategory
+ }];
+ }
+ let tmpSelected = <Models.IMainCategory> mainCategoryClone;
+
+ let result:Array<Models.IMainCategory> = [];
+ result.push(tmpSelected);
+
+ return result;
+ };
+
+ private convertCategoryOneArrayToString = (categories:Array<Models.IMainCategory>):string => {
+ let mainCategory:string = categories[0].name;
+ let subCategory:string = '';
+ if(categories[0].subcategories) {
+ subCategory = categories[0].subcategories[0].name;
+ }
+ return this.$scope.calculateUnique(mainCategory, subCategory);
+ };
+
+ }
+
+}
diff --git a/catalog-ui/app/scripts/view-models/wizard/hierarchy-step/hierarchy-step.html b/catalog-ui/app/scripts/view-models/wizard/hierarchy-step/hierarchy-step.html
new file mode 100644
index 0000000000..7fc3e9224f
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/wizard/hierarchy-step/hierarchy-step.html
@@ -0,0 +1,40 @@
+<div class="hierarchy-step" data-ng-controller="Sdc.ViewModels.Wizard.HierarchyStepViewModel">
+ <div class="dropdown-container" clicked-outside="{onClickedOutside: 'clickOutside()', clickedOutsideEnable: 'true'}" >
+ <input placeholder="Add Group" data-ng-click="onInputTextClicked()" class="dropdown-input-text" data-ng-model="search.filterTerms" data-ng-model-options="{debounce: 200}"/>
+ <div data-ng-class="{'show': showDropDown}" class="dropdown-content" >
+ <perfect-scrollbar scroll-y-margin-offset="0" include-padding="false" class="scrollbar-container">
+ <div ng-repeat="category in categoriesOptions track by $index">
+ <div ng-repeat="subcategory in category.subcategories track by $index">
+ <div class="dropdown-option" ng-show="!category.filteredGroup || category.filteredGroup.length > 0">
+ <div class="category-container">
+ <div class="category">{{category.name}}</div>
+ <div class="subcategory">{{subcategory.name}}</div>
+ </div>
+ <div class="groupings-container">
+ <div ng-init="group.filterTerms = group.name + ' ' + category.name + ' ' + subcategory.name"
+ ng-repeat="group in (category.filteredGroup = (subcategory.groupings | filter:search )) track by $index">
+ <div class="group" data-ng-disabled="group.isDisabled" data-ng-class="{'disabled-group': group.isDisabled}" ng-click="onGroupSelected(category, subcategory, group)">
+ <span >{{group.name}}</span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </perfect-scrollbar>
+ </div>
+ </div>
+ <div class="hierarchy-groups-container">
+ <perfect-scrollbar scroll-y-margin-offset="0" include-padding="true" class="scrollbar-container">
+ <div ng-if="!product.categories.length || product.categories.length === 0" class="no-groups-text" translate="NEW_PRODUCT_NO_CATEGORIES_TO_DISPLAY"></div>
+ <div ng-repeat="category in product.categories track by $index">
+ <div ng-repeat="subcategory in category.subcategories track by $index">
+ <div class="group-tag" ng-repeat="group in subcategory.groupings track by $index"
+ data-ng-init="tooltip = '<b>' + category.name + '</b><br />' + subcategory.name">
+ <sdc-tag data-on-delete="deleteGroup(uniqueId)" data-tag-data="{tag: group.name, tooltip: tooltip, id: group.uniqueId }"></sdc-tag>
+ </div>
+ </div>
+ </div>
+ </perfect-scrollbar>
+ </div>
+</div>
diff --git a/catalog-ui/app/scripts/view-models/wizard/hierarchy-step/hierarchy-step.less b/catalog-ui/app/scripts/view-models/wizard/hierarchy-step/hierarchy-step.less
new file mode 100644
index 0000000000..74786c127a
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/wizard/hierarchy-step/hierarchy-step.less
@@ -0,0 +1,125 @@
+.hierarchy-step {
+ margin-top: 35px;
+
+ .scrollbar-container{
+ max-height:400px;
+ .perfect-scrollbar;
+ }
+
+ .dropdown-container {
+ position: relative;
+ display: inline-block;
+ width: 100%;
+
+ &:after{
+ top: 47%;
+ right: 1%;
+ border: solid transparent;
+ content: " ";
+ height: 0;
+ width: 0;
+ position: absolute;
+ pointer-events: none;
+ border-color: rgba(0, 0, 0, 0);
+ border-top-color: black;
+ border-width: 4px;
+ margin-left: -4px;
+ }
+
+ .dropdown-input-text {
+ width: 100%;
+ padding: 4px 10px;
+ }
+
+ .dropdown-content {
+ .perfect-scrollbar;
+ border: 1px solid #d8d8d8;
+ display: none;
+ position: absolute;
+ overflow: hidden;
+ width: 100%;
+ .bg_c;
+ max-height: 400px;
+ z-index: 999999;
+
+ .dropdown-option {
+ border-bottom: 1px solid #d8d8d8;
+ display: inline-block;
+ width: 100%;
+ }
+
+ .category-container{
+ width: 250px;
+ float: left;
+ padding-left: 5px;
+
+ .category {
+ .bold;
+ padding: 3px 3px 2px 3px;
+ &:after{
+ .sprite;
+ .arrow-left;
+ content: '';
+ margin-left: 5px;
+ transform: rotate(180deg);
+ }
+ }
+ .subcategory {
+ padding-left: 3px;
+ }
+ }
+
+ .groupings-container{
+ display: inline-block;
+ width: 424px;
+ border-left: 1px solid #d8d8d8;
+ min-height: 55px;
+ .group{
+ padding: 3px 3px 3px 10px;
+ &:hover{
+ .hand;
+ .bg_n;
+ }
+ &.disabled-group {
+ opacity: 0.5;
+ &:hover{
+ cursor: auto;
+ .bg_c;
+ }
+ }
+ }
+ }
+
+ .seperator {
+ height: 1px;
+ width: 100%;
+ .bg_j;
+ margin: 5px 0px;
+ }
+ }
+ .show {
+ display: block;
+ }
+ }
+
+ .hierarchy-groups-container{
+ .b_9;
+ width: 100%;
+ border: 1px solid #d8d8d8;
+ height: 425px;
+ padding: 15px;
+ text-align: center;
+
+ .no-group-text{
+ text-align: center;
+ margin-top:25px;
+ a {
+ cursor: pointer;
+ }
+ }
+ .group-tag{
+ display: inline-block;
+ float: left;
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/wizard/hierarchy-step/hierarchy-step.ts b/catalog-ui/app/scripts/view-models/wizard/hierarchy-step/hierarchy-step.ts
new file mode 100644
index 0000000000..a974c0af81
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/wizard/hierarchy-step/hierarchy-step.ts
@@ -0,0 +1,149 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../../references"/>
+module Sdc.ViewModels.Wizard {
+ 'use strict';
+
+ export interface IHierarchyStepScope extends IWizardCreationScope {
+
+ categoriesOptions: Array<Models.IMainCategory>;
+ product:Models.Components.Product;
+ isLoading:boolean;
+ showDropDown:boolean;
+
+ onInputTextClicked():void;
+ onGroupSelected(category: Models.IMainCategory, subcategory: Models.ISubCategory, group: Models.IGroup):void;
+ clickOutside():void;
+ deleteGroup(uniqueId:string):void;
+ }
+
+ export class HierarchyStepViewModel implements IWizardCreationStep {
+
+ static '$inject' = [
+ '$scope',
+ 'Sdc.Services.CacheService',
+ 'ComponentFactory'
+ ];
+
+ constructor(private $scope:IHierarchyStepScope,
+ private cacheService:Sdc.Services.CacheService,
+ private ComponentFactory: Sdc.Utils.ComponentFactory) {
+
+ this.$scope.registerChild(this);
+ this.$scope.setValidState(true);
+ this.$scope.product = <Models.Components.Product>this.$scope.getComponent();
+ this.initScope();
+ }
+
+ private initCategories = () => {
+ this.$scope.categoriesOptions = angular.copy(this.cacheService.get('productCategories'));
+ let selectedGroup:Array<Models.IGroup> = [];
+ _.forEach(this.$scope.product.categories, (category: Models.IMainCategory) => {
+ _.forEach(category.subcategories, (subcategory:Models.ISubCategory) => {
+ selectedGroup = selectedGroup.concat(subcategory.groupings);
+ });
+ });
+ _.forEach(this.$scope.categoriesOptions, (category: Models.IMainCategory) => {
+ _.forEach(category.subcategories, (subcategory:Models.ISubCategory) => {
+ _.forEach(subcategory.groupings, (group:Models.ISubCategory) => {
+ let componentGroup:Models.IGroup = _.find(selectedGroup, (componentGroupObj) => {
+ return componentGroupObj.uniqueId == group.uniqueId;
+ });
+ if(componentGroup){
+ group.isDisabled = true;
+ }
+ });
+ });
+ });
+ };
+
+ private setFormValidation = ():void => {
+ if(!this.$scope.product.categories || this.$scope.product.categories.length === 0){
+ this.$scope.setValidState(false);
+ }
+ else{
+ this.$scope.setValidState(true);
+ }
+
+ };
+
+ private initScope = ():void => {
+ this.$scope.isLoading= false;
+ this.$scope.showDropDown =false;
+ this.initCategories();
+ this.setFormValidation();
+
+ this.$scope.onGroupSelected = (category: Models.IMainCategory, subcategory: Models.ISubCategory, group: Models.IGroup):void => {
+ this.$scope.showDropDown = false;
+ this.$scope.product.addGroup(category, subcategory, group);
+ group.isDisabled = true;
+ this.setFormValidation();
+ };
+
+ this.$scope.onInputTextClicked = ():void => {//just edit the component in place, no pop up nor server update ?
+ this.$scope.showDropDown = !this.$scope.showDropDown;
+ };
+
+ this.$scope.clickOutside = (): any => {
+ this.$scope.showDropDown = false;
+ };
+
+ this.$scope.deleteGroup = (uniqueId:string) : void => {
+ //delete group from component
+ this.$scope.product.deleteGroup(uniqueId);
+ this.setFormValidation();
+ //enabled group
+ _.forEach(this.$scope.categoriesOptions, (category: Models.IMainCategory) => {
+ _.forEach(category.subcategories, (subcategory:Models.ISubCategory) => {
+ let groupObj:Models.IGroup = _.find (subcategory.groupings, (group) => {
+ return group.uniqueId === uniqueId;
+ });
+ if(groupObj){
+ groupObj.isDisabled = false;
+ }
+ });
+ });
+ }
+ };
+
+ public save = (callback:Function):void => {
+ let onFailed = (response) => {
+ callback(false);
+ };
+
+ let onSuccess = (component: Models.Components.Component) => {
+ this.$scope.product = <Models.Components.Product> this.ComponentFactory.createComponent(component);
+ this.$scope.setComponent(this.$scope.product);
+ callback(true);
+ };
+
+ try {
+ this.$scope.product.updateComponent().then(onSuccess, onFailed);
+ }catch(e){
+ //console.log("ERROR: Error in updating/creating component: " + e);
+ callback(false);
+ }
+ };
+
+ public back = (callback:Function):void => {
+ this.save(callback);
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/wizard/icons-step/icons-step.html b/catalog-ui/app/scripts/view-models/wizard/icons-step/icons-step.html
new file mode 100644
index 0000000000..2ae386283c
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/wizard/icons-step/icons-step.html
@@ -0,0 +1,26 @@
+<div class="icons-step" data-ng-controller="Sdc.ViewModels.Wizard.IconsStepViewModel">
+
+ <form novalidate class="w-sdc-form" name="iconForm">
+ <label class="i-sdc-form-label icons-label required">Icons</label>
+ <div class="selected-icon-container">
+ <div class="i-sdc-form-item-suggested-icon medium selected-icon {{iconSprite}} {{component.icon}}"
+ ng-model="component.icon"
+ tooltips tooltip-content='{{component.icon | translate}}'
+ >
+ </div>
+ </div>
+
+ <label class="i-sdc-form-label icons-label required">Select one of the icons below for the asset</label>
+ <div class="i-sdc-form-item suggested-icons-container">
+ <div class ="suggested-icon-wrapper" ng-class="component.icon==='{{iconSrc}}' ? 'selected' : '' " data-ng-repeat="iconSrc in icons track by $index">
+ <div class="i-sdc-form-item-suggested-icon medium {{iconSprite}} {{iconSrc}}" data-ng-class="component.isAlreadyCertified()? 'disable':'hand'"
+ ng-model="component.icon"
+ data-tests-id="{{iconSrc}} iconBox"
+ data-ng-click="!component.isAlreadyCertified() && setComponentIcon(iconSrc)"
+ tooltips tooltip-content='{{iconSrc | translate}}'
+ >
+ </div>
+ </div>
+ </div>
+ </form>
+</div>
diff --git a/catalog-ui/app/scripts/view-models/wizard/icons-step/icons-step.less b/catalog-ui/app/scripts/view-models/wizard/icons-step/icons-step.less
new file mode 100644
index 0000000000..c03c949962
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/wizard/icons-step/icons-step.less
@@ -0,0 +1,55 @@
+.icons-step {
+
+ .w-sdc-form {
+ padding-top: 0px;
+ padding-bottom: 0px;
+ .selected-icon-container {
+ text-align: left;
+ border: 1px solid #cfcfcf;
+ clear: both;
+ margin-bottom: 15px;
+ padding-left: 3px;
+ padding-bottom: 3px;
+ .selected-icon {
+ margin: 8px 5px 0px 6px;
+ }
+ }
+
+ .suggested-icons-container {
+ text-align: left;
+ border: 1px solid #cfcfcf;
+ clear: both;
+ padding-left: 3px;
+ height: 340px;
+ margin-bottom: 0px;
+
+ .suggested-icon-wrapper {
+ margin: 8px 5px 0px 6px;
+ display: inline-block;
+
+ &.selected {
+ border: 1px solid @color_p;
+ border-radius: 25px;
+ box-shadow: 0 0 2px #888;
+ display: inline-block;
+ line-height: 0px;
+ padding: 2px;
+ }
+
+ }
+ .suggested-icon {
+ // margin: 8px 5px 0px 6px;
+ display: inline-block;
+ &.disable{
+ opacity: 0.4;
+ }
+ }
+
+
+ }
+
+ .icons-label {
+ float: left;
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/wizard/icons-step/icons-step.ts b/catalog-ui/app/scripts/view-models/wizard/icons-step/icons-step.ts
new file mode 100644
index 0000000000..4dc5e377fa
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/wizard/icons-step/icons-step.ts
@@ -0,0 +1,150 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../../references"/>
+module Sdc.ViewModels.Wizard {
+ 'use strict';
+
+ export interface IIconsStepScope extends IWizardCreationStepScope{
+ icons : Array<string>;
+ component: Models.Components.Component;
+ iconSprite: string;
+ setComponentIcon(iconSrc:string): void;
+ }
+
+ export class IconsStepViewModel implements IWizardCreationStep {
+
+ static '$inject' = [
+ '$scope',
+ 'Sdc.Services.AvailableIconsService',
+ 'ComponentFactory'
+ ];
+
+ constructor(private $scope:IIconsStepScope,
+ private availableIconsService:Services.AvailableIconsService,
+ private ComponentFactory: Sdc.Utils.ComponentFactory) {
+
+ this.$scope.registerChild(this);
+ this.$scope.component = this.$scope.getComponent();
+ this.$scope.iconSprite = this.$scope.component.iconSprite;
+ this.initScope();
+ this.initIcons();
+
+ if(this.$scope.component.isResource()) {
+ this.initVendor();
+ }
+ // In case there is one icons select it.
+ if( this.$scope.icons.length == 1 && !this.$scope.component.isAlreadyCertified()){
+ this.$scope.setComponentIcon(this.$scope.icons[0]);
+ }
+ }
+
+ private initIcons = ():void => {
+
+ // For subcategories that where created by admin, there is no icons
+ this.$scope.icons = new Array<string>();
+ if (this.$scope.component.categories && this.$scope.component.categories.length > 0) {
+
+ _.forEach(this.$scope.component.categories, (category:Models.IMainCategory):void => {
+ if (category.icons) {
+ this.$scope.icons = this.$scope.icons.concat(category.icons);
+ }
+ if (category.subcategories) {
+ _.forEach(category.subcategories, (subcategory:Models.ISubCategory):void => {
+ if (subcategory.icons) {
+ this.$scope.icons = this.$scope.icons.concat(subcategory.icons);
+ }
+ });
+ }
+ });
+ }
+
+ if (this.$scope.component.isResource()) {
+ let resourceType:string = this.$scope.component.getComponentSubType();
+ if (resourceType === 'VL') {
+ this.$scope.icons = ['vl'];
+ }
+ if (resourceType === 'CP') {
+ this.$scope.icons = ['cp'];
+ }
+ }
+
+ if (this.$scope.icons.length === 0) {
+ this.$scope.icons = this.availableIconsService.getIcons(this.$scope.component.componentType);
+ }
+
+ };
+
+ private initVendor = ():void => {
+ let vendors:Array<string> = this.availableIconsService.getIcons(this.$scope.component.componentType).slice(5, 19);
+ let vendorName = this.$scope.component.vendorName.toLowerCase();
+ if ('at&t' === vendorName){
+ vendorName = 'att';
+ }
+ if ('nokia' === vendorName){
+ vendorName = 'nokiasiemens';
+ }
+
+ let vendor:string = _.find(vendors, (vendor:string)=>{
+ return vendor.replace(/[_]/g, '').toLowerCase() === vendorName;
+ });
+
+ if(vendor && this.$scope.icons.indexOf(vendor)===-1) {
+ this.$scope.icons.push(vendor);
+ }
+ };
+
+ private initScope():void {
+ this.$scope.icons = [];
+
+ if(this.$scope.component.icon === Utils.Constants.DEFAULT_ICON){
+ this.$scope.setValidState(false);
+ }
+
+ this.$scope.setComponentIcon = (iconSrc:string):void => {
+ this.$scope.component.icon = iconSrc;
+ this.$scope.setValidState(true);
+ }
+ }
+
+ save(callback:Function):void {
+ let onFailed = () => {
+ callback(false);
+ };
+
+ let onSuccess = (component:Models.Components.Component) => {
+ this.$scope.component = component;
+ this.$scope.setComponent(this.$scope.component);
+ callback(true);
+ };
+
+ try {
+ this.$scope.component.updateComponent().then(onSuccess, onFailed);
+ }catch(e){
+ callback(false);
+ }
+ }
+
+ public back = (callback:Function):void => {
+ this.save(callback);
+ }
+
+ }
+
+}
diff --git a/catalog-ui/app/scripts/view-models/wizard/properties-step/properties-step.html b/catalog-ui/app/scripts/view-models/wizard/properties-step/properties-step.html
new file mode 100644
index 0000000000..4429451871
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/wizard/properties-step/properties-step.html
@@ -0,0 +1,57 @@
+<div class="properties-step" data-ng-controller="Sdc.ViewModels.Wizard.PropertiesStepViewModel">
+
+ <div class="w-sdc-classic-btn gray" data-tests-id="addGrey" data-ng-click="addOrUpdateProperty()">Add</div>
+
+ <div class="table-container-flex">
+
+ <div class="table">
+ <div class="head flex-container">
+ <div class="table-header head-row hand flex-item" data-ng-repeat="header in tableHeadersList track by $index" data-ng-click="sort(header.property)">{{header.title}}
+ <span data-ng-if="sortBy === header.property" class="table-header-sort-arrow" data-ng-class="{'down': reverse, 'up':!reverse}"> </span>
+ </div>
+ <div class="table-no-text-header head-row flex-item"></div>
+ <!--div class="table-no-text-header head-row flex-item"></div-->
+ </div>
+
+ <div class="body">
+ <perfect-scrollbar scroll-y-margin-offset="0" include-padding="true" class="scrollbar-container">
+ <div data-ng-if="component.properties.length === 0 " class="no-row-text">
+ There are no properties to display <br>
+ click <a data-ng-click="addOrUpdateProperty()">here</a> to add one
+ </div>
+ <div data-ng-repeat-start="property in component.properties | orderBy:sortBy:reverse track by $index"
+ class="flex-container data-row" data-ng-class="{'selected': property.selected}"
+ data-ng-click="property.selected = !property.selected">
+
+ <div class="table-col-general flex-item text">
+ <span class="sprite table-arrow" data-ng-class="{'opened': property.selected}"></span>
+ <span data-tests-id="{{property.name}}" tooltips tooltip-content="{{property.name}}">{{property.name}}</span>
+
+ </div>
+
+ <div class="table-col-general flex-item text" data-tests-id="{{property.type}}" data-ng-bind="property.type"></div>
+
+ <div class="table-col-general flex-item text">
+ <span tooltips tooltip-content="{{property.defaultValue}}" data-tests-id="{{property.defaultValue}}" data-ng-bind="property.defaultValue"></span>
+ </div>
+
+ <div class="table-btn-col flex-item">
+ <button class="table-edit-btn" data-ng-show="property.parentUniqueId==component.uniqueId"
+ data-ng-click="addOrUpdateProperty(property); $event.stopPropagation();"> </button>
+ <button class="table-delete-btn" data-ng-show="property.parentUniqueId==component.uniqueId"
+ data-ng-click="delete(property); $event.stopPropagation();"> </button>
+ </div>
+
+ <!--div class="table-btn-col flex-item">
+
+ </div-->
+ </div>
+ <div data-ng-repeat-end="" data-ng-if="property.selected && property.description" class="item-opened" data-ng-bind="property.description">
+ </div>
+ </perfect-scrollbar>
+ </div>
+
+ </div>
+ </div>
+
+</div>
diff --git a/catalog-ui/app/scripts/view-models/wizard/properties-step/properties-step.less b/catalog-ui/app/scripts/view-models/wizard/properties-step/properties-step.less
new file mode 100644
index 0000000000..0d7dad8dc2
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/wizard/properties-step/properties-step.less
@@ -0,0 +1,55 @@
+.properties-step {
+
+ .w-sdc-classic-btn {
+ float: right;
+ margin-bottom: 10px;
+ }
+
+ .table{
+ height: 412px;
+ margin-bottom: 0;
+ }
+
+ .table-container-flex {
+ margin-top: 0px;
+
+ .text{
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: inline-block;
+ white-space: nowrap;
+ padding: 5px 4px;
+
+ }
+
+ .flex-item:nth-child(1) {
+ flex-grow: 15;
+
+ .hand;
+ span.table-arrow {
+ margin-right: 7px;
+ }
+ }
+
+ .flex-item:nth-child(2) {
+ flex-grow: 6;
+ }
+
+ .flex-item:nth-child(3) {
+ flex-grow: 9;
+ }
+
+ .flex-item:nth-child(4) {
+ flex-grow: 3;
+ //padding-top: 10px;
+ padding: 10px 4px;
+
+ }
+
+ .flex-item:nth-child(5) {
+ flex-grow: 1;
+ }
+
+ }
+
+}
diff --git a/catalog-ui/app/scripts/view-models/wizard/properties-step/properties-step.ts b/catalog-ui/app/scripts/view-models/wizard/properties-step/properties-step.ts
new file mode 100644
index 0000000000..08dfb5e153
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/wizard/properties-step/properties-step.ts
@@ -0,0 +1,123 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../../references"/>
+module Sdc.ViewModels.Wizard {
+ 'use strict';
+
+ interface IPropertiesStepViewModelScope extends IWizardCreationStepScope {
+ component: Models.Components.Component;
+ tableHeadersList: Array<any>;
+ reverse: boolean;
+ sortBy:string;
+
+ addOrUpdateProperty(): void;
+ delete(property: Models.PropertyModel): void;
+ sort(sortBy:string): void;
+ }
+
+ export class PropertiesStepViewModel implements IWizardCreationStep {
+
+ static '$inject' = [
+ '$scope',
+ '$filter',
+ '$modal',
+ '$templateCache',
+ 'ModalsHandler'
+ ];
+
+
+ constructor(
+ private $scope:IPropertiesStepViewModelScope,
+ private $filter:ng.IFilterService,
+ private $modal:ng.ui.bootstrap.IModalService,
+ private $templateCache:ng.ITemplateCacheService,
+ private ModalsHandler: Utils.ModalsHandler
+ ){
+
+ this.$scope.registerChild(this);
+ this.$scope.setValidState(true);
+ this.initScope();
+ }
+
+ public save = (callback:Function):void => {
+ this.$scope.setComponent(this.$scope.component);
+ callback(true);
+ };
+
+ public back = (callback:Function):void => {
+ this.$scope.setComponent(this.$scope.component);
+ callback(true);
+ }
+
+
+ private openEditPropertyModal = (property: Models.PropertyModel): void => {
+ let viewModelsHtmlBasePath: string = '/app/scripts/view-models/';
+
+ let modalOptions: ng.ui.bootstrap.IModalSettings = {
+ template: this.$templateCache.get(viewModelsHtmlBasePath+'wizard/property-form/property-form.html'),
+ controller: 'Sdc.ViewModels.Wizard.PropertyFormViewModel',
+ size: 'sdc-md',
+ backdrop: 'static',
+ keyboard: false,
+ resolve: {
+ property: (): Models.PropertyModel => {
+ return property;
+ },
+ component: (): Models.Components.Component => {
+ return <Models.Components.Component> this.$scope.getComponent();
+ }
+ }
+ };
+ this.$modal.open(modalOptions);
+ };
+
+ private initScope = (): void => {
+
+ let self = this;
+ this.$scope.component = this.$scope.getComponent();
+ this.$scope.sortBy = 'name';
+ this.$scope.reverse = false;
+
+ this.$scope.tableHeadersList = [
+ {title:'Name', property: 'name'},
+ {title:'Type', property: 'type'},
+ {title:'Default Value', property: 'defaultValue'}
+ ];
+ this.$scope.sort = (sortBy:string):void => {
+ this.$scope.reverse = (this.$scope.sortBy === sortBy) ? !this.$scope.reverse : false;
+ this.$scope.sortBy = sortBy;
+ };
+
+ this.$scope.addOrUpdateProperty = (property?: Models.PropertyModel): void => {
+ this.openEditPropertyModal(property ? property : new Models.PropertyModel());
+ };
+
+ this.$scope.delete = (property: Models.PropertyModel): void => {
+
+ let onOk = (): void => {
+ this.$scope.component.deleteProperty(property.uniqueId);
+ };
+ let title:string = this.$filter('translate')("PROPERTY_VIEW_DELETE_MODAL_TITLE");
+ let message:string = this.$filter('translate')("PROPERTY_VIEW_DELETE_MODAL_TEXT", "{'name': '" + property.name + "'}");
+ this.ModalsHandler.openConfirmationModal(title, message, false).then(onOk);
+ };
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/wizard/property-form/property-form-view-model-tests.ts b/catalog-ui/app/scripts/view-models/wizard/property-form/property-form-view-model-tests.ts
new file mode 100644
index 0000000000..3f390841ca
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/wizard/property-form/property-form-view-model-tests.ts
@@ -0,0 +1,163 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../../references"/>
+
+describe("property form View Model ", () => {
+
+ let $controllerMock:ng.IControllerService;
+ let $qMock:ng.IQService;
+ let $httpBackendMock:ng.IHttpBackendService;
+ let $scopeMock:Sdc.ViewModels.Wizard.IPropertyFormViewModelScope;
+ let $stateMock:ng.ui.IStateService;
+ let $stateParams:any;
+ let component = {
+ "uniqueId": "ece818e0-fd59-477a-baf6-e27461a7ce23",
+ "uuid": "8db823c2-6a9c-4636-8676-f5e713270dd7",
+ "contactId": "uf2345",
+ "category": "Network Layer 2-3/Router",
+ "creationDate": 1447235352429,
+ "description": "u",
+ "highestVersion": true,
+ "icon": "network",
+ "lastUpdateDate": 1447235370064,
+ "lastUpdaterUserId": "cs0008",
+ "lastUpdaterFullName": "Carlos Santana",
+ "lifecycleState": "NOT_CERTIFIED_CHECKOUT",
+ "name": "u",
+ "version": "0.1",
+ "type": 1,
+ "tags": [
+ "u"
+ ],
+ "vendorName": "u",
+ "vendorRelease": "u",
+ "systemName": "U",
+ "$$hashKey": "object:34"
+ };
+
+
+ beforeEach(angular.mock.module('sdcApp'));
+
+ beforeEach(angular.mock.inject((_$controller_:ng.IControllerService,
+ _$httpBackend_:ng.IHttpBackendService,
+ _$rootScope_,
+ _$q_:ng.IQService,
+ _$state_:ng.ui.IStateService,
+ _$stateParams_:any) => {
+
+ $controllerMock = _$controller_;
+ $httpBackendMock = _$httpBackend_
+ $scopeMock = _$rootScope_.$new();
+ $qMock = _$q_;
+ $stateMock = _$state_;
+ $stateParams = _$stateParams_;
+
+
+ //handle all http request thet not relevant to the tests
+ $httpBackendMock.expectGET(/.*languages\/en_US.json.*/).respond(200, JSON.stringify({}));
+ // $httpBackendMock.expectGET(/.*resources\/certified\/abstract.*/).respond(200, JSON.stringify({}));
+ $httpBackendMock.expectGET(/.*rest\/version.*/).respond(200, JSON.stringify({}));
+ $httpBackendMock.expectGET(/.*configuration\/ui.*/).respond(200, JSON.stringify({}));
+ $httpBackendMock.expectGET(/.*user\/authorize.*/).respond(200, JSON.stringify({}));
+ $httpBackendMock.expectGET(/.*categories\/services.*/).respond(200, JSON.stringify({}));
+ $httpBackendMock.expectGET(/.*categories\/resources.*/).respond(200, JSON.stringify({}));
+ $httpBackendMock.expectGET(/.*categories\/products.*/).respond(200, JSON.stringify({}));
+ $httpBackendMock.expectGET('http://feHost:8181/sdc1/feProxy/rest/version').respond(200, JSON.stringify({}));
+
+ /**
+ * Mock the service
+ * @type {any}
+ */
+ //getAllEntitiesDefered = $qMock.defer();
+ //getAllEntitiesDefered.resolve(getAllEntitiesResponseMock);
+ //entityServiceMock = jasmine.createSpyObj('entityServiceMock', ['getAllComponents']);
+ //entityServiceMock.getAllComponents.and.returnValue(getAllEntitiesDefered.promise);
+
+ // $stateParams['show'] = '';
+
+ /**
+ * Need to inject into the controller only the objects that we want to MOCK
+ * those that we need to change theirs behaviors
+ */
+ $controllerMock(Sdc.ViewModels.Wizard.PropertyFormViewModel, {
+ '$scope': $scopeMock,
+ 'property': new Sdc.Models.PropertyModel(),
+ 'component': component,
+ });
+
+ }));
+
+ describe("when Controller 'PropertyFormViewModel' created", () => {
+
+ it('should have a regexp per each type', () => {
+ $scopeMock.$apply();
+ expect(Object.keys($scopeMock.listRegex).length).toBe($scopeMock.editPropertyModel["simpleTypes"].length);
+ });
+
+ it('should have equal regexps for map and list', () => {
+ $scopeMock.$apply();
+ expect(Object.keys($scopeMock.listRegex).length).toBe(Object.keys($scopeMock.mapRegex).length);
+ });
+
+ });
+
+ /*describe("when Controller 'DashboardViewModel' created", () => {
+
+ it('should generate all entities', () => {
+ $scopeMock.$apply();
+ expect($scopeMock.components.length).toBe(getAllEntitiesResponseMock.length);
+ });
+
+
+ it('should show tutorial page ', () => {
+ $stateParams.show = 'tutorial';
+
+ $controllerMock(Sdc.ViewModels.DashboardViewModel, {
+ '$scope': $scopeMock,
+ '$stateParams': $stateParams,
+ 'Sdc.Services.EntityService': entityServiceMock,
+ //to complete injects
+ });
+
+ $scopeMock.$apply();
+ expect($scopeMock.isFirstTime).toBeTruthy();
+ expect($scopeMock.showTutorial).toBeTruthy();
+ });
+
+ });
+
+
+ describe("when function 'entitiesCount' invoked", () => {
+
+ beforeEach(() => {
+ $controllerMock(Sdc.ViewModels.DashboardViewModel, {
+ '$scope': $scopeMock,
+ 'Sdc.Services.EntityService': entityServiceMock,
+ });
+ $scopeMock.$apply();
+ });
+
+ it('should return entities count per folder', () => {
+
+ });
+
+
+ });*/
+});
diff --git a/catalog-ui/app/scripts/view-models/wizard/property-form/property-form-view-model.ts b/catalog-ui/app/scripts/view-models/wizard/property-form/property-form-view-model.ts
new file mode 100644
index 0000000000..5cb0ef1ddd
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/wizard/property-form/property-form-view-model.ts
@@ -0,0 +1,250 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../../references"/>
+
+module Sdc.ViewModels.Wizard {
+ 'use strict';
+
+ export interface IEditPropertyModel{
+ property: Models.PropertyModel;
+ types: Array<string>;
+ simpleTypes: Array<string>;
+ sources: Array<string>;
+ }
+
+ export interface IPropertyFormViewModelScope extends ng.IScope{
+
+ $$childTail: any;
+ editForm:ng.IFormController;
+ forms:any;
+ footerButtons: Array<any>;
+ isNew: boolean;
+ isLoading: boolean;
+ validationPattern: RegExp;
+ propertyNameValidationPattern: RegExp;
+ integerValidationPattern: RegExp;
+ floatValidationPattern: RegExp;
+ commentValidationPattern: RegExp;
+ listRegex: Sdc.Utils.IMapRegex;
+ mapRegex: Sdc.Utils.IMapRegex;
+ editPropertyModel: IEditPropertyModel;
+ modalInstanceProperty: ng.ui.bootstrap.IModalServiceInstance;
+ save(doNotCloseModal?: boolean): void;
+ saveAndAnother(): void;
+ getValidation(): RegExp;
+ validateIntRange(value:string):boolean;
+ close(): void;
+ onValueChange(): void;
+ onTypeChange(resetSchema:boolean): void;
+ showSchema(): boolean;
+ getValidationTranslate():string;
+ validateUniqueKeys(viewValue:string):boolean;
+ }
+
+ export class PropertyFormViewModel{
+
+ private originalValue: string;
+
+ static '$inject' = [
+ '$scope',
+ '$modalInstance',
+ 'property',
+ 'ValidationPattern',
+ 'PropertyNameValidationPattern',
+ 'IntegerNoLeadingZeroValidationPattern',
+ 'FloatValidationPattern',
+ 'CommentValidationPattern',
+ 'ValidationUtils',
+ 'component'
+ ];
+
+ private formState: Utils.Constants.FormState;
+ private entityId: string;
+ private resourceInstanceUniqueId: string;
+ private readonly: boolean;
+
+ constructor(
+ private $scope:IPropertyFormViewModelScope,
+ private $modalInstance: ng.ui.bootstrap.IModalServiceInstance,
+ private property : Models.PropertyModel,
+ private ValidationPattern : RegExp,
+ private PropertyNameValidationPattern: RegExp,
+ private IntegerNoLeadingZeroValidationPattern : RegExp,
+ private FloatValidationPattern : RegExp,
+ private CommentValidationPattern: RegExp,
+ private ValidationUtils: Sdc.Utils.ValidationUtils,
+ private component:Models.Components.Component
+ ){
+ this.entityId = this.component.uniqueId;
+ this.formState = angular.isDefined(property.name) ? Utils.Constants.FormState.UPDATE : Utils.Constants.FormState.CREATE;
+ this.initScope();
+ }
+
+
+ private initResource = (): void => {
+ this.$scope.editPropertyModel.property = new Sdc.Models.PropertyModel(this.property);
+ this.originalValue = this.property.defaultValue;
+ if(this.$scope.editPropertyModel.types.indexOf(this.property.type) === -1 && !this.$scope.isNew){
+ this.property.type = "string";
+ }
+ };
+
+ private initEditPropertyModel = (): void => {
+ this.$scope.editPropertyModel = {
+ property: null,
+ types: ['integer', 'string', 'float', 'boolean', 'list', 'map'],
+ simpleTypes: ['integer', 'string', 'float', 'boolean'],
+ sources: ['A&AI', 'Order', 'Runtime']
+ };
+
+ this.initResource();
+ };
+
+ private initScope = (): void => {
+
+ this.$scope.modalInstanceProperty = this.$modalInstance;
+ //scope properties
+ this.$scope.validationPattern = this.ValidationPattern;
+ this.$scope.propertyNameValidationPattern = this.PropertyNameValidationPattern;
+ this.$scope.integerValidationPattern = this.IntegerNoLeadingZeroValidationPattern;
+ this.$scope.floatValidationPattern = this.FloatValidationPattern;
+ this.$scope.commentValidationPattern = this.CommentValidationPattern;
+
+ //map & list validation patterns
+ this.$scope.listRegex = this.ValidationUtils.getPropertyListPatterns();
+ this.$scope.mapRegex = this.ValidationUtils.getPropertyMapPatterns();
+
+ this.$scope.isLoading = false;
+ this.$scope.isNew = (this.formState === Utils.Constants.FormState.CREATE);
+ this.initEditPropertyModel();
+
+ //scope methods
+ this.$scope.save = (): void => {
+ this.$scope.editPropertyModel.property.description = this.ValidationUtils.stripAndSanitize(this.$scope.editPropertyModel.property.description);
+ this.$scope.isLoading = true;
+
+ let onFailed = (response) => {
+ console.info('onFaild',response);
+ this.$scope.isLoading = false;
+ this.$scope.editPropertyModel.property.readonly = this.readonly;
+ this.$scope.editPropertyModel.property.resourceInstanceUniqueId = this.resourceInstanceUniqueId;
+ };
+
+ let onSuccess = (property: Models.PropertyModel): void => {
+ console.info('property added : ',property);
+ this.$scope.isLoading = false;
+ property.resourceInstanceUniqueId = this.resourceInstanceUniqueId;
+ property.readonly = (property.parentUniqueId !== this.component.uniqueId) /*|| this.component.isService()*/;
+
+ this.$modalInstance.close();
+ };
+
+ this.resourceInstanceUniqueId = this.$scope.editPropertyModel.property.resourceInstanceUniqueId;
+ this.readonly = this.$scope.editPropertyModel.property.readonly;
+ this.$scope.editPropertyModel.property.defaultValue = this.$scope.editPropertyModel.property.defaultValue ? this.$scope.editPropertyModel.property.defaultValue:null;
+
+ this.component.addOrUpdateProperty(this.$scope.editPropertyModel.property).then(onSuccess, onFailed);
+ };
+
+ this.$scope.saveAndAnother = (): void => {
+ this.$scope.save();
+ };
+
+ this.$scope.showSchema = () :boolean => {
+ return ['list', 'map'].indexOf(this.$scope.editPropertyModel.property.type) > -1;
+ };
+
+ this.$scope.getValidationTranslate = () : string => {
+ let result = "PROPERTY_EDIT_PATTERN";
+ if (this.$scope.showSchema()) {
+
+ result = "PROPERTY_EDIT_" + this.$scope.editPropertyModel.property.type.toUpperCase();
+
+ if(this.$scope.editPropertyModel.property.schema.property.type === 'string') {
+ result += "_STRING";
+ } else {
+ result += "_GENERIC";
+ }
+ }
+
+ return result;
+ };
+
+ this.$scope.getValidation = () : RegExp => {
+ let type = this.$scope.editPropertyModel.property.type;
+ switch (type){
+ case 'integer':
+ return this.$scope.integerValidationPattern;
+ case 'float':
+ return this.$scope.floatValidationPattern;
+ case 'list':
+ return this.$scope.listRegex[this.$scope.editPropertyModel.property.schema.property.type];
+ case 'map':
+ return this.$scope.mapRegex[this.$scope.editPropertyModel.property.schema.property.type];
+ default :
+ return null;
+ }
+ };
+
+ this.$scope.validateUniqueKeys = (viewValue:string) : boolean => {
+ if(this.$scope.editPropertyModel.property.type === 'map') {
+ return this.ValidationUtils.validateUniqueKeys(viewValue);
+ }
+ else {
+ return true; //always valid if not a map
+ }
+ };
+
+ this.$scope.validateIntRange = (value:string):boolean => {
+ return !value || this.ValidationUtils.validateIntRange(value);
+ };
+
+ this.$scope.close = (): void => {
+ this.$modalInstance.close();
+ };
+
+ this.$scope.onValueChange = (): void => {
+ if(!this.$scope.editPropertyModel.property.defaultValue && this.$scope.editPropertyModel.property.required) {
+ this.$scope.editPropertyModel.property.defaultValue = this.originalValue;
+ }
+ };
+
+ this.$scope.onTypeChange = (resetSchema:boolean): void => {
+ this.$scope.editPropertyModel.property.defaultValue = '';
+ if (resetSchema) {
+ this.$scope.editPropertyModel.property.schema.property.type = '';
+ }
+ };
+
+ //new form layout for import asset
+ this.$scope.forms = {};
+ this.$scope.footerButtons = [
+ {'name': this.$scope.isNew ? 'Add' : 'Update', 'css':'blue', 'callback': this.$scope.save},
+ {'name':'Cancel', 'css':'grey', 'callback': this.$scope.close}
+ ];
+
+ this.$scope.$watch('forms.editForm.$invalid', () => {
+ this.$scope.footerButtons[0].disabled = this.$scope.forms.editForm.$invalid;
+ });
+
+ }
+
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/wizard/property-form/property-form.html b/catalog-ui/app/scripts/view-models/wizard/property-form/property-form.html
new file mode 100644
index 0000000000..be237112a4
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/wizard/property-form/property-form.html
@@ -0,0 +1,133 @@
+<sdc-modal modal="modalInstanceProperty" type="classic" class="sdc-add-property" buttons="footerButtons" header="{{isNew ? 'Add' : 'Update' }} Property" show-close-button="true">
+
+ <form novalidate class="w-sdc-form two-columns" name="forms.editForm" >
+
+ <div class="w-sdc-form-columns-wrapper">
+
+ <div class="w-sdc-form-column">
+
+ <div class="i-sdc-form-item" data-ng-class="{error:(forms.editForm.propertyName.$dirty && forms.editForm.propertyName.$invalid)}">
+ <label class="i-sdc-form-label" ng-class="{'required': !isService}">Name</label>
+ <input class="i-sdc-form-input"
+ data-ng-maxlength="50"
+ data-ng-disabled="!isNew || editPropertyModel.property.readonly"
+ maxlength="50"
+ data-ng-model="editPropertyModel.property.name"
+ type="text"
+ name="propertyName"
+ data-ng-pattern="propertyNameValidationPattern"
+ data-required
+ data-ng-model-options="{ debounce: 200 }"
+ data-tests-id="propertyName"
+ autofocus />
+
+ <div class="input-error" data-ng-show="forms.editForm.propertyName.$dirty && forms.editForm.propertyName.$invalid">
+ <span ng-show="forms.editForm.propertyName.$error.required" translate="VALIDATION_ERROR_REQUIRED" translate-values="{'field': 'Property name' }"></span>
+ <span ng-show="forms.editForm.propertyName.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '128' }"></span>
+ <span ng-show="forms.editForm.propertyName.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span>
+ </div>
+ </div>
+
+
+ <div class="i-sdc-form-item" data-ng-class="{error:(forms.editForm.schemaType.$dirty && forms.editForm.schemaType.$invalid)}"
+ data-ng-if="showSchema()">
+ <label class="i-sdc-form-label required">Entry Schema</label>
+ <select class="i-sdc-form-select"
+ data-required
+ name="schemaType"
+ data-tests-id="schemaType"
+ data-ng-change="onTypeChange(false)"
+ data-ng-model="editPropertyModel.property.schema.property.type"
+ data-ng-options="type for type in editPropertyModel.simpleTypes">
+ <option value="">Choose Schema Type</option>
+ </select>
+
+ <div class="input-error" data-ng-show="forms.editForm.schemaType.$dirty && forms.editForm.schemaType.$invalid">
+ <span ng-show="forms.editForm.schemaType.$error.required" translate="VALIDATION_ERROR_REQUIRED" translate-values="{'field': 'Entry schema' }"></span>
+ </div>
+ </div>
+
+
+ <div class="i-sdc-form-item" data-ng-class="{error:(forms.editForm.value.$dirty && forms.editForm.value.$invalid)}">
+ <label class="i-sdc-form-label">Default Value</label>
+ <input class="i-sdc-form-input"
+ ng-if="!(editPropertyModel.property.type == 'boolean')"
+ data-ng-maxlength="100"
+ data-ng-disableddddddd="editPropertyModel.property.readonly && !isService && !isPropertyValueOwner()"
+ maxlength="100"
+ data-ng-model="editPropertyModel.property.defaultValue"
+ type="text"
+ name="value"
+ data-custom-validation="" data-validation-func="validateUniqueKeys"
+ data-ng-pattern="getValidation()"
+ data-ng-model-options="{ debounce: 200 }"
+ data-ng-change="!forms.editForm.value.$error.pattern && ('integer'==editPropertyModel.property.type && forms.editForm.value.$setValidity('pattern', validateIntRange(editPropertyModel.property.defaultValue)) || onValueChange())"
+ data-tests-id="defaultValue"
+ autofocus />
+ <select class="i-sdc-form-select"
+ ng-if="editPropertyModel.property.type == 'boolean'"
+ data-ng-disabled="editPropertyModel.property.readonly && !isPropertyValueOwner()"
+ name="value"
+ data-ng-change="onValueChange()"
+ data-ng-model="editPropertyModel.property.defaultValue">
+ <option value=""></option>
+ <option value="true">true</option>
+ <option value="false">false</option>
+ </select>
+
+ <div class="input-error" data-ng-show="forms.editForm.value.$dirty && forms.editForm.value.$invalid">
+ <span ng-show="forms.editForm.value.$error.required" translate="VALIDATION_ERROR_REQUIRED" translate-values="{'field': 'Property' }"></span>
+ <span ng-show="forms.editForm.value.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '100' }"></span>
+ <span ng-show="forms.editForm.value.$error.pattern" translate="{{getValidationTranslate()}}"></span>
+ <span ng-show="forms.editForm.value.$error.customValidation" translate="PROPERTY_EDIT_MAP_UNIQUE_KEYS"></span>
+ </div>
+ </div>
+
+ </div>
+
+ <div class="w-sdc-form-column">
+
+ <div class="i-sdc-form-item" data-ng-class="{error:(forms.editForm.type.$dirty && forms.editForm.type.$invalid)}">
+ <label class="i-sdc-form-label" ng-class="{'required': !isService}">Type</label>
+ <select class="i-sdc-form-select"
+ data-required
+ data-ng-disableddddddd="editPropertyModel.property.readonly"
+ data-tests-id="propertyType"
+ name="type"
+ data-ng-change="onTypeChange(true)"
+ data-ng-model="editPropertyModel.property.type"
+ data-ng-options="type for type in editPropertyModel.types">
+ <option value="">Choose Type</option>
+ </select>
+
+ <div class="input-error" data-ng-show="forms.editForm.type.$dirty && forms.editForm.type.$invalid">
+ <span ng-show="forms.editForm.type.$error.required" translate="VALIDATION_ERROR_REQUIRED" translate-values="{'field': 'Type' }"></span>
+ </div>
+ </div>
+
+ <div class="i-sdc-form-item" data-ng-class="{error:(forms.editForm.description.$dirty && forms.editForm.description.$invalid)}">
+ <label class="i-sdc-form-label">Description</label>
+ <textarea class="i-sdc-form-textarea"
+ data-ng-maxlength="256"
+ data-ng-disableddddddd="editPropertyModel.property.readonly"
+ maxlength="256"
+ data-ng-pattern="commentValidationPattern"
+ name="description"
+ data-ng-model="editPropertyModel.property.description"
+ data-ng-model-options="{ debounce: 200 }"
+ data-tests-id="description"
+ ></textarea>
+
+ <div class="input-error" data-ng-show="forms.editForm.description.$dirty && forms.editForm.description.$invalid">
+ <span ng-show="forms.editForm.description.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '256' }"></span>
+ <span ng-show="forms.editForm.description.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span>
+ <span ng-show="forms.editForm.description.$error.required" translate="VALIDATION_ERROR_REQUIRED" translate-values="{'field': 'Description' }"></span>
+ </div>
+ </div>
+
+ </div>
+
+ </div>
+ </form>
+
+</sdc-modal>
diff --git a/catalog-ui/app/scripts/view-models/wizard/property-form/property-form.less b/catalog-ui/app/scripts/view-models/wizard/property-form/property-form.less
new file mode 100644
index 0000000000..52b8564fdb
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/wizard/property-form/property-form.less
@@ -0,0 +1,7 @@
+.sdc-add-property{
+
+ .w-sdc-form {
+
+ }
+
+}
diff --git a/catalog-ui/app/scripts/view-models/wizard/wizard-creation-base.html b/catalog-ui/app/scripts/view-models/wizard/wizard-creation-base.html
new file mode 100644
index 0000000000..afa9307265
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/wizard/wizard-creation-base.html
@@ -0,0 +1,12 @@
+<loader data-display="isLoading"></loader>
+<div class="sdc-wizard-name-type-label" data-ng-if="assetName!==undefined"><span class="sprite sprite-green-tick animated flash"></span><span class="name">{{assetName}}</span>&nbsp;|&nbsp;<span class="type">{{assetType}}</span></div>
+<sdc-modal modal="modalInstance" type="classic" get-close-modal-response="getComponent" buttons="footerButtons" class="sdc-wizard" header="{{modalTitle}}" show-close-button="true">
+ <div class="sdc-wizard-wrapper">
+ <div class="sdc-wizard-left-content">
+ <sdc-wizard-step steps="directiveSteps" control="assetCreationControl" class="wizard-steps-line"></sdc-wizard-step>
+ </div>
+ <div class="sdc-wizard-right-content">
+ <ng-include src="templateUrl" class="sdc-wizard-right-include"></ng-include>
+ </div>
+ </div>
+</sdc-modal>
diff --git a/catalog-ui/app/scripts/view-models/wizard/wizard-creation-base.less b/catalog-ui/app/scripts/view-models/wizard/wizard-creation-base.less
new file mode 100644
index 0000000000..591186789b
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/wizard/wizard-creation-base.less
@@ -0,0 +1,60 @@
+.sdc-wizard-wrapper {
+ display: flex;
+ padding: 80px 0;
+ height: 620px;
+ // So input validation error will be shown outside the modal (long messages).
+ // overflow: hidden;
+
+ .sdc-wizard-left-content {
+ width: 400px;
+ white-space: nowrap;
+
+ .wizard-steps-line {
+ padding: 0 80px 0 0;
+ display: block;
+ }
+
+ }
+
+ .sdc-wizard-right-content {
+ margin-right: 200px;
+ /* background-color: #fafafa; */
+ height: 100%;
+ width: 100%;
+ .perfect-scrollbar;
+ overflow: visible;
+ //.animation-duration(2s);
+
+ .sdc-wizard-right-include {
+
+ }
+ }
+
+}
+
+.sprite-green-tick.animated {
+ .animation-duration(3s);
+}
+
+.sdc-wizard-name-type-label {
+ position: absolute;
+ top: 64px;
+ right: 40px;
+
+ span {
+ .b_7;
+ &.name {.bold;}
+ &.sprite-green-tick {
+ position: absolute;
+ left: -22px;
+ top: 5px;
+ }
+ }
+
+}
+
+.w-wizard-footer {
+ button.cancel {
+ margin-right: 120px;
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/wizard/wizard-creation-base.ts b/catalog-ui/app/scripts/view-models/wizard/wizard-creation-base.ts
new file mode 100644
index 0000000000..365d3aedf6
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/wizard/wizard-creation-base.ts
@@ -0,0 +1,399 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../references"/>
+module Sdc.ViewModels.Wizard {
+ 'use strict';
+
+ export class StepNames {
+ static general = "General";
+ static icon = "Icon";
+ static deploymentArtifact = "Deployment Artifact";
+ static informationArtifact = "Information Artifact";
+ static properties = "Properties";
+ static hierarchy = "Hierarchy";
+ }
+
+ class FooterButtons {
+ static cancel = "Cancel";
+ static back = "Back";
+ static next = "Next";
+ static finish = "Finish";
+ }
+
+ export class WizardCreationTypes {
+ static importAsset = "importAsset";
+ static create = "create";
+ static edit = "edit";
+ }
+
+ export class _CreationStep {
+ name:string;
+ url:string;
+ }
+
+ export class CurrentStep {
+ assetCreationStep:_CreationStep;
+ reference:IWizardCreationStep;
+ index:number;
+ valid: boolean;
+ }
+
+ export interface IWizardCreationStep {
+ save(callback:Function):void;
+ back(callback:Function):void;
+ }
+
+ export interface IWizardCreationStepScope extends ng.IScope {
+ data:any;
+ getComponent():Sdc.Models.Components.Component;
+ setComponent(component: Sdc.Models.Components.Component):void;
+ registerChild(child:IWizardCreationStep):void; // Called from the step
+ setValidState(valid:boolean):void; // Called from the step
+ }
+
+ export interface IWizardCreationScope extends ng.IScope {
+ isLoading: boolean;
+ data:any; // data passed from dashboard (opener), need it on the scope, because general step will use this (extends the scope)
+ directiveSteps: Array<Sdc.Directives.IWizardStep>; // Steps for the directive, on the scope (on the scope because need to pass to directive via HTML)
+ templateUrl: string; // On the scope because need to pass to <ng-include> via HTML
+ footerButtons: Array<Sdc.Directives.ISdcModalButton>; // Wizard footer buttons (on the scope because need to pass to directive via HTML)
+ assetCreationControl:any; // Link to wizard directive functions.
+ modalInstance:ng.ui.bootstrap.IModalServiceInstance; // Reference to the modal, so we can close it (on the scope because need to pass to directive via HTML)
+ assetName:string;
+ assetType:string;
+ modalTitle:string;
+ getComponent():Sdc.Models.Components.Component;
+ setComponent(component: Sdc.Models.Components.Component):void;
+ registerChild(child:IWizardCreationStep):void; // Called from the step
+ setValidState(valid:boolean):void; // Called from the step
+ }
+
+ export class WizardCreationBaseViewModel {
+
+ component: Sdc.Models.Components.Component;
+ protected assetCreationSteps: Array<_CreationStep>; // Contains URL and name so we can replace them
+ currentStep:CurrentStep;
+ protected type:string;
+
+ constructor(protected $scope:IWizardCreationScope,
+ protected data:any,
+ protected ComponentFactory: Utils.ComponentFactory,
+ protected $modalInstance: ng.ui.bootstrap.IModalServiceInstance
+ ) {
+
+ this.$scope.data = data;
+ this.currentStep = new CurrentStep();
+ this.currentStep.valid=false;
+ this.$scope.modalInstance = this.$modalInstance;
+ this.initScope();
+ this.noBackspaceNav();
+
+ // In case the modal was opened with filled resource (edit mode).
+ if (data.component){
+ this.$scope.setComponent(data.component);
+ data.componentType = this.$scope.getComponent().componentType;
+ window.setTimeout(()=>{
+ this.safeApply(this.setCurrentStepByIndex(0, false));
+ },100);
+ } else {
+ // Default step to start with
+ window.setTimeout(()=>{
+ this.safeApply(this.setCurrentStepByIndex(0, false));
+ },100);
+ }
+
+ }
+
+ private safeApply = (fn:any) => {
+ let phase = this.$scope.$root.$$phase;
+ if (phase == '$apply' || phase == '$digest') {
+ if (fn && (typeof(fn) === 'function')) {
+ fn();
+ }
+ } else {
+ this.$scope.$apply(fn);
+ }
+ };
+
+ private initScope = ():void => {
+
+ // Control to call functions on wizard step directive
+ this.$scope.assetCreationControl = {};
+
+ // Footer buttons definitions for the modal directive.
+ this.$scope.footerButtons = [
+ {"name":FooterButtons.cancel, "css":'white cancel',"callback": ()=>{this.btnCancelClicked();}},
+ {"name":FooterButtons.back, "disabled":true, "css":'white back',"callback": ()=>{this.btnBackClicked();}},
+ {"name":FooterButtons.next, "disabled":true, "css":'blue next',"callback": ()=>{this.btnNextClicked();}},
+ {"name":FooterButtons.finish, "disabled":true, "css":'white finish',"callback": ()=>{this.btnFinishedClicked();}}
+ ];
+
+ // Will be called from step constructor to register him.
+ // So the current step will be the reference.
+ this.$scope.registerChild=(child:IWizardCreationStep):void => {
+ this.currentStep.reference=child;
+ };
+
+ // Will be called from each step to notify if the step is valid
+ // The wizard will set the "Next", "Finish" buttons accordingly.
+ this.$scope.setValidState = (valid:boolean):void => {
+ this.currentStep.valid=valid;
+ let currentDirectiveStep:Sdc.Directives.IWizardStep = this.$scope.directiveSteps[this.currentStep.index];
+ this.$scope.assetCreationControl.setStepValidity(currentDirectiveStep, valid);
+ this.footerButtonsStateMachine();
+ this.wizardButtonsIconsStateMachine();
+ };
+
+ /**
+ * Will be called from each step to get current entity.
+ * This will return copy of the entity (not reference), because I do not want that the step will change entity parameters.
+ * If the step need to update the entity it can call setComponent function.
+ * @returns {Sdc.Models.IEntity}
+ */
+ this.$scope.getComponent = ():Sdc.Models.Components.Component => {
+ return this.component;
+ };
+
+ // Will be called from each step after save to update the resource.
+ this.$scope.setComponent = (component:Sdc.Models.Components.Component):void => {
+ this.component = component;
+ };
+
+ };
+
+ protected setCurrentStepByName = (stepName:string):boolean => {
+ let stepIndex:number = this.getStepIndex(stepName);
+ return this.setCurrentStepByIndex(stepIndex);
+ };
+
+ // Set the current step, change the URL in ng-include.
+ protected setCurrentStepByIndex = (index:number, doSave:boolean=true):boolean => {
+ let result:boolean = false;
+ if (this.currentStep.index!==index) { // Check that not pressing on same step, also the first time currentStepIndex=undefined
+ if (doSave===true) {
+ this.callStepSave(() => {
+ // This section will be executed only if success save.
+
+ // Set current step in the left wizard directive = valid
+ let currentDirectiveStep:Sdc.Directives.IWizardStep = this.$scope.directiveSteps[this.currentStep.index];
+ this.$scope.assetCreationControl.setStepValidity(currentDirectiveStep, true);
+
+ // Move to next step
+ let step:_CreationStep = this.assetCreationSteps[index];
+ this.currentStep.index = index;
+ this.currentStep.assetCreationStep = step;
+ this.$scope.templateUrl = step.url;
+
+ // Update the next/back buttons and steps buttons.
+ this.footerButtonsStateMachine();
+ this.wizardButtonsIconsStateMachine();
+
+ // Can not perform step click without enabling the step
+ this.$scope.directiveSteps[index].enabled = true; // Need to set the step enabled, before clicking it.
+ this.$scope.assetCreationControl.stepClicked(step.name);
+
+ // After saving the asset name and type will be shown in the top right of the screen.
+ this.fillAssetNameAndType();
+
+ result=true;
+ });
+ } else {
+ // For the first time
+ let step:_CreationStep = this.assetCreationSteps[index];
+ this.currentStep.index = index;
+ this.currentStep.assetCreationStep=step;
+ this.$scope.templateUrl = step.url;
+ this.$scope.directiveSteps[index].enabled = true; // Need to set the step enabled, before clicking it.
+ this.$scope.assetCreationControl.stepClicked(step.name);
+ result=true;
+ }
+
+ //this.updateFooterButtonsStates();
+
+ } else {
+ result=true;
+ }
+ return result;
+ };
+
+ // Save the current step
+ private callStepSave = (successCallback:Function):void => {
+ this.$scope.isLoading = true;
+ this.currentStep.reference.save((result:boolean)=>{
+ this.$scope.isLoading = false;
+ if (result===true){
+ successCallback();
+ } else {
+ // Set the next and finish button enabled.
+ //this.updateFooterButtonsStates(true);
+ }
+ });
+ };
+
+ // Save the current step
+ private callStepBack = (successCallback:Function):void => {
+ this.$scope.isLoading = true;
+ this.currentStep.reference.back((result:boolean)=>{
+ this.$scope.isLoading = false;
+ if (result===true){
+ successCallback();
+ } else {
+ // Set the next and finish button enabled.
+ //this.updateFooterButtonsStates(true);
+ }
+ });
+ };
+
+ private getStepIndex = (stepName:string):number => {
+ let index:number=-1;
+ let tmp = _.find(this.assetCreationSteps, function (item, indx) {
+ index = indx;
+ return item.name === stepName;
+ });
+ return index;
+ };
+
+ private btnNextClicked = ():void => {
+ if (this.hasNext()===true) {
+ let tmp = this.currentStep.index+1;
+ this.setCurrentStepByIndex(tmp);
+ }
+ };
+
+ private btnBackClicked = ():void => {
+ if (this.hasBack()===true) {
+ this.callStepBack(() => {
+ let tmp = this.currentStep.index-1;
+ this.setCurrentStepByIndex(tmp, false);
+ });
+ }
+ };
+
+ private btnCancelClicked = ():void => {
+ this.$modalInstance.dismiss(this.$scope.getComponent());
+ };
+
+ private btnFinishedClicked = ():void => {
+ this.callStepSave(() => {
+ this.$modalInstance.close(this.$scope.getComponent());
+ });
+ };
+
+ // Check if we can move next
+ private hasNext = ():boolean => {
+ if (this.assetCreationSteps.length-1>this.currentStep.index){
+ return true;
+ } else {
+ return false;
+ }
+ };
+
+ // Check if we can move back
+ private hasBack = ():boolean => {
+ if (this.currentStep.index===0){
+ return false;
+ } else {
+ return true;
+ }
+ };
+
+ private fillAssetNameAndType=():void => {
+ this.$scope.assetName = this.$scope.getComponent().name;
+ this.$scope.assetType = this.$scope.getComponent().getComponentSubType();
+
+ };
+
+ protected enableAllWizardSteps=():void => {
+ this.$scope.directiveSteps.forEach((step:Sdc.Directives.IWizardStep) => {
+ step.enabled=true;
+ });
+ };
+
+ protected disableAllWizardSteps=():void => {
+ this.$scope.directiveSteps.forEach((step:Sdc.Directives.IWizardStep) => {
+ step.enabled=false;
+ });
+ };
+
+ private footerButtonsStateMachine = ():void => {
+ //console.log("footerButtonsStateMachine, current step validity: " + this.currentStep.valid);
+ let stepIndex:number = this.currentStep.index;
+ let cancelButton = this.$scope.footerButtons[0];
+ let backButton = this.$scope.footerButtons[1];
+ let nextButton = this.$scope.footerButtons[2];
+ let finishButton = this.$scope.footerButtons[3];
+
+ // NEXT button
+ // Disable next button if it is the last step, and if not check the validity of the step.
+ if (this.hasNext()){
+ nextButton.disabled = !this.currentStep.valid;
+ } else {
+ nextButton.disabled = true;
+ }
+
+ // BACK button
+ backButton.disabled = !this.hasBack();
+
+ // FINISH button
+ // If step 2 is valid show the finish button.
+ if (stepIndex>=1 && this.currentStep.valid===true) {
+ finishButton.disabled = false;
+ }
+ if (this.currentStep.valid===false){
+ finishButton.disabled = true;
+ }
+
+ // EDIT
+ if (this.type===WizardCreationTypes.edit && this.currentStep.valid===true){
+ finishButton.disabled = false;
+ }
+
+ };
+
+
+
+ private wizardButtonsIconsStateMachine = ():void => {
+
+ // Enable or disable wizard directive next step, in case the current step form is valid or not.
+ let stepIndex:number = this.currentStep.index;
+ if (this.$scope.directiveSteps[stepIndex + 1]) {
+ this.$scope.directiveSteps[stepIndex + 1].enabled = this.currentStep.valid;
+ }
+
+ // In case step 1 and 2 are valid, we can open all other steps.
+ if (this.$scope.directiveSteps[0].valid===true && this.$scope.directiveSteps[1].valid===true){
+ // Enable all wizard directive steps
+ this.enableAllWizardSteps();
+ } else if (this.currentStep.valid===false) {
+ // Disable all steps
+ this.disableAllWizardSteps();
+ }
+ };
+
+ private noBackspaceNav:Function = ():void => {
+ this.$scope.$on('$locationChangeStart', (event, newUrl, oldUrl):void =>{
+ event.preventDefault();
+ })
+ };
+
+
+ }
+
+}
diff --git a/catalog-ui/app/scripts/view-models/wizard/wizard-state/create-wizard.ts b/catalog-ui/app/scripts/view-models/wizard/wizard-state/create-wizard.ts
new file mode 100644
index 0000000000..9490cddfdb
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/wizard/wizard-state/create-wizard.ts
@@ -0,0 +1,114 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/**
+ * Created by obarda on 3/15/2016.
+ */
+/// <reference path="../../../references"/>
+module Sdc.ViewModels.Wizard {
+ 'use strict';
+
+ export class CreateWizardViewModel extends WizardCreationBaseViewModel {
+
+ static '$inject' = [
+ '$scope',
+ 'data',
+ 'ComponentFactory',
+ '$modalInstance'
+ ];
+
+ constructor(public $scope:IWizardCreationScope,
+ public data:any,
+ public ComponentFactory: Sdc.Utils.ComponentFactory,
+ public $modalInstance: ng.ui.bootstrap.IModalServiceInstance) {
+
+ super($scope, data, ComponentFactory, $modalInstance);
+ this.type = WizardCreationTypes.create;
+ this.init();
+ this.initCreateAssetScope();
+ }
+
+ private init = ():void => {
+
+ switch (this.data.componentType){
+ case Utils.Constants.ComponentType.RESOURCE: {
+ this.assetCreationSteps = [
+ {"name": StepNames.general, "url": '/app/scripts/view-models/wizard/general-step/general-step.html'},
+ {"name": StepNames.icon, "url": '/app/scripts/view-models/wizard/icons-step/icons-step.html'},
+ {"name": StepNames.deploymentArtifact, "url": '/app/scripts/view-models/wizard/artifact-deployment-step/artifact-deployment-step.html'},
+ {"name": StepNames.informationArtifact, "url": '/app/scripts/view-models/wizard/artifact-information-step/artifact-information-step.html'},
+ {"name": StepNames.properties, "url": '/app/scripts/view-models/wizard/properties-step/properties-step.html'}
+ ];
+ }
+ break;
+
+ case Utils.Constants.ComponentType.SERVICE: {
+ this.assetCreationSteps = [
+ {"name": StepNames.general, "url": '/app/scripts/view-models/wizard/general-step/general-step.html'},
+ {"name": StepNames.icon, "url": '/app/scripts/view-models/wizard/icons-step/icons-step.html'}
+ ];
+ }
+ break;
+
+ case Utils.Constants.ComponentType.PRODUCT: {
+ this.assetCreationSteps = [
+ {"name": StepNames.general, "url": '/app/scripts/view-models/wizard/general-step/general-step.html'},
+ {"name": StepNames.hierarchy, "url": '/app/scripts/view-models/wizard/hierarchy-step/hierarchy-step.html'},
+ {"name": StepNames.icon, "url": '/app/scripts/view-models/wizard/icons-step/icons-step.html'}
+ ];
+ }
+ break;
+ }
+ };
+
+ private initCreateAssetScope = ():void => {
+ switch (this.data.componentType){
+ case Utils.Constants.ComponentType.RESOURCE: {
+ this.$scope.directiveSteps = [
+ {"name": StepNames.general, "enabled": true, "callback": ()=> {return this.setCurrentStepByName(StepNames.general);}},
+ {"name": StepNames.icon, "enabled": false, "callback": ()=> {return this.setCurrentStepByName(StepNames.icon);}},
+ {"name": StepNames.deploymentArtifact, "enabled": false, "callback": ()=> {return this.setCurrentStepByName(StepNames.deploymentArtifact);}},
+ {"name": StepNames.informationArtifact, "enabled": false, "callback": ()=> {return this.setCurrentStepByName(StepNames.informationArtifact);}},
+ {"name": StepNames.properties, "enabled": false, "callback": ()=> {return this.setCurrentStepByName(StepNames.properties);}}
+ ];
+ this.$scope.modalTitle = "Create VF";
+ break;
+ }
+ case Utils.Constants.ComponentType.SERVICE: {
+ this.$scope.directiveSteps = [
+ {"name": StepNames.general, "enabled": true, "callback": ()=> {return this.setCurrentStepByName(StepNames.general);}},
+ {"name": StepNames.icon, "enabled": false, "callback": ()=> {return this.setCurrentStepByName(StepNames.icon);}},
+
+ ];
+ this.$scope.modalTitle = "Create Service";
+ break;
+ }
+ case Utils.Constants.ComponentType.PRODUCT: {
+ this.$scope.directiveSteps = [
+ {"name": StepNames.general, "enabled": true, "callback": ()=> {return this.setCurrentStepByName(StepNames.general);}},
+ {"name": StepNames.hierarchy, "enabled":false, "callback": ()=> {return this.setCurrentStepByName(StepNames.hierarchy);}},
+ {"name": StepNames.icon, "enabled": false, "callback": ()=> {return this.setCurrentStepByName(StepNames.icon);}}
+ ];
+ this.$scope.modalTitle = "Create Product";
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/wizard/wizard-state/edit-wizard.ts b/catalog-ui/app/scripts/view-models/wizard/wizard-state/edit-wizard.ts
new file mode 100644
index 0000000000..353c487e0a
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/wizard/wizard-state/edit-wizard.ts
@@ -0,0 +1,164 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../../references"/>
+module Sdc.ViewModels.Wizard {
+ 'use strict';
+
+ export class EditWizardViewModel extends WizardCreationBaseViewModel {
+
+ static '$inject' = [
+ '$scope',
+ 'data',
+ 'ComponentFactory',
+ '$modalInstance'
+ ];
+
+ constructor(public $scope:IWizardCreationScope,
+ public data:any,
+ public ComponentFactory: Sdc.Utils.ComponentFactory,
+ public $modalInstance: ng.ui.bootstrap.IModalServiceInstance) {
+
+ super($scope, data, ComponentFactory, $modalInstance);
+ this.type = WizardCreationTypes.edit;
+ this.init();
+ this.initCreateAssetScope();
+
+ // Enable all wizard directive steps
+ this.enableAllWizardSteps();
+ }
+
+ private init = ():void => {
+ switch (this.data.component.componentType){
+ case Utils.Constants.ComponentType.RESOURCE: {
+ if(this.data.component.isComplex()) {
+ this.assetCreationSteps = [
+ {
+ "name": StepNames.general,
+ "url": '/app/scripts/view-models/wizard/general-step/general-step.html'
+ },
+ {
+ "name": StepNames.icon,
+ "url": '/app/scripts/view-models/wizard/icons-step/icons-step.html'
+ },
+ {
+ "name": StepNames.deploymentArtifact,
+ "url": '/app/scripts/view-models/wizard/artifact-deployment-step/artifact-deployment-step.html'
+ },
+ {
+ "name": StepNames.informationArtifact,
+ "url": '/app/scripts/view-models/wizard/artifact-information-step/artifact-information-step.html'
+ },
+ {
+ "name": StepNames.properties,
+ "url": '/app/scripts/view-models/wizard/properties-step/properties-step.html'
+ }
+ ];
+ }else{
+ this.assetCreationSteps = [
+ {"name": StepNames.general, "url": '/app/scripts/view-models/wizard/general-step/general-step.html'},
+ {"name": StepNames.icon, "url": '/app/scripts/view-models/wizard/icons-step/icons-step.html'},
+ {"name": StepNames.informationArtifact, "url": '/app/scripts/view-models/wizard/artifact-information-step/artifact-information-step.html'},
+ {"name": StepNames.properties, "url": '/app/scripts/view-models/wizard/properties-step/properties-step.html'}
+ ];
+ }
+ break;
+ }
+ case Utils.Constants.ComponentType.SERVICE: {
+ this.assetCreationSteps = [
+ {"name": StepNames.general, "url": '/app/scripts/view-models/wizard/general-step/general-step.html'},
+ {"name": StepNames.icon, "url": '/app/scripts/view-models/wizard/icons-step/icons-step.html'}
+ ];
+ break;
+ }
+ case Utils.Constants.ComponentType.PRODUCT: {
+ this.assetCreationSteps = [
+ {"name": StepNames.general, "url": '/app/scripts/view-models/wizard/general-step/general-step.html'},
+ {"name": StepNames.hierarchy, "url": '/app/scripts/view-models/wizard/hierarchy-step/hierarchy-step.html'},
+ {"name": StepNames.icon, "url": '/app/scripts/view-models/wizard/icons-step/icons-step.html'}
+ ];
+ break;
+ }
+ }
+ };
+
+ private initCreateAssetScope = ():void => {
+ switch (this.data.component.componentType){
+ case Utils.Constants.ComponentType.RESOURCE: {
+ if(this.data.component.isComplex()) {
+ this.$scope.directiveSteps = [
+ {
+ "name": StepNames.general, "enabled": true, "callback": ()=> {
+ return this.setCurrentStepByName(StepNames.general);
+ }
+ },
+ {
+ "name": StepNames.icon, "enabled": false, "callback": ()=> {
+ return this.setCurrentStepByName(StepNames.icon);
+ }
+ },
+ {
+ "name": StepNames.deploymentArtifact, "enabled": false, "callback": ()=> {
+ return this.setCurrentStepByName(StepNames.deploymentArtifact);
+ }
+ },
+ {
+ "name": StepNames.informationArtifact, "enabled": false, "callback": ()=> {
+ return this.setCurrentStepByName(StepNames.informationArtifact);
+ }
+ },
+ {
+ "name": StepNames.properties, "enabled": false, "callback": ()=> {
+ return this.setCurrentStepByName(StepNames.properties);
+ }
+ }
+ ];
+ }else{
+ this.$scope.directiveSteps = [
+ {"name": StepNames.general, "enabled": true, "callback": ()=> {return this.setCurrentStepByName(StepNames.general);}},
+ {"name": StepNames.icon, "enabled": false, "callback": ()=> {return this.setCurrentStepByName(StepNames.icon);}},
+ {"name": StepNames.informationArtifact, "enabled": false, "callback": ()=> {return this.setCurrentStepByName(StepNames.informationArtifact);}},
+ {"name": StepNames.properties, "enabled": false, "callback": ()=> {return this.setCurrentStepByName(StepNames.properties);}}
+ ];
+ }
+ this.$scope.modalTitle = "Edit " + this.data.component.resourceType;
+ break;
+ }
+ case Utils.Constants.ComponentType.SERVICE: {
+ this.$scope.directiveSteps = [
+ {"name": StepNames.general, "enabled": true, "callback": ()=> {return this.setCurrentStepByName(StepNames.general);}},
+ {"name": StepNames.icon, "enabled": false, "callback": ()=> {return this.setCurrentStepByName(StepNames.icon);}}
+ ];
+ this.$scope.modalTitle = "Edit Service";
+ break;
+ }
+ case Utils.Constants.ComponentType.PRODUCT: {
+ this.$scope.directiveSteps = [
+ {"name": StepNames.general, "enabled": true, "callback": ()=> {return this.setCurrentStepByName(StepNames.general);}},
+ {"name": StepNames.hierarchy, "enabled":false, "callback": ()=> {return this.setCurrentStepByName(StepNames.hierarchy);}},
+ {"name": StepNames.icon, "enabled": false, "callback": ()=> {return this.setCurrentStepByName(StepNames.icon);}}
+ ];
+ this.$scope.modalTitle = "Edit Product";
+ break;
+ }
+ }
+ }
+ }
+}
+
diff --git a/catalog-ui/app/scripts/view-models/wizard/wizard-state/import-wizard.ts b/catalog-ui/app/scripts/view-models/wizard/wizard-state/import-wizard.ts
new file mode 100644
index 0000000000..5fe1bf7e59
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/wizard/wizard-state/import-wizard.ts
@@ -0,0 +1,64 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../../references"/>
+module Sdc.ViewModels.Wizard {
+ 'use strict';
+
+ export class ImportWizardViewModel extends WizardCreationBaseViewModel {
+
+ static '$inject' = [
+ '$scope',
+ 'data',
+ 'ComponentFactory',
+ '$modalInstance'
+ ];
+
+ constructor(public $scope:IWizardCreationScope,
+ public data:any,
+ public ComponentFactory: Sdc.Utils.ComponentFactory,
+ public $modalInstance: ng.ui.bootstrap.IModalServiceInstance) {
+
+ super($scope, data, ComponentFactory, $modalInstance );
+ this.type = WizardCreationTypes.importAsset;
+ this.init();
+ this.initImportAssetScope();
+ }
+
+ private init = ():void => {
+ this.assetCreationSteps = [
+ {"name": StepNames.general, "url": '/app/scripts/view-models/wizard/general-step/general-step.html'},
+ {"name": StepNames.icon, "url": '/app/scripts/view-models/wizard/icons-step/icons-step.html'},
+ {"name": StepNames.informationArtifact, "url": '/app/scripts/view-models/wizard/artifact-information-step/artifact-information-step.html'},
+ {"name": StepNames.properties, "url": '/app/scripts/view-models/wizard/properties-step/properties-step.html'}
+ ];
+ };
+
+ private initImportAssetScope = ():void => {
+ this.$scope.directiveSteps = [
+ {"name": StepNames.general, "enabled": true, "callback": ()=> {return this.setCurrentStepByName(StepNames.general);}},
+ {"name": StepNames.icon, "enabled": false, "callback": ()=> {return this.setCurrentStepByName(StepNames.icon);}},
+ {"name": StepNames.informationArtifact, "enabled": false, "callback": ()=> {return this.setCurrentStepByName(StepNames.informationArtifact);}},
+ {"name": StepNames.properties, "enabled": false, "callback": ()=> {return this.setCurrentStepByName(StepNames.properties);}}
+ ];
+
+ this.$scope.modalTitle = "Import Asset";
+ };
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/activity-log/activity-log.html b/catalog-ui/app/scripts/view-models/workspace/tabs/activity-log/activity-log.html
new file mode 100644
index 0000000000..23c08f6ec6
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/activity-log/activity-log.html
@@ -0,0 +1,85 @@
+<div class="activity-log">
+
+ <div class="title-wrapper">
+ <div class="top-search">
+ <input type="text"
+ class="search-text"
+ placeholder="Search"
+ data-ng-model="searchBind"
+ data-tests-id="main-menu-input-search"
+ ng-model-options="{ debounce: 500 }" />
+ <span class="w-sdc-search-icon magnification"></span>
+ </div>
+ </div>
+
+ <div class="table-container-flex">
+ <div class="table" data-ng-class="{'view-mode': isViewMode()}">
+
+ <!-- Table headers -->
+ <div class="head flex-container">
+ <div class="table-header head-row hand flex-item" ng-repeat="header in tableHeadersList track by $index" data-ng-click="sort(header.property)">{{header.title}}
+ <span data-ng-show="sortBy === header.property" class="table-header-sort-arrow" data-ng-class="{'down': reverse, 'up':!reverse}"> </span>
+ </div>
+ </div>
+
+ <!-- Table body -->
+ <div class="body">
+ <perfect-scrollbar suppress-scroll-x="true" scroll-y-margin-offset="0" include-padding="true" class="scrollbar-container">
+
+ <!-- In case the logs are empty -->
+ <div data-ng-if="!activityLog || activityLog.length===0" class="no-row-text">
+ There are no logs to display
+ </div>
+
+ <!-- Loop on logs list -->
+ <div data-ng-repeat="item in activityLog | filter: searchBind | orderBy:sortBy:reverse track by $index"
+ data-ng-init="item.dateFormat = ( item.TIMESTAMP.replace(' UTC', '') | stringToDateFilter | date: 'MM/dd/yyyy':'UTC')+' | '+(item.TIMESTAMP.replace(' UTC', '') | stringToDateFilter | date: 'shortTime':'UTC' )"
+ class="flex-container data-row"
+ data-ng-class="{'selected': component === selectedComponent}"
+ data-ng-click="doSelectComponent(component);"
+ >
+
+ <!-- Date -->
+ <div class="table-col-general flex-item" sdc-smart-tooltip>
+ {{item.dateFormat}}
+ </div>
+
+ <!-- Action -->
+ <div class="table-col-general flex-item" sdc-smart-tooltip>
+ {{item.ACTION}}
+ </div>
+
+ <!-- Comment -->
+ <div class="table-col-general flex-item" sdc-smart-tooltip>
+ {{item.COMMENT}}
+ </div>
+
+ <!-- Username -->
+ <div class="table-col-general flex-item" sdc-smart-tooltip>
+ {{item.MODIFIER}}
+ </div>
+
+ <!-- Status -->
+ <div class="table-col-general flex-item" sdc-smart-tooltip>
+ {{item.STATUS}}
+ <span data-ng-class="{'success': item.STATUS>='200' && item.STATUS<='204','error': item.STATUS<'200' || item.STATUS>='300'}"></span>
+ </div>
+
+ </div>
+
+ </perfect-scrollbar>
+ </div><!-- End table body -->
+ </div><!-- End table -->
+ </div><!-- End table-container-flex -->
+
+</div>
+
+
+
+<!--<div ng-repeat="activityDate in activityDateArray " class="w-sdc-component-viewer-right-activity-log" >
+ <div class="w-sdc-component-viewer-right-activity-log-date" >{{activityDate | date: 'longDate'}}</div>
+ <div ng-repeat="activity in activityLog[activityDate] | orderBy: '-TIMESTAMP'">
+ <div class="w-sdc-component-viewer-right-activity-log-time">{{activity.TIMESTAMP.replace(" UTC", '') | stringToDateFilter | date: 'mediumTime':'UTC'}}</div>
+ <div class="w-sdc-component-viewer-right-activity-log-content">{{"Action: " + parseAction(activity.ACTION) + " Performed by: " + activity.MODIFIER + " Status: " + activity.STATUS}}</div>
+ </div>
+</div>-->
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/activity-log/activity-log.less b/catalog-ui/app/scripts/view-models/workspace/tabs/activity-log/activity-log.less
new file mode 100644
index 0000000000..61bb3e9f01
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/activity-log/activity-log.less
@@ -0,0 +1,83 @@
+.activity-log {
+
+ margin-top: 30px;
+
+ .title-wrapper {
+ display: flex;
+ justify-content: flex-end;
+ }
+
+ .table-container-flex .table .body .scrollbar-container {
+ max-height: 448px;
+ }
+
+ .view-mode {
+ background-color: @main_color_p;
+ }
+
+ .table{
+ height: 490px;
+ margin-bottom: 0;
+ }
+
+ .table-container-flex {
+ margin-top: 10px;
+
+ .flex-item:nth-child(1) { width: 200px; }
+ .flex-item:nth-child(2) { flex-grow: 20; }
+ .flex-item:nth-child(3) { flex-grow: 30; }
+ .flex-item:nth-child(4) { flex-grow: 20; }
+ .flex-item:nth-child(5) { width: 80px; }
+
+ .success {
+ position: absolute;
+ top: 11px;
+ right: 20px;
+ .sprite-new;
+ .sdc-success;
+ }
+
+ .error {
+ position: absolute;
+ top: 11px;
+ right: 20px;
+ .sprite-new;
+ .sdc-error;
+ }
+
+ }
+
+ .data-row {
+ position: relative;
+ }
+
+ .top-search {
+ float: right;
+ position: relative;
+
+ input.search-text {
+ .border-radius(2px);
+ width: 245px;
+ height: 32px;
+ line-height: 32px;
+ border: 1px solid @main_color_o;
+ margin: 0;
+ outline: none;
+ text-indent: 10px;
+
+ &::-webkit-input-placeholder { font-style: italic; } /* Safari, Chrome and Opera */
+ &:-moz-placeholder { font-style: italic; } /* Firefox 18- */
+ &::-moz-placeholder { font-style: italic; } /* Firefox 19+ */
+ &:-ms-input-placeholder { font-style: italic; } /* IE 10+ */
+ &:-ms-input-placeholder { font-style: italic; } /* Edge */
+ }
+
+ .magnification {
+ position: absolute;
+ top: 10px;
+ right: 10px;
+ }
+
+ }
+
+}
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/activity-log/activity-log.ts b/catalog-ui/app/scripts/view-models/workspace/tabs/activity-log/activity-log.ts
new file mode 100644
index 0000000000..665d0c0ef6
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/activity-log/activity-log.ts
@@ -0,0 +1,122 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../../../references"/>
+module Sdc.ViewModels {
+ 'use strict';
+
+ export interface IActivityLogViewModelScope extends IWorkspaceViewModelScope {
+ activityDateArray: Array<any>; //this is in order to sort the dates
+ activityLog: Array<Models.Activity>;
+ preVersion:string;
+
+ tableHeadersList: Array<any>;
+ reverse: boolean;
+ sortBy:string;
+ searchBind:string;
+
+ getActivityLog(uniqueId:string):void;
+ onVersionChanged(version:any) : void;
+ parseAction(action:string):string;
+ sort(sortBy:string): void;
+ }
+
+ export class ActivityLogViewModel {
+
+ static '$inject' = [
+ '$scope',
+ '$state',
+ 'Sdc.Services.ActivityLogService'
+ ];
+
+ constructor(private $scope:IActivityLogViewModelScope,
+ private $state:ng.ui.IStateService,
+ private activityLogService:Services.ActivityLogService
+ ) {
+
+ this.initScope();
+ this.$scope.setValidState(true);
+ this.initSortedTableScope();
+ this.$scope.updateSelectedMenuItem();
+
+ // Set default sorting
+ this.$scope.sortBy = 'logDate';
+ }
+
+ private initScope():void {
+
+ this.$scope.preVersion = this.$scope.component.version;
+
+ this.$scope.onVersionChanged = (version:any):void => {
+ if (version.versionNumber != this.$scope.component.version) {
+ this.$scope.isLoading = true;
+ this.$scope.getActivityLog(version.versionId);
+ }
+ };
+
+ this.$scope.getActivityLog = (uniqueId:any):void => {
+
+ let onError = (response) => {
+ this.$scope.isLoading = false;
+ console.info('onFaild', response);
+
+ };
+
+ let onSuccess = (response:Array<Models.Activity>) => {
+ this.$scope.activityLog = _.sortBy(response, function(o) { return o.TIMESTAMP; }); //response; //
+ this.$scope.isLoading = false;
+ };
+
+ this.$scope.isLoading = true;
+ if (this.$scope.component.isResource()) {
+ this.activityLogService.getActivityLogService('resources', uniqueId).then(onSuccess, onError);
+ }
+ if (this.$scope.component.isService()) {
+ this.activityLogService.getActivityLogService('services', uniqueId).then(onSuccess, onError);
+ }
+
+ };
+
+ if (!this.$scope.activityLog || this.$scope.preVersion != this.$scope.component.version) {
+ this.$scope.getActivityLog(this.$scope.component.uniqueId);
+ }
+
+ this.$scope.parseAction = (action:string) => {
+ return action ? action.split(/(?=[A-Z])/).join(' ') : '';
+ };
+
+ }
+
+ private initSortedTableScope = ():void => {
+ this.$scope.tableHeadersList = [
+ {title: 'Date', property: 'logDate'},
+ {title: 'Action', property: 'logAction'},
+ {title: 'Comment', property: 'logComment'},
+ {title: 'Username', property: 'logUsername'},
+ {title: 'Status', property: 'logStatus'}
+ ];
+
+ this.$scope.sort = (sortBy:string):void => {
+ this.$scope.reverse = (this.$scope.sortBy === sortBy) ? !this.$scope.reverse : false;
+ this.$scope.sortBy = sortBy;
+ };
+ };
+
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/attributes/attributes-view-model.ts b/catalog-ui/app/scripts/view-models/workspace/tabs/attributes/attributes-view-model.ts
new file mode 100644
index 0000000000..469da6a2e1
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/attributes/attributes-view-model.ts
@@ -0,0 +1,107 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../../../references"/>
+module Sdc.ViewModels {
+ 'use strict';
+
+ interface IAttributesViewModelScope extends IWorkspaceViewModelScope {
+ tableHeadersList: Array<any>;
+ reverse: boolean;
+ sortBy:string;
+
+ addOrUpdateAttribute(attribute?:Models.AttributeModel): void;
+ delete(attribute:Models.AttributeModel): void;
+ sort(sortBy:string): void;
+ }
+
+ export class AttributesViewModel {
+
+ static '$inject' = [
+ '$scope',
+ '$filter',
+ '$modal',
+ '$templateCache',
+ 'ModalsHandler'
+ ];
+
+
+ constructor(private $scope:IAttributesViewModelScope,
+ private $filter:ng.IFilterService,
+ private $modal:ng.ui.bootstrap.IModalService,
+ private $templateCache:ng.ITemplateCacheService,
+ private ModalsHandler:Utils.ModalsHandler) {
+ this.initScope();
+ this.$scope.updateSelectedMenuItem();
+ }
+
+
+ private openEditAttributeModal = (attribute:Models.AttributeModel):void => {
+ let viewModelsHtmlBasePath:string = '/app/scripts/view-models/';
+
+ let modalOptions:ng.ui.bootstrap.IModalSettings = {
+ template: this.$templateCache.get(viewModelsHtmlBasePath + 'forms/attribute-form/attribute-form-view.html'),
+ controller: 'Sdc.ViewModels.AttributeFormViewModel',
+ size: 'sdc-md',
+ backdrop: 'static',
+ keyboard: false,
+ resolve: {
+ attribute: ():Models.AttributeModel => {
+ return attribute;
+ },
+ component: ():Models.Components.Component => {
+ return <Models.Components.Component> this.$scope.component;
+ }
+ }
+ };
+ this.$modal.open(modalOptions);
+ };
+
+ private initScope = ():void => {
+
+ //let self = this;
+ this.$scope.sortBy = 'name';
+ this.$scope.reverse = false;
+ this.$scope.setValidState(true);
+ this.$scope.tableHeadersList = [
+ {title: 'Name', property: 'name'},
+ {title: 'Type', property: 'type'},
+ {title: 'Default Value', property: 'defaultValue'}
+ ];
+ this.$scope.sort = (sortBy:string):void => {
+ this.$scope.reverse = (this.$scope.sortBy === sortBy) ? !this.$scope.reverse : false;
+ this.$scope.sortBy = sortBy;
+ };
+
+ this.$scope.addOrUpdateAttribute = (attribute?:Models.AttributeModel):void => {
+ this.openEditAttributeModal(attribute ? attribute : new Models.AttributeModel());
+ };
+
+ this.$scope.delete = (attribute:Models.AttributeModel):void => {
+
+ let onOk = ():void => {
+ this.$scope.component.deleteAttribute(attribute.uniqueId);
+ };
+ let title:string = this.$filter('translate')("ATTRIBUTE_VIEW_DELETE_MODAL_TITLE");
+ let message:string = this.$filter('translate')("ATTRIBUTE_VIEW_DELETE_MODAL_TEXT", "{'name': '" + attribute.name + "'}");
+ this.ModalsHandler.openConfirmationModal(title, message, false).then(onOk);
+ };
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/attributes/attributes-view.html b/catalog-ui/app/scripts/view-models/workspace/tabs/attributes/attributes-view.html
new file mode 100644
index 0000000000..59ba933a0a
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/attributes/attributes-view.html
@@ -0,0 +1,52 @@
+<div class="workspace-attributes">
+ <div class="add-btn" data-tests-id="add-attribute-button" ng-if="!isViewMode()"
+ data-ng-class="{'disabled': isDisableMode()}" data-ng-click="addOrUpdateAttribute()" data-tests-id="add-attribute-button">Add</div>
+ <div class="table-container-flex">
+ <div class="table" data-ng-class="{'view-mode': isViewMode()}">
+ <div class="head flex-container">
+ <div class="table-header head-row hand flex-item" data-ng-repeat="header in tableHeadersList track by $index" data-ng-click="sort(header.property)">{{header.title}}
+ <span data-ng-if="sortBy === header.property" class="table-header-sort-arrow" data-ng-class="{'down': reverse, 'up':!reverse}"> </span>
+ </div>
+ <div class="table-no-text-header head-row flex-item" ng-if="!isViewMode()"></div>
+ <!--div class="table-no-text-header head-row flex-item"></div-->
+ </div>
+
+ <div class="body">
+ <perfect-scrollbar scroll-y-margin-offset="0" include-padding="true" class="scrollbar-container">
+ <div data-ng-if="component.attributes.length === 0" class="no-row-text" data-ng-class="{'disabled': isDisableMode()}">
+ There are no attributes to display <br>
+ <span ng-if="!isViewMode()"> click <a data-ng-click="addOrUpdateAttribute()">here</a> to add one </span>
+
+ </div>
+ <div data-ng-repeat-start="attribute in component.attributes | orderBy:sortBy:reverse track by $index"
+ class="flex-container data-row" data-ng-class="{'selected': attribute.selected}"
+ data-ng-click="attribute.selected = !attribute.selected" data-tests-id="attributes-table-row">
+
+ <div class="table-col-general flex-item text">
+ <span class="sprite table-arrow" data-ng-class="{'opened': attribute.selected}"></span>
+ <span data-tests-id="{{attribute.name}}" tooltips tooltip-content="{{attribute.name}}">{{attribute.name}}</span>
+
+ </div>
+
+ <div class="table-col-general flex-item text" data-tests-id="{{attribute.type}}" data-ng-bind="attribute.type"></div>
+
+ <div class="table-col-general flex-item text">
+ <span tooltips tooltip-content="{{attribute.defaultValue}}" data-tests-id="{{attribute.defaultValue}}" data-ng-bind="attribute.defaultValue"></span>
+ </div>
+
+ <div class="table-btn-col flex-item" ng-if="!isViewMode()">
+ <button class="table-edit-btn" data-tests-id="edit_{{attribute.name}}" data-ng-show="attribute.parentUniqueId==component.uniqueId"
+ data-ng-click="addOrUpdateAttribute(attribute); $event.stopPropagation();" data-ng-class="{'disabled': isViewMode()}"> </button>
+ <button class="table-delete-btn" data-tests-id="delete_{{attribute.name}}" data-ng-show="attribute.parentUniqueId==component.uniqueId"
+ data-ng-click="delete(attribute); $event.stopPropagation();" data-ng-class="{'disabled': isViewMode()}"> </button>
+ </div>
+ </div>
+ <div data-ng-repeat-end="" data-ng-if="attribute.selected && attribute.description" class="item-opened" data-ng-bind="attribute.description">
+ </div>
+ </perfect-scrollbar>
+ </div>
+
+ </div>
+ </div>
+
+</div>
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/attributes/attributes.less b/catalog-ui/app/scripts/view-models/workspace/tabs/attributes/attributes.less
new file mode 100644
index 0000000000..ffd28afce4
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/attributes/attributes.less
@@ -0,0 +1,54 @@
+.workspace-attributes {
+
+ width: 93%;
+ display: inline-block;
+ .w-sdc-classic-btn {
+ float: right;
+ margin-bottom: 10px;
+ }
+
+ .table{
+ height:490px;
+ margin-bottom: 0;
+ }
+
+ .table-container-flex {
+ margin-top: 27px;
+
+ .text{
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: inline-block;
+ white-space: nowrap;
+ }
+
+ .flex-item:nth-child(1) {
+ flex-grow: 15;
+
+ .hand;
+ span.table-arrow {
+ margin-right: 7px;
+ }
+ }
+
+ .flex-item:nth-child(2) {
+ flex-grow: 6;
+ }
+
+ .flex-item:nth-child(3) {
+ flex-grow: 9;
+ }
+
+ .flex-item:nth-child(4) {
+ flex-grow: 3;
+ padding-top: 10px;
+ }
+
+ .flex-item:nth-child(5) {
+ flex-grow: 1;
+
+ }
+
+ }
+
+}
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/composition/composition-view-model.ts b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/composition-view-model.ts
new file mode 100644
index 0000000000..f8eeaf7f64
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/composition-view-model.ts
@@ -0,0 +1,232 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+/// <reference path="../../../../references"/>
+module Sdc.ViewModels {
+ 'use strict';
+
+ export interface ICompositionViewModelScope extends IWorkspaceViewModelScope {
+
+ currentComponent: Models.Components.Component;
+ selectedComponent: Models.Components.Component;
+ isLoading: boolean;
+ graphApi:any;
+ sharingService:Sdc.Services.SharingService;
+ sdcMenu:Models.IAppMenu;
+ version:string;
+ isViewOnly:boolean;
+ isLoadingRightPanel:boolean;
+ setComponent(component: Models.Components.Component);
+ isComponentInstanceSelected():boolean;
+ updateSelectedComponent(): void
+ openUpdateModal();
+ deleteSelectedComponentInstance():void;
+ onBackgroundClick():void;
+ setSelectedInstance(componentInstance: Models.ComponentsInstances.ComponentInstance): void;
+ printScreen():void;
+
+ cacheComponentsInstancesFullData: Models.Components.Component;
+ }
+
+ export class CompositionViewModel {
+
+ static '$inject' = [
+ '$scope',
+ '$log',
+ 'sdcMenu',
+ 'MenuHandler',
+ '$modal',
+ '$templateCache',
+ '$state',
+ 'Sdc.Services.SharingService',
+ '$filter',
+ 'Sdc.Services.CacheService',
+ 'ComponentFactory',
+ 'ChangeLifecycleStateHandler',
+ 'LeftPaletteLoaderService',
+ 'ModalsHandler',
+ 'EventListenerService'
+ ];
+
+ constructor(private $scope:ICompositionViewModelScope,
+ private $log: ng.ILogService,
+ private sdcMenu:Models.IAppMenu,
+ private MenuHandler: Utils.MenuHandler,
+ private $modal:ng.ui.bootstrap.IModalService,
+ private $templateCache:ng.ITemplateCacheService,
+ private $state:ng.ui.IStateService,
+ private sharingService:Services.SharingService,
+ private $filter:ng.IFilterService,
+ private cacheService:Services.CacheService,
+ private ComponentFactory: Utils.ComponentFactory,
+ private ChangeLifecycleStateHandler: Sdc.Utils.ChangeLifecycleStateHandler,
+ private LeftPaletteLoaderService: Services.Components.LeftPaletteLoaderService,
+ private ModalsHandler: Sdc.Utils.ModalsHandler,
+ private eventListenerService:Services.EventListenerService) {
+
+ this.$scope.setValidState(true);
+ this.initScope();
+ this.$scope.updateSelectedMenuItem();
+ this.registerGraphEvents(this.$scope);
+ }
+ private cacheComponentsInstancesFullData: Array<Models.Components.Component>;
+
+ private initComponent = ():void => {
+
+ this.$scope.currentComponent = this.$scope.component;
+ this.$scope.selectedComponent = this.$scope.currentComponent;
+ this.updateUuidMap();
+ this.$scope.isViewOnly = this.$scope.isViewMode();
+ };
+ private registerGraphEvents = (scope:ICompositionViewModelScope):void => {
+
+ this.eventListenerService.registerObserverCallback(Utils.Constants.GRAPH_EVENTS.ON_NODE_SELECTED, scope.setSelectedInstance);
+ this.eventListenerService.registerObserverCallback(Utils.Constants.GRAPH_EVENTS.ON_GRAPH_BACKGROUND_CLICKED, scope.onBackgroundClick);
+
+ }
+ private openUpdateComponentInstanceNameModal = ():void => {
+
+ let viewModelsHtmlBasePath:string = '/app/scripts/view-models/';
+ let modalOptions:ng.ui.bootstrap.IModalSettings = {
+ template: this.$templateCache.get(viewModelsHtmlBasePath + 'forms/resource-instance-name-form/resource-instance-name-view.html'),
+ controller: 'Sdc.ViewModels.ResourceInstanceNameViewModel',
+ size: 'sdc-sm',
+ backdrop: 'static',
+ resolve: {
+ component: ():Models.Components.Component => {
+ return this.$scope.currentComponent;
+
+ }
+ }
+ };
+
+ let modalInstance:ng.ui.bootstrap.IModalServiceInstance = this.$modal.open(modalOptions);
+ modalInstance.result.then(():void => {
+ this.eventListenerService.notifyObservers(Utils.Constants.GRAPH_EVENTS.ON_COMPONENT_INSTANCE_NAME_CHANGED, this.$scope.currentComponent.selectedInstance);
+ //this.$scope.graphApi.updateNodeName(this.$scope.currentComponent.selectedInstance);
+ });
+ };
+
+ private removeSelectedComponentInstance = ():void => {
+ this.eventListenerService.notifyObservers(Utils.Constants.GRAPH_EVENTS.ON_DELETE_MULTIPLE_COMPONENTS);
+ };
+
+ private updateUuidMap = ():void => {
+ /**
+ * In case user press F5, the page is refreshed and this.sharingService.currentEntity will be undefined,
+ * but after loadService or loadResource this.sharingService.currentEntity will be defined.
+ * Need to update the uuidMap with the new resource or service.
+ */
+ this.sharingService.addUuidValue(this.$scope.currentComponent.uniqueId,this.$scope.currentComponent.uuid);
+ };
+
+ private initScope = ():void => {
+
+ this.$scope.sharingService = this.sharingService;
+ this.$scope.sdcMenu = this.sdcMenu;
+ this.$scope.isLoading = false;
+ this.$scope.isLoadingRightPanel = false;
+ this.$scope.graphApi = {};
+ this.$scope.version = this.cacheService.get('version');
+ this.initComponent();
+
+ this.cacheComponentsInstancesFullData = new Array<Models.Components.Component>();
+
+ this.$scope.isComponentInstanceSelected = ():boolean => {
+ return this.$scope.currentComponent && this.$scope.currentComponent.selectedInstance != undefined && this.$scope.currentComponent.selectedInstance != null;
+ };
+
+ this.$scope.updateSelectedComponent = (): void => {
+ if(this.$scope.currentComponent.selectedInstance){
+
+ let componentParent = _.find(this.cacheComponentsInstancesFullData, (component) => {
+ return component.uniqueId === this.$scope.currentComponent.selectedInstance.componentUid;
+ });
+ if(componentParent) {
+ this.$scope.selectedComponent = componentParent;
+ }
+ else {
+ try {
+ let onSuccess = (component:Models.Components.Component) => {
+ this.$scope.isLoadingRightPanel = false;
+ this.$scope.selectedComponent = component;
+ this.cacheComponentsInstancesFullData.push(component);
+ };
+ let onError = (component:Models.Components.Component) => {
+ console.log("Error updating selected component");
+ this.$scope.isLoadingRightPanel = false;
+ };
+ this.ComponentFactory.getComponentFromServer(this.$scope.currentComponent.selectedInstance.originType, this.$scope.currentComponent.selectedInstance.componentUid).then(onSuccess, onError);
+ } catch(e){
+ console.log("Error updating selected component", e);
+ this.$scope.isLoadingRightPanel = false;
+ }
+ }
+ }
+ else {
+ this.$scope.selectedComponent = this.$scope.currentComponent;
+ }
+ };
+
+ this.$scope.setSelectedInstance = (selectedComponent:Models.ComponentsInstances.ComponentInstance):void => {
+
+ this.$log.debug('composition-view-model::onNodeSelected:: with id: '+ selectedComponent.uniqueId);
+ this.$scope.currentComponent.setSelectedInstance(selectedComponent);
+ this.$scope.updateSelectedComponent();
+
+ if (this.$state.current.name === 'workspace.composition.api') {
+ this.$state.go('workspace.composition.details');
+ }
+ if (this.$state.current.name === 'workspace.composition.relations' && this.$scope.currentComponent.isProduct()) {
+ this.$state.go('workspace.composition.details');
+ }
+ };
+
+ this.$scope.onBackgroundClick = ():void => {
+ this.$scope.currentComponent.selectedInstance = null;
+ this.$scope.selectedComponent = this.$scope.currentComponent;
+
+ if (this.$state.current.name === 'workspace.composition.api') {
+ this.$state.go('workspace.composition.details');
+ }
+ };
+
+ this.$scope.openUpdateModal = ():void => {
+ this.openUpdateComponentInstanceNameModal();
+ };
+
+ this.$scope.deleteSelectedComponentInstance = ():void => {
+ let state = "deleteInstance";
+ let onOk = ():void => {
+ this.removeSelectedComponentInstance();
+ //this.$scope.graphApi.deleteSelectedNodes();
+ };
+ let title:string = this.$scope.sdcMenu.alertMessages[state].title;
+ let message:string = this.$scope.sdcMenu.alertMessages[state].message.format([this.$scope.currentComponent.selectedInstance.name]);
+ this.ModalsHandler.openAlertModal(title, message).then(onOk);
+ };
+
+ this.$scope.setComponent = (component: Models.Components.Product):void => {
+ this.$scope.currentComponent = component;
+ }
+
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/composition/composition-view.html b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/composition-view.html
new file mode 100644
index 0000000000..4efc74c31b
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/composition-view.html
@@ -0,0 +1,99 @@
+<div class="workspace-composition">
+ <loader data-display="isLoading"></loader>
+ <div class="w-sdc-designer-canvas" data-ng-class="{sidebaractive: displayDesignerRightSidebar}" >
+ <palette components="leftPanelComponents"
+ current-component="currentComponent"
+ is-view-only="isViewOnly"
+ is-loading="isLoading"></palette>
+
+ <composition-graph component="currentComponent" is-view-only="isViewOnly"></composition-graph>
+ <!--<graph-creator left-panel-components="leftPanelComponents"-->
+ <!--data-tests-id="canvas"-->
+ <!--on-instance-selected="setSelectedInstance(componentInstance)"-->
+ <!--on-background-click="onBackgroundClick()" current-component="currentComponent"-->
+ <!--api="graphApi" is-view-only="isViewOnly" is-loading="isLoading"></graph-creator>-->
+ </div>
+
+ <div class="w-sdc-designer-sidebar-toggle" data-ng-class="{'active': displayDesignerRightSidebar}"
+ data-ng-init="displayDesignerRightSidebar = true"
+ data-ng-click="displayDesignerRightSidebar = !displayDesignerRightSidebar">
+ <div class="w-sdc-designer-sidebar-toggle-icon sprite-new pointer menu-open-left"></div>
+ </div>
+
+ <div class="w-sdc-designer-sidebar" data-ng-class="{'view-mode':isViewOnly}">
+
+ <div class="w-sdc-designer-sidebar-head" data-tests-id="w-sdc-designer-sidebar-head">
+ <div class="w-sdc-designer-sidebar-logo-ph">
+ <div class="large {{selectedComponent.iconSprite}} {{selectedComponent.icon}}">
+ <div ng-if="isComponentInstanceSelected()"
+ data-ng-class="{'non-certified':'CERTIFIED' !== selectedComponent.lifecycleState, 'smaller-icon': selectedComponent.icon==='vl' || selectedComponent.icon==='cp'}"
+ tooltips tooltip-side="top" tooltip-content="Not certified"></div>
+ </div>
+ </div>
+
+ <div class="w-sdc-designer-sidebar-logo">
+ <span class="w-sdc-designer-sidebar-logo-title" data-tests-id="selectedCompTitle" tooltips
+ tooltip-class="tooltip-custom break-word-tooltip"
+ tooltip-content="&#8203;{{isComponentInstanceSelected() ? currentComponent.selectedInstance.name : currentComponent.name | resourceName}}"
+ data-ng-bind="isComponentInstanceSelected() ? currentComponent.selectedInstance.name : currentComponent.name | resourceName"></span>
+ </div>
+ <div class="sprite e-sdc-small-icon-pencil w-sdc-designer-update-resource-icon"
+ data-ng-if="!isViewOnly && isComponentInstanceSelected()"
+ data-ng-click="openUpdateModal()" id="editPencil"></div>
+
+ <div class="sprite e-sdc-small-icon-delete w-sdc-designer-delete-resource-icon"
+ data-tests-id="e-sdc-small-icon-delete"
+ data-ng-if="!isViewOnly && isComponentInstanceSelected()"
+ data-ng-click="!isLoading && deleteSelectedComponentInstance()" title="Delete Resource Instance"></div>
+ </div>
+
+ <div class="w-sdc-designer-sidebar-tabs">
+ <button class="i-sdc-designer-sidebar-tab" data-ui-sref-active="active"
+ data-ui-sref="workspace.composition.details"
+ tooltips tooltip-class="tooltip-custom tab-tooltip" tooltip-content="Information">
+ <div class="i-sdc-designer-sidebar-tab-icon sprite-new info"></div>
+ </button>
+ <button class="i-sdc-designer-sidebar-tab" data-ui-sref-active="active"
+ ui-sref="workspace.composition.structure"
+ tooltips tooltip-class="tooltip-custom tab-tooltip" tooltip-content="Composition">
+ <div class="i-sdc-designer-sidebar-tab-icon sprite-new structure"></div>
+ </button>
+ <button class="i-sdc-designer-sidebar-tab" data-ui-sref-active="active"
+ data-ui-sref="workspace.composition.deployment"
+ tooltips tooltip-class="tooltip-custom tab-tooltip" tooltip-content="Deployment Artifacts"
+ data-tests-id="deployment-artifact-tab">
+ <div class="i-sdc-designer-sidebar-tab-icon sprite-new deployment-artifacts"></div>
+ </button>
+ <button tooltips tooltip-class="tooltip-custom tab-tooltip"
+ tooltip-content="{{selectedComponent.isResource() ? 'Properties and Attributes': 'Inputs'}}"
+ class="i-sdc-designer-sidebar-tab" data-ui-sref-active="active"
+ data-ui-sref="workspace.composition.properties"
+ data-tests-id="properties-and-attributes-tab">
+ <div class="i-sdc-designer-sidebar-tab-icon sprite-new"
+ ng-class="selectedComponent.isResource() ? 'properties': 'inputs'"></div>
+ </button>
+ <button class="i-sdc-designer-sidebar-tab" data-ui-sref-active="active"
+ data-ui-sref="workspace.composition.artifacts"
+ tooltips tooltip-class="tooltip-custom tab-tooltip" tooltip-content="Information Artifacts">
+ <div class="i-sdc-designer-sidebar-tab-icon sprite-new information-artifacts"></div>
+ </button>
+ <button data-ng-show="!selectedComponent.isService()" class="i-sdc-designer-sidebar-tab"
+ data-ui-sref-active="active" ui-sref="workspace.composition.relations"
+ tooltips tooltip-class="tooltip-custom tab-tooltip tooltip-rightside"
+ tooltip-content="Requirements and Capabilities">
+ <div class="i-sdc-designer-sidebar-tab-icon sprite-new relations"></div>
+ </button>
+ <button data-ng-show="selectedComponent.isService()" class="i-sdc-designer-sidebar-tab"
+ data-ui-sref-active="active" ui-sref="workspace.composition.api" data-tests-id="tab-api"
+ tooltips tooltip-class="tooltip-custom tab-tooltip tooltip-rightside" tooltip-content="API">
+ <div class="i-sdc-designer-sidebar-tab-icon sprite-new api"></div>
+ </button>
+
+ </div>
+
+ <div data-ui-view="" class="w-sdc-designer-sidebar-tab-content-view"></div>
+
+ <loader data-display="isLoadingRightPanel" relative="true" size="medium"></loader>
+
+ </div>
+</div>
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/composition/composition.less b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/composition.less
new file mode 100644
index 0000000000..4c4c0a87a5
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/composition.less
@@ -0,0 +1,864 @@
+
+.composition{
+ .sdc-workspace-container{
+ .w-sdc-main-container{
+ .w-sdc-main-right-container{
+ left:0;
+ .sdc-workspace-top-bar {
+ padding-left: 295px;
+ .not-latest{
+ left: 270px;
+ }
+ }
+ .w-sdc-main-container-body-content{
+ padding: 0 0 0 247px;
+ }
+
+ > div:first-child{
+ padding: 0;
+ }
+ }
+ }
+ }
+}
+
+.workspace-composition {
+ height:100%;
+ display: block;
+ text-align: left;
+ align-items: left;
+ padding: 0;
+
+
+
+ // ---------------------------------------------------------------------------------------------------
+ // Sidebar
+ // ---------------------------------------------------------------------------------------------------
+
+
+
+ .w-sdc-designer-sidebar-toggle {
+ background-color: @main_color_p;
+ border-left: 1px solid @main_color_o;
+ border-bottom: 1px solid @main_color_o;
+ height: 21px;
+ position: absolute;
+ right: 0;
+ top: 53px;
+ width: 17px;
+ transition: right 0.2s;
+ z-index: 10;
+ .box-shadow(-1px 1px 3px 0 @main_color_n);
+
+ &.active {
+ right: 302px;
+ .w-sdc-designer-sidebar-toggle-icon{
+ transform: rotate(180deg);
+ }
+ }
+
+ }
+
+ .w-sdc-designer-sidebar-toggle-icon {
+ margin-left: 6px;
+ margin-top: 6px;
+ }
+
+ .w-sdc-designer-sidebar {
+ background-color:@main_color_p ;
+ .noselect;
+ bottom: 0;
+ position: fixed;
+ right: -302px;
+ width: 302px;
+ top: 102px;
+ transition: right 0.2s;
+ z-index: 9;
+ .box-shadow(-7px -3px 6px -8px @main_color_n);
+
+ }
+
+ .w-sdc-designer-sidebar-toggle.active + .w-sdc-designer-sidebar {
+ right: 0;
+
+ }
+
+ .w-sdc-designer-sidebar-head {
+ padding: 36px 30px 30px 30px;
+ height: 120px;
+ }
+
+ .w-sdc-designer-sidebar-logo-ph {
+ display: inline-block;
+ vertical-align: middle;
+ line-height: 48px;
+ height: 48px;
+ }
+
+ .w-sdc-designer-sidebar-logo {
+ .g_6;
+ display: inline-block;
+ margin-left: 10px;
+ font-weight: 500;
+ }
+
+ .w-sdc-designer-sidebar-logo-title {
+ .s_16_r;
+ .selectable;
+ vertical-align: middle;
+ text-overflow: ellipsis;
+ max-width: 167px;
+ display: inline-block;
+ white-space: nowrap;
+ overflow: hidden;
+ }
+
+ .w-sdc-designer-update-resource-icon {
+ .hand;
+ position: absolute;
+ right: 20px;
+ top: 10px;
+ }
+
+ .w-sdc-designer-delete-resource-icon {
+ .hand;
+ position: absolute;
+ right: 40px;
+ top: 10px;
+ }
+
+ .w-sdc-designer-sidebar-tabs {
+ .bg_e;
+ }
+
+ .w-sdc-designer-sidebar-tabs::after {
+ clear: both;
+ content: '';
+ display: table;
+ }
+
+ .i-sdc-designer-sidebar-tab {
+ background-color: @main_color_p;
+ border: 1px solid @tlv_color_u;;
+ border-left: none;
+ display: inline-block;
+ float: left;
+ height: 36px;
+ padding-top: 9px;
+ text-align: center;
+ width: 50px;
+ .hand;
+
+ &:focus {
+ outline: none;
+ }
+ &.tab-disabled {
+ /* .disabled; */
+ }
+ &.active, &:hover:enabled {
+ background-color: @tlv_color_u;
+ .i-sdc-designer-sidebar-tab-icon {
+ opacity: 1;
+
+
+ }
+
+ }
+
+ div& {
+ padding-top: 0;
+ }
+ /*for tooltip on disabled buttons*/
+ }
+
+ .i-sdc-designer-sidebar-tab-icon {
+ margin-top: 5px ;
+ // opacity: .4;
+ }
+
+ .w-sdc-designer-sidebar-tab-content {
+ .perfect-scrollbar;
+ height: 100%;
+ }
+
+ .w-sdc-designer-sidebar-tab-content-view {
+ position: absolute;
+ top: 156px;
+ bottom: 0px;
+ width: 100%;
+
+ }
+
+ .w-sdc-designer-sidebar-section {
+ }
+
+ .w-sdc-designer-sidebar-section-title {
+ .m_14_m;
+ background-color: @tlv_color_u;
+ .hand;
+ clear: both;
+ height: 32px;
+ line-height: 32px;
+ margin-top: 1px;
+ padding: 0 40px 0 20px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ position: relative;
+ width: 100%;
+ display: block;
+
+ &.expanded {
+ .w-sdc-designer-sidebar-section-title-icon {
+ transform: rotate(180deg);
+ }
+ }
+ }
+
+ .w-sdc-designer-sidebar-section-title-text {
+ max-width: 240px;
+ display: inline-block;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ position: relative;
+ }
+
+ .w-sdc-designer-sidebar-section-title-icon {
+ .hand;
+ .sprite-new;
+ .arrow-up;
+ right: 16px;
+ top: 10px;
+ transition: .3s all;
+ position: absolute;
+ }
+
+ .w-sdc-designer-sidebar-section-content {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+
+ .w-sdc-designer-sidebar-section-title + .w-sdc-designer-sidebar-section-content {
+ margin: 0 auto;
+ }
+
+ .w-sdc-designer-sidebar-section-title.expanded + .w-sdc-designer-sidebar-section-content {
+ margin: 0 auto 1px;
+
+ }
+
+ .i-sdc-designer-sidebar-section-content-item {
+ .b_7;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ //max-width: 250px;
+
+ &.description {
+ margin-top: 28px;
+ white-space: normal;
+ word-wrap: break-word;
+ }
+ }
+
+ .i-sdc-designer-sidebar-section-content-item-tag {
+ .g_7;
+ .bg_c;
+ border-radius: 4px;
+ display: inline-block;
+ line-height: 25px;
+ margin: 0 4px 6px 0;
+ min-width: 50px;
+ padding: 0 9px;
+ text-align: center;
+ max-width: 280px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ .w-sdc-designer-sidebar-section-footer {
+ margin-top: 10px;
+ text-align: center;
+ width: 100%;
+ }
+
+
+
+ .w-sdc-designer-sidebar-section-footer-action {
+ width: 180px;
+ margin-top: 10px;
+ }
+
+ //////////////////////Relationship
+ .w-sdc-designer-sidebar-section-requirements {
+ border-bottom: 1px solid @color_e;
+ margin: 0 13px 20px 13px;
+ padding: 15px 0 0;
+ }
+
+ .w-sdc-designer-sidebar-section-requirements-item {
+ margin-bottom: 20px;
+ }
+
+ .w-sdc-designer-sidebar-section-requirements-label {
+ display: inline-block;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ vertical-align: middle;
+ white-space: nowrap;
+ width: 102px;
+ }
+
+ .w-sdc-designer-sidebar-section-requirements-select {
+ border: 1px solid @color_e;
+ min-height: 30px;
+ padding: 4px 13px;
+ width: 168px;
+ }
+
+ //////////////////////Properties
+ .i-sdc-designer-sidebar-section-content-item-property-and-attribute {
+ .b_7;
+ border-bottom: 1px solid @color_e;
+ min-height: 72px;
+ padding: 15px 10px 10px 18px;
+ position: relative;
+
+ &:first-child {
+ //margin-top: -18px;
+ }
+
+ &:hover {
+ // .bg_c_hover;
+ .bg_c;
+ transition: all .3s;
+
+ .i-sdc-designer-sidebar-section-content-item-button {
+ display: block;
+ }
+ }
+ }
+
+ .i-sdc-designer-sidebar-section-content-item-property-and-attribute-label {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ max-width: 200px;
+ white-space: nowrap;
+ display: inline-block;
+ &:hover {
+ .a_7;
+ }
+ }
+
+ .i-sdc-designer-sidebar-section-content-item-property-value {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ max-width: 200px;
+ display: inline-block;
+ white-space: nowrap;
+
+ }
+
+ .i-sdc-designer-sidebar-section-content-item-property-label-value {
+ }
+
+ .i-sdc-designer-sidebar-section-content-item-button {
+ display: none;
+ position: absolute;
+ top: 25px;
+
+ &.update {
+ background-color: transparent;
+ border: 0;
+ right: 60px;
+ }
+
+ &.delete {
+ background-color: transparent;
+ border: 0;
+ right: 13px;
+ }
+
+ &.download {
+ background-color: transparent;
+ border: 0;
+ right: 35px;
+ }
+
+ &.download-env {
+ background-color: transparent;
+ border: 0;
+ right: 35px;
+ margin-top: 65px;
+ }
+
+ &.attach {
+ background-color: transparent;
+ border: 0;
+ right: 15px;
+ }
+ }
+
+ // ---------------------------------------------------------------------------------------------------
+ // Canvas
+ // ---------------------------------------------------------------------------------------------------
+ .w-sdc-designer-canvas {
+ height:100%;
+ .noselect;
+ .bg_c;
+ bottom: 0;
+ // position: fixed;
+ //right: 0;
+ //left: 240px;
+ //top: 94px;
+ .view-mode{
+ background-color: #f8f8f8;
+ border:0;
+ }
+ }
+
+ .w-sdc-designer-canvas.sidebaractive {
+ //right: 300px;
+ }
+
+ .w-sdc-designer-element {
+ .hand;
+ width: 200px;
+ height: 100px;
+ position: absolute;
+ text-align: center;
+ top: 50%;
+ margin-top: -200px;
+ left: 50%;
+ margin-left: -50px;
+ }
+
+ .w-sdc-designer-resource-label {
+ .q_7;
+ }
+
+ .w-sdc-designer-resource-label-indicator {
+ .bg_q;
+ border-radius: 50%;
+ display: inline-block;
+ height: 10px;
+ margin-right: 6px;
+ vertical-align: middle;
+ width: 10px;
+
+ &.valid {
+ .bg_l;
+ }
+
+ &.invalid {
+ .bg_h;
+ }
+ }
+
+ // ---------------------------------------------------------------------------------------------------
+ // Leftbar
+ // ---------------------------------------------------------------------------------------------------
+ .w-sdc-designer-leftbar {
+ background-color: @main_color_p;
+ bottom: 0;
+ left: 0;
+ overflow-y: scroll;
+ overflow-x: hidden;
+ position: absolute;
+ top: 0;
+ width: 244px;
+ .box-shadow(7px -3px 6px -8px @main_color_n);
+
+ }
+
+ .w-sdc-designer-leftbar-title {
+
+ .p_16_m;
+ background-color: @main_color_n;
+ line-height: 40px;
+ padding: 0 17px;
+ }
+
+ .w-sdc-designer-leftbar-title-count {
+ float: right;
+ }
+
+ .w-scd-diagram-container {
+ // left: 240px;
+ //right: 300px;
+ }
+
+ .w-sdc-designer-leftbar-search {
+ background-color: @tlv_color_u;
+ padding: 10px;
+ white-space: nowrap;
+ position: relative;
+ }
+
+ .w-sdc-designer-leftbar-search-input {
+ border: 1px solid @color_e;
+ .border-radius(4px);
+ height: 30px;
+ margin: 0;
+ padding: 0px 28px 3px 10px;
+ vertical-align: 4px;
+ width: 100%;
+ outline: none;
+ font-style: italic;
+ }
+
+ .w-sdc-designer-leftbar-search-filter {
+
+ }
+
+ .i-sdc-designer-leftbar-section {
+ .hand;
+ }
+
+ .i-sdc-designer-leftbar-section-title {
+ .m_14_m;
+ background-color: @tlv_color_u;
+ .hand;
+ clear: both;
+ height: 40px;
+ line-height: 40px;
+ margin-top: 1px;
+ padding: 0 10px;
+ position: relative;
+ text-transform: uppercase;
+ font-weight: bold;
+ }
+
+ .i-sdc-designer-leftbar-section-title-icon {
+ .hand;
+ .sprite-new;
+ .arrow-up;
+ width: 15px;
+ height: 9px;
+ position: absolute;
+ right: 13px;
+ top: 18px;
+ transition: .3s all;
+ }
+
+ .i-sdc-designer-leftbar-section.expanded .i-sdc-designer-leftbar-section-title-icon {
+ transform: rotate(180deg);
+ margin-right: 2px;
+ }
+
+ .i-sdc-designer-leftbar-section-content {
+ background-color: @main_color_o;
+ }
+
+ .i-sdc-designer-leftbar-section-content-item {
+ background-color: @main_color_p;
+ overflow: hidden;
+
+ &:hover {
+ background-color: @main_color_p;
+ }
+
+ .cp{
+ margin: 6px;
+ }
+
+ .vl{
+ margin: 6px;
+ }
+ }
+
+ .i-sdc-designer-leftbar-section-content-subcat {
+ .m_14_m;
+ background-color: @tlv_color_t;
+ line-height: 35px;
+ padding: 0 10px;
+ cursor: default;
+
+
+ &:hover {
+ background-color: @func_color_r;
+ }
+
+
+ }
+
+ .i-sdc-designer-leftbar-section .i-sdc-designer-leftbar-section-content .i-sdc-designer-leftbar-section-content-item {
+ max-height: 0px;
+ margin: 0 auto;
+ transition: all .3s;
+ }
+
+ .i-sdc-designer-leftbar-section.expanded .i-sdc-designer-leftbar-section-content .i-sdc-designer-leftbar-section-content-item {
+ max-height: 64px;
+ margin: 0 auto 1px auto;
+ // padding: 4px 13px;
+ }
+
+ .i-sdc-designer-leftbar-section.expanded .i-sdc-designer-leftbar-section-content .i-sdc-designer-leftbar-section-content-subcat {
+ margin: 0;
+ }
+
+ .i-sdc-designer-leftbar-section-content-item-icon-ph {
+ display: inline-block;
+ margin: 12px 0 12px 10px;
+ pointer-events: auto;
+
+ .non-certified {
+ position: relative;
+ left: 27px;
+ bottom: 6px;
+ .sprite;
+ .s-sdc-state-non-certified;
+ display: block;
+
+ &.smaller-icon {
+ bottom: 6px;
+ left: 13px;
+ }
+ }
+
+
+
+ }
+
+ .non-certified {
+ position: relative;
+ left: 43px;
+ bottom: 3px;
+ .sprite;
+ .s-sdc-state-non-certified;
+ display: block;
+
+ &.smaller-icon {
+ left: 35px;
+ bottom: -14px;
+ }
+ }
+ /*
+ .i-sdc-composition-leftbar-section-content-item-icon {
+ background-image: url('../../../styles/images/resource-icons/default.png');
+ // position: absolute;
+ right: 20px;
+ top: 10px;
+ height: 40px;
+ width: 40px;
+ background-size: 40px;
+ }
+ */
+
+ .i-sdc-designer-leftbar-section-content-item-info {
+ display: inline-block;
+ // margin-left: 10px;
+ //overflow: hidden;
+ // vertical-align: middle;
+ width: 160px;
+ padding: 0 0 0 10px;
+ }
+
+ .i-sdc-designer-leftbar-section-content-item-info-title {
+ .m_14_m;
+ line-height: 14px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ text-transform: uppercase;
+ max-width: 120px;
+ display: inline-block;
+ white-space: nowrap;
+ vertical-align: bottom;
+ }
+
+ .i-sdc-designer-leftbar-section-content-item-info-text {
+ .p_3;
+ line-height: 15px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ //margin: -1px 0 2px 0;
+ }
+
+ .i-sdc-designer-leftbar-section-content-item-info-text-link {
+ color: @color_s;
+ text-decoration: underline;
+ float: right;
+ position: absolute;
+ right: 17px;
+ // bottom: 5px;
+ }
+
+ // ---------------------------------------------------------------------------------------------------
+ // Form actions
+ // ---------------------------------------------------------------------------------------------------
+ .w-sdc-form-actions-container.add-property {
+ text-align: center;
+ width: 100%;
+ margin-top: 2px;
+ margin-bottom: 12px;
+
+ .w-sdc-form-action {
+ width: 245px;
+ }
+ .w-sdc-form-action.add-property-add-another {
+ .bg_a;
+ margin-left: 35px;
+ }
+ .w-sdc-form-action.add-property-done {
+ margin-left: 312px;
+ }
+ .w-sdc-form-action.save {
+ margin-left: 327px;
+ margin-bottom: 30px;
+ }
+
+ }
+
+ // ---------------------------------------------------------------------------------------------------
+ // Top menu
+ // ---------------------------------------------------------------------------------------------------
+ .w-sdc-header-menu {
+ padding: 25px 0;
+ text-align: center;
+ white-space: nowrap;
+ }
+
+ .i-sdc-header-menu-item {
+ cursor: pointer;
+ display: inline-block;
+ height: 43px;
+ min-width: 93px;
+ padding: 0 38px;
+ position: relative;
+ vertical-align: middle;
+
+ &::after {
+ border-right: 1px solid @color_m;
+ content: '';
+ display: block;
+ height: 43px;
+ right: 0;
+ position: absolute;
+ top: 0;
+ width: 2px;
+ }
+
+ &:first-child {
+ &::before {
+ border-right: 1px solid @color_m;
+ content: '';
+ display: block;
+ height: 43px;
+ left: 0;
+ position: absolute;
+ top: 0;
+ width: 2px;
+ }
+ }
+ }
+
+ .i-sdc-header-menu-item-icon {
+ display: inline-block;
+ height: 20px;
+ width: 28px;
+ }
+
+ .i-sdc-header-menu-item-label {
+ .g_1;
+ line-height: 18px;
+ }
+
+ // ---------------------------------------------------------------------------------------------------
+ // Canvas inline menu
+ // ---------------------------------------------------------------------------------------------------
+ .w-sdc-canvas-menu {
+ position: fixed;
+ z-index: 100;
+
+ border-style: solid;
+ border-width: 1px;
+ border-color: #d8d8d8;
+ box-sizing: border-box;
+ background-color: #ffffff;
+ box-shadow: 0px 2px 2px 0px rgba(24, 24, 25, 0.1);
+ width: 91px;
+
+/* &.vl-type-select{
+ width: 173px;
+ }
+*/
+
+ h3 {
+ color: @func_color_s;
+ font-size: 14px;
+ font-weight: bold;
+ margin: 0;
+ padding: 7px 11px;
+ border-bottom: 1px solid #e5e5e5;
+ }
+
+ .w-sdc-canvas-menu-content {
+ padding: 5px 5px;
+
+ &.vl-select{
+ border-bottom: #d8d8d8 solid 1px;
+ line-height: 15px;
+
+ .tlv-radio {
+ padding: 3px 0px;
+
+ .tlv-radio-label {
+ padding: 3px 0px;
+
+ &::before {
+ margin-right: 10px;
+ }
+ }
+ }
+ }
+
+ .w-sdc-canvas-menu-content-update-button {
+ .sprite;
+ .sprite.e-sdc-small-icon-delete;
+ .hand;
+ position: absolute;
+ top: 15px;
+ right: 10px;
+ }
+ .w-sdc-canvas-menu-content-delete-button {
+ .sprite;
+ .sprite.e-sdc-small-icon-delete;
+ .hand;
+ margin: 0 8px 0 6px;
+ }
+ }
+
+ .w-sdc-canvas-menu-arrow {
+ //TODO: Missing image for small blue triangle.
+ background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAFCAYAAAB4ka1VAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMDE0IDc5LjE1Njc5NywgMjAxNC8wOC8yMC0wOTo1MzowMiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTQgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOkE1OTIzNDI1MENFQjExRTU4ODRERTI1MDM2REZCOUYzIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOkE1OTIzNDI2MENFQjExRTU4ODRERTI1MDM2REZCOUYzIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6QTU5MjM0MjMwQ0VCMTFFNTg4NERFMjUwMzZERkI5RjMiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6QTU5MjM0MjQwQ0VCMTFFNTg4NERFMjUwMzZERkI5RjMiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4gBXTlAAAAOElEQVR42mK0rp7NgASMgZgFiE/CBJjQJPcA8U4gNkdXAJMUAGJ+ZEVMaJIwAFfEhEUSRRFAgAEAVtgJyiLAPWAAAAAASUVORK5CYII=');
+ content: '';
+ display: block;
+ height: 21px;
+ position: absolute;
+ right: 12px;
+ top: -24px;
+ width: 184px;
+ background-repeat: no-repeat;
+ background-position: 175px 16px;
+ }
+
+ }
+}
+/*.right-tab-loader {
+ border: 16px solid #f3f3f3; !* Light grey *!
+ border-top: 16px solid #3498db; !* Blue *!
+ border-radius: 50%;
+ width: 120px;
+ height: 120px;
+ animation: spin 2s linear infinite;
+}*/
+
+@keyframes spin {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+}
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/artifacts/artifacts-view-model.ts b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/artifacts/artifacts-view-model.ts
new file mode 100644
index 0000000000..5bb5d2cbbd
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/artifacts/artifacts-view-model.ts
@@ -0,0 +1,255 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../../../../../references"/>
+module Sdc.ViewModels {
+ 'use strict';
+ import Resource = Sdc.Models.Components.Resource;
+
+ export interface IArtifactsViewModelScope extends ICompositionViewModelScope {
+ artifacts: Array<Models.ArtifactModel>;
+ artifactType: string;
+ downloadFile:Models.IFileDownload;
+ isLoading:boolean;
+
+ getTitle(): string;
+ addOrUpdate(artifact:Models.ArtifactModel): void;
+ delete(artifact:Models.ArtifactModel): void;
+ download(artifact:Models.ArtifactModel): void;
+ openEditEnvParametersModal(artifact:Models.ArtifactModel):void;
+ getEnvArtifact(heatArtifact:Models.ArtifactModel):any;
+ getEnvArtifactName(artifact:Models.ArtifactModel):string;
+ isLicenseArtifact(artifact:Models.ArtifactModel):boolean;
+ isVFiArtifact(artifact:Models.ArtifactModel):boolean;
+ }
+
+ export class ResourceArtifactsViewModel {
+
+ static '$inject' = [
+ '$scope',
+ '$filter',
+ '$modal',
+ '$templateCache',
+ '$state',
+ 'sdcConfig',
+ 'ArtifactsUtils',
+ 'ModalsHandler',
+ 'Sdc.Services.CacheService'
+ ];
+
+ constructor(private $scope:IArtifactsViewModelScope,
+ private $filter:ng.IFilterService,
+ private $modal:ng.ui.bootstrap.IModalService,
+ private $templateCache:ng.ITemplateCacheService,
+ private $state:any,
+ private sdcConfig:Models.IAppConfigurtaion,
+ private artifactsUtils:Sdc.Utils.ArtifactsUtils,
+ private ModalsHandler: Utils.ModalsHandler,
+ private cacheService:Services.CacheService) {
+
+ this.initScope();
+ }
+
+
+ private initArtifactArr = (artifactType:string):void => {
+ let artifacts:Array<Models.ArtifactModel> = [];
+
+ if (this.$scope.selectedComponent) {
+ if ('interface' == artifactType) {
+ let interfaces = this.$scope.selectedComponent.interfaces;
+ if (interfaces && interfaces.standard && interfaces.standard.operations) {
+
+ angular.forEach(interfaces.standard.operations, (operation:any, interfaceName:string):void => {
+ let item:Sdc.Models.ArtifactModel = <Sdc.Models.ArtifactModel>{};
+ if (operation.implementation) {
+ item = <Sdc.Models.ArtifactModel> operation.implementation;
+ }
+ item.artifactDisplayName = interfaceName;
+ item.artifactLabel = interfaceName;
+ item.mandatory = false;
+ artifacts.push(item);
+ });
+ }
+ }else {
+ //init normal artifacts, deployment or api artifacts
+ let artifactsObj:Models.ArtifactGroupModel;
+ switch (artifactType) {
+ case "api":
+ artifactsObj = (<Models.Components.Service>this.$scope.selectedComponent).serviceApiArtifacts;
+ break;
+ case "deployment":
+ if (!this.$scope.isComponentInstanceSelected()) {
+ artifactsObj = this.$scope.selectedComponent.deploymentArtifacts;
+ } else {
+ artifactsObj = this.$scope.currentComponent.selectedInstance.deploymentArtifacts;
+ }
+ break;
+ default:
+ artifactsObj = this.$scope.selectedComponent.artifacts;
+ break;
+ }
+ _.forEach(artifactsObj, (artifact:Models.ArtifactModel, key) => {
+ artifacts.push(artifact);
+ });
+ }
+ }
+ this.$scope.artifacts = artifacts;
+ };
+
+ private openEditArtifactModal = (artifact:Models.ArtifactModel):void => {
+ let viewModelsHtmlBasePath:string = '/app/scripts/view-models/';
+
+ let modalOptions:ng.ui.bootstrap.IModalSettings = {
+ template: this.$templateCache.get(viewModelsHtmlBasePath + 'forms/artifact-form/artifact-form-view.html'),
+ controller: 'Sdc.ViewModels.ArtifactResourceFormViewModel',
+ size: 'sdc-md',
+ backdrop: 'static',
+ keyboard: false,
+ resolve: {
+ artifact: ():Models.ArtifactModel => {
+ return artifact;
+ },
+ component: (): Models.Components.Component => {
+ return this.$scope.currentComponent;
+ }
+ }
+ };
+
+ let modalInstance:ng.ui.bootstrap.IModalServiceInstance = this.$modal.open(modalOptions);
+ modalInstance
+ .result
+ .then(():void => {
+ this.initArtifactArr(this.$scope.artifactType);
+ });
+ };
+
+ private initScope = ():void => {
+ let self = this;
+ this.$scope.isLoading= false;
+ this.$scope.artifactType = this.artifactsUtils.getArtifactTypeByState(this.$state.current.name);
+ this.initArtifactArr(this.$scope.artifactType);
+
+ this.$scope.getTitle = ():string => {
+ return this.artifactsUtils.getTitle(this.$scope.artifactType, this.$scope.selectedComponent);
+ };
+
+ let vfiArtifactTypes:any = this.cacheService.get('UIConfiguration').artifacts.deployment.resourceInstanceDeploymentArtifacts;
+
+ this.$scope.isVFiArtifact=(artifact:Models.ArtifactModel):boolean=>{
+ return vfiArtifactTypes[artifact.artifactType];
+ }
+
+ this.$scope.$watch('selectedComponent', (newResource:Models.Components.Component):void => {
+ if (newResource) {
+ this.initArtifactArr(this.$scope.artifactType);
+ }
+ });
+
+
+ this.$scope.$watch('currentComponent.selectedInstance', (newInstance:Models.ComponentsInstances.ComponentInstance):void => {
+ if (newInstance) {
+ this.initArtifactArr(this.$scope.artifactType);
+ }
+ });
+
+ this.$scope.addOrUpdate = (artifact:Models.ArtifactModel):void => {
+ this.artifactsUtils.setArtifactType(artifact, this.$scope.artifactType);
+ let artifactCopy = new Models.ArtifactModel(artifact);
+ this.openEditArtifactModal(artifactCopy);
+ };
+
+
+ this.$scope.delete = (artifact:Models.ArtifactModel):void => {
+
+ let onOk = ():void => {
+ this.$scope.isLoading= true;
+ this.artifactsUtils.removeArtifact(artifact, this.$scope.artifacts);
+
+ let success = (responseArtifact:Models.ArtifactModel):void => {
+ this.initArtifactArr(this.$scope.artifactType);
+ this.$scope.isLoading= false;
+ };
+
+ let error =(error:any):void =>{
+ console.log('Delete artifact returned error:', error);
+ this.initArtifactArr(this.$scope.artifactType);
+ this.$scope.isLoading= false;
+ };
+ if(this.$scope.isComponentInstanceSelected()){
+ this.$scope.currentComponent.deleteInstanceArtifact(artifact.uniqueId, artifact.artifactLabel).then(success, error);
+ }else{
+ this.$scope.currentComponent.deleteArtifact(artifact.uniqueId, artifact.artifactLabel).then(success, error);//TODO simulate error (make sure error returns)
+ }
+ };
+ let title: string = this.$filter('translate')("ARTIFACT_VIEW_DELETE_MODAL_TITLE");
+ let message: string = this.$filter('translate')("ARTIFACT_VIEW_DELETE_MODAL_TEXT", "{'name': '" + artifact.artifactDisplayName + "'}");
+ this.ModalsHandler.openConfirmationModal(title, message, false).then(onOk);
+ };
+
+
+ this.$scope.getEnvArtifact = (heatArtifact:Models.ArtifactModel):any=>{
+ return _.find(this.$scope.artifacts, (item:Models.ArtifactModel)=>{
+ return item.generatedFromId === heatArtifact.uniqueId;
+ });
+ };
+
+ this.$scope.getEnvArtifactName = (artifact:Models.ArtifactModel):string =>{
+ let envArtifact = this.$scope.getEnvArtifact(artifact);
+ if(envArtifact){
+ return envArtifact.artifactDisplayName;
+ }
+ };
+
+ this.$scope.isLicenseArtifact = (artifact:Models.ArtifactModel) :boolean => {
+ let isLicense:boolean = false;
+ if(this.$scope.component.isResource() && (<Resource>this.$scope.component).isCsarComponent()) {
+ isLicense = this.artifactsUtils.isLicenseType(artifact.artifactType);
+ }
+
+ return isLicense;
+ };
+
+ this.$scope.openEditEnvParametersModal = (artifact:Models.ArtifactModel):void => {
+
+ let modalOptions:ng.ui.bootstrap.IModalSettings = {
+ template: this.$templateCache.get('/app/scripts/view-models/forms/env-parameters-form/env-parameters-form.html'),
+ controller: 'Sdc.ViewModels.EnvParametersFormViewModel',
+ size: 'sdc-md',
+ backdrop: 'static',
+ resolve: {
+ artifact: ():Models.ArtifactModel => {
+ return artifact;
+ },
+ component: (): Models.Components.Component => {
+ return this.$scope.currentComponent;
+ }
+ }
+ };
+
+ let modalInstance:ng.ui.bootstrap.IModalServiceInstance = this.$modal.open(modalOptions);
+ modalInstance
+ .result
+ .then(():void => {
+ this.initArtifactArr(this.$scope.artifactType);
+ });
+ };
+
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/artifacts/artifacts-view.html b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/artifacts/artifacts-view.html
new file mode 100644
index 0000000000..8c0138964f
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/artifacts/artifacts-view.html
@@ -0,0 +1,55 @@
+<perfect-scrollbar class="w-sdc-designer-sidebar-tab-content artifacts">
+ <div class="w-sdc-designer-sidebar-section">
+ <expand-collapse
+ expanded-selector=".w-sdc-designer-sidebar-section-content" class="w-sdc-designer-sidebar-section-title">
+ <span class="w-sdc-designer-sidebar-section-title-text" data-ng-bind="getTitle()" tooltips tooltip-content="{{getTitle()}}"></span>
+ <div class="w-sdc-designer-sidebar-section-title-icon"></div>
+ </expand-collapse>
+
+ <div class="w-sdc-designer-sidebar-section-content" data-ng-hide="'deployment' == artifactType && !selectedComponent.isComplex()">
+ <div class="i-sdc-designer-sidebar-section-content-item">
+ <div class="i-sdc-designer-sidebar-section-content-item-artifact"
+ data-ng-repeat="artifact in artifacts | orderBy: ['-mandatory', 'artifactDisplayName'] track by $index" data-ng-if="!isComponentInstanceSelected() || (isVFiArtifact(artifact)|| artifact.esId) && 'HEAT_ENV' !== artifact.artifactType">
+ <span data-ng-if="isComponentInstanceSelected() && artifact.heatParameters.length" class="i-sdc-designer-sidebar-section-content-item-file-link"></span>
+ <div class="i-sdc-designer-sidebar-section-content-item-artifact-details" data-ng-class="{'heat':artifact.isHEAT() && artifact.heatParameters.length}">
+ <div class="i-sdc-designer-sidebar-section-content-item-artifact-filename" data-tests-id="artifactName"
+ data-ng-class="{'hand enabled':!isComponentInstanceSelected() && artifact.heatParameters.length && !isViewMode()}"
+ data-ng-bind="artifact.artifactName" tooltips tooltip-content="{{artifact.artifactName}}"
+ data-ng-click="!isViewMode() && !isComponentInstanceSelected() && artifact.heatParameters.length && openEditEnvParametersModal(artifact)" data-ng-if="artifact.artifactName"></div>
+ <div>
+ <span class="i-sdc-designer-sidebar-section-content-item-artifact-details-name" data-tests-id="artifact_Display_Name-{{artifact.artifactDisplayName}}"
+ data-ng-class="{'hand enabled': (!isComponentInstanceSelected()||isVFiArtifact(artifact)) && !isViewMode() && !artifact.isHEAT() && !artifact.isThirdParty() && !isLicenseArtifact(artifact)}"
+ data-ng-bind="artifact.artifactDisplayName" data-ng-click="!isViewMode() && !isLoading && (!isComponentInstanceSelected()||isVFiArtifact(artifact)) && !artifact.isHEAT() && !artifact.isThirdParty() && !isLicenseArtifact(artifact) && addOrUpdate(artifact)"
+ tooltips tooltip-content="{{artifact.artifactDisplayName}}"></span>
+ <div class="i-sdc-designer-sidebar-section-content-item-artifact-heat-env" ng-if="isComponentInstanceSelected() && artifact.heatParameters.length">
+ <span class="enabled" data-ng-bind="getEnvArtifactName(artifact)" data-ng-click="!isViewMode() && addOrUpdate(getEnvArtifact(artifact))"></span>
+ <download-artifact class="i-sdc-designer-sidebar-section-content-item-button download-env sprite e-sdc-small-download hand" artifact="getEnvArtifact(artifact)"
+ component="currentComponent" instance="true"
+ data-tests-id="download"></download-artifact>
+ </div>
+ </div>
+
+ <div class="i-sdc-designer-sidebar-section-content-item-artifact-details-desc">
+ <span class="i-sdc-designer-sidebar-section-content-item-artifact-details-desc-label" data-ng-show="artifact.description">Description:</span>{{artifact.description}}
+ </div>
+ </div>
+ <button ng-if="!isViewMode() && artifact.esId && (!isComponentInstanceSelected()||isVFiArtifact(artifact)) && !artifact.isHEAT() && !artifact.isThirdParty() && !isLicenseArtifact(artifact)" class="i-sdc-designer-sidebar-section-content-item-button delete sprite e-sdc-small-icon-delete"
+ data-tests-id="delete" data-ng-click="delete(artifact)" type="button"></button>
+ <button ng-if="!isViewMode() && isComponentInstanceSelected() && (getEnvArtifact(artifact)).heatParameters.length"
+ class="i-sdc-designer-sidebar-section-content-item-button attach sprite e-sdc-small-icon-pad"
+ data-ng-click="openEditEnvParametersModal(getEnvArtifact(artifact))" type="button"></button>
+ <download-artifact ng-if="artifact.esId && 'deployment' != artifactType" class="i-sdc-designer-sidebar-section-content-item-button download sprite e-sdc-small-download hand"
+ artifact="artifact" component="selectedComponent" data-tests-id="download"></download-artifact>
+ <download-artifact ng-if="artifact.esId && 'deployment' == artifactType" class="i-sdc-designer-sidebar-section-content-item-button download sprite e-sdc-small-download hand"
+ artifact="artifact" component="currentComponent" instance="isComponentInstanceSelected()" data-tests-id="download"></download-artifact>
+ <button ng-if="!isViewMode() && !artifact.esId && artifactType==='deployment' && !isComponentInstanceSelected() && !artifact.isThirdParty()" class="i-sdc-designer-sidebar-section-content-item-button attach sprite e-sdc-small-icon-upload"
+ data-ng-click="addOrUpdate(artifact)" type="button" data-tests-id="add_Artifact"></button>
+ </div>
+ </div>
+
+ </div>
+ <div class="w-sdc-designer-sidebar-section-footer" data-ng-if="!isViewMode() && artifactType!=='api' && (!isComponentInstanceSelected()||selectedComponent.resourceType=='VF') && !currentComponent.isProduct() && ('deployment' != artifactType || selectedComponent.isComplex())">
+ <button class="w-sdc-designer-sidebar-section-footer-action tlv-btn blue" data-tests-id="add_Artifact_Button" data-ng-click="addOrUpdate({})" type="button">Add Artifact</button>
+ </div>
+ </div>
+</perfect-scrollbar>
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/artifacts/artifacts.less b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/artifacts/artifacts.less
new file mode 100644
index 0000000000..5726ca66fc
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/artifacts/artifacts.less
@@ -0,0 +1,172 @@
+.w-sdc-designer-sidebar-tab-content.artifacts {
+
+ .i-sdc-designer-sidebar-section-content-item-artifact.hand {
+ .hand;
+ }
+
+ .w-sdc-designer-sidebar-section-content {
+ padding: 0;
+ }
+ .w-sdc-designer-sidebar-section-title {
+ &.expanded {
+ margin-bottom: 0;
+ }
+ }
+
+ .i-sdc-designer-sidebar-section-content-item-artifact-details {
+ display: inline-block;
+ margin-left: 5px;
+ vertical-align: middle;
+ width: 180px;
+ &.heat {
+ line-height: 18px;
+ width: 250px;
+ }
+ }
+
+ .i-sdc-designer-sidebar-section-content-item-artifact-details-name {
+ .g_7;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ max-width:220px;
+ display: inline-block;
+ //text-transform: capitalize;
+ &.enabled {
+ &:hover {
+ .a_7;
+ }
+ }
+
+ }
+
+ .i-sdc-designer-sidebar-section-content-item-artifact-heat-env {
+ .g_7;
+ margin-top: 6px;
+ line-height: 42px;
+ padding-top: 10px;
+ border-top:1px solid #c8cdd1;
+ .enabled {
+ &:hover {
+ .hand;
+ .a_7;
+ }
+ }
+ }
+
+ .i-sdc-designer-sidebar-section-content-item-artifact-filename {
+ .g_7;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ max-width: 225px;
+ display: inline-block;
+ .bold;
+ &.enabled {
+ &:hover {
+ .a_7;
+ }
+ }
+ }
+
+
+ .i-sdc-designer-sidebar-section-content-item-file-link{
+ border-left: 1px #848586 solid;
+ height: 58px;
+ margin-left: -11px;
+ margin-top: 11px;
+ border-top: 1px #848586 solid;
+ border-bottom: 1px #848586 solid;
+ width: 12px;
+ float: left;
+ }
+
+ .i-sdc-designer-sidebar-section-content-item-artifact-details-desc {
+ display: none;
+ line-height: 16px;
+ word-wrap: break-word;
+ white-space: normal;
+ }
+
+ .i-sdc-designer-sidebar-section-content-item-artifact-details-desc-label {
+ .b_3;
+ }
+
+
+ .i-sdc-designer-sidebar-section-content-item-artifact {
+ border-bottom: 1px solid #c8cdd1;
+ padding: 5px 10px 5px 18px;
+ position: relative;
+ // line-height: 36px;
+ min-height: 61px;
+ cursor: default;
+ display: flex;
+ align-items: center;
+
+
+ .i-sdc-designer-sidebar-section-content-item-button {
+ top: 20px;
+ line-height: 10px;
+ }
+
+ &:hover {
+ //background-color: @color_c;
+ .bg_c;
+ transition: all .3s;
+
+ .i-sdc-designer-sidebar-section-content-item-button {
+ display: block;
+
+ }
+
+ }
+ }
+
+}
+
+///////////////////Lifecycle Management
+.i-sdc-designer-sidebar-section-content-item-lm {
+ .b_7;
+ border-bottom: 1px solid @color_e;
+ cursor: pointer;
+ height: 65px;
+ padding: 22px 0;
+ position: relative;
+
+ &:hover {
+ .bg_c_hover;
+ margin-left: -10px;
+ margin-right: -10px;
+ padding: 22px 10px;
+
+ .i-sdc-designer-sidebar-section-content-item-lm-icon {
+ right: 16px;
+ }
+ }
+}
+
+.i-sdc-designer-sidebar-section-content-item-lm:first-child {
+ margin-top: -18px;
+}
+
+.i-sdc-designer-sidebar-section-content-item-lm-icon {
+ position: absolute;
+ right: 6px;
+
+ //TODO: Replace the icons.
+ &.icon-view {
+ background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAJCAYAAAACTR1pAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMDE0IDc5LjE1Njc5NywgMjAxNC8wOC8yMC0wOTo1MzowMiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTQgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjdGMDNBRUJDMDkxNjExRTVCMjRBOEI5QzMxQTlBQjY4IiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjdGMDNBRUJEMDkxNjExRTVCMjRBOEI5QzMxQTlBQjY4Ij4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6N0YwM0FFQkEwOTE2MTFFNUIyNEE4QjlDMzFBOUFCNjgiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6N0YwM0FFQkIwOTE2MTFFNUIyNEE4QjlDMzFBOUFCNjgiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4U2decAAABRUlEQVR42pyRPUvDUBSG39t2UyyaBgvWQZMqKoFq4lBH0U0UqXSxKPaPieCoWPAPKIgdjG1ohaghOrSR1iaLKMXpmnMhQVfPcLmc8zxwPhjnHFE4rx3eMO/w4rr4+vwQuZHRMcwqClaMVeRnplnEskg8O7/g96YJSZLgBz5+R0bKIAgC6IaBvdKukBP0HJ+cctu2cVCtQsrIUFUVlcp+LE6EItWIIVaIV9c3/PnRRqlcxkJeYV2vi+/hEO/9QSx6b56oEUMsOal6/TacQcXinCpaGE+n0QnljufFIuUoiAlZTk6iWFyD6zpoWi3RwvrmBlLJZCzRn3IUxBBLjlgO9e06T9ja3sGyrqPX66NtWQLWCgVks5Owmg1c1mpQ8vM4OqywP1s1w1PkpnJY0jTIsizy/sDHQ7sFmt0ITxJtlf33jj8CDADhB52tEX6ifAAAAABJRU5ErkJggg==');
+ height: 9px;
+ top: 29px;
+ width: 14px;
+ }
+
+ //TODO: Replace the icons.
+ &.icon-alert {
+ background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAANCAYAAAB2HjRBAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMDE0IDc5LjE1Njc5NywgMjAxNC8wOC8yMC0wOTo1MzowMiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTQgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjhBM0YxQTBCMDkyMDExRTVBNzlCQUYxNEYwMDUwOTQ5IiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjhBM0YxQTBDMDkyMDExRTVBNzlCQUYxNEYwMDUwOTQ5Ij4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6OEEzRjFBMDkwOTIwMTFFNUE3OUJBRjE0RjAwNTA5NDkiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6OEEzRjFBMEEwOTIwMTFFNUE3OUJBRjE0RjAwNTA5NDkiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7exgceAAAB5klEQVR42oySy2tTQRTGfzN3cpvbpFTaUlzUjeCuVFyJf4DuCl34wL/CrbpUunLnwpUuFFy48IFiwYWhpYuW+gCLrbWKBQumaRIba5ub3MccJ5WECCV44MDMme873/lmBhGhnZulcn/3/sfrO7J/45R017pT0xUS2bC93vy2LAOLt5DfCeUn14RD4h/ysbHRDii/8pyo7DmFiP7VZ4Rbu9KT3I7i+qJklp7ibQt2J6HeNOhXV/gv8tDcbaLtXYLJKeTcZfxqSPPzvGu6ID3J1Q8z0ny/hIkM5swF8qenUFUfwixHCtd7Kwez04jOgEmc+hq6tuZ8N9E6xG5U+Pn2kRxKLs7eE7tRc0XlToRM2sD+2sGmDiY+kWsSFG528Kr1Xgfjfn0n5uElvFrrzVqVDHqkiTKjxCsV9LBx08QkgcY/eZH+89Oqo+x9eumkwQaQaoPEKWpsHHN8ArItgLt5UdiG6736mFLxixyQt+bviik8QPuaNFRIkjqQRZ2YJDo6QWydjTRtOcGPPdJKgnlxFfN9fVmGNhecR4U3mLrL6nPTRTQHXZO5+4jyyI4Iqs99GJvgBiKjcySlj3897y6/kbreI4w0QS7ASxrsa4/cXkxkIzK5PKmNqRMw7NfdeczA+Fn1R4ABAPnMAeCjkgf5AAAAAElFTkSuQmCC');
+ height: 13px;
+ top: 27px;
+ width: 15px;
+ }
+
+}
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/details/details-view-model.ts b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/details/details-view-model.ts
new file mode 100644
index 0000000000..b28de8d331
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/details/details-view-model.ts
@@ -0,0 +1,132 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../../../../../references"/>
+module Sdc.ViewModels {
+
+ 'use strict';
+
+ export interface IEditResourceVersion {
+ allVersions: any;
+ changeVersion: string;
+ }
+
+ interface IDetailsViewModelScope extends ICompositionViewModelScope {
+ isLoading: boolean;
+ $parent: ICompositionViewModelScope;
+ expandedSection: Array<string>;
+ editForm:ng.IFormController;
+ editResourceVersion: IEditResourceVersion;
+
+ changeResourceVersion(): void;
+ }
+
+ export class DetailsViewModel {
+
+ static '$inject' = [
+ '$scope',
+ 'LeftPaletteLoaderService',
+ 'EventListenerService'
+
+ ];
+
+ constructor(private $scope:IDetailsViewModelScope,
+ private LeftPaletteLoaderService:Services.Components.LeftPaletteLoaderService,
+ private eventListenerService:Services.EventListenerService) {
+ this.initScope();
+ }
+
+ private clearSelectedVersion = ():void => {
+ this.$scope.editResourceVersion = {
+ allVersions: {},
+ changeVersion: null
+ };
+ };
+
+ private versioning:Function = (versionNumber:string):string => {
+ let version:Array<string> = versionNumber.split('.');
+ return '00000000'.slice(version[0].length) + version[0] + '.' + '00000000'.slice(version[1].length) + version[1];
+ };
+
+ private initEditResourceVersion = ():void => {
+ this.clearSelectedVersion();
+ this.$scope.editResourceVersion.allVersions[this.$scope.currentComponent.selectedInstance.componentVersion] = this.$scope.currentComponent.selectedInstance.componentUid;
+ _.merge(this.$scope.editResourceVersion.allVersions, angular.copy(this.$scope.selectedComponent.allVersions));
+ let sorted:any= _.sortBy(_.toPairs(this.$scope.editResourceVersion.allVersions), (item)=>{
+ return this.versioning(item[0]);
+ });
+ this.clearSelectedVersion();
+ _.forEach(sorted, (item)=> {
+ this.$scope.editResourceVersion.allVersions[item[0]]= item[1];
+ });
+
+ let highestVersion = _.last(Object.keys(this.$scope.selectedComponent.allVersions));
+
+ //TODO - ask ronny - what happend if the parent is not in the leftPalette (instance of csar for example)
+ if (parseFloat(highestVersion) % 1) { //if highest is minor, make sure it is the latest checked in -
+ let latestVersionComponent:Models.Components.Component = _.find(this.LeftPaletteLoaderService.getFullDataComponentListWithVls(this.$scope.currentComponent.componentType), (component:Models.Components.Component) => { //latest checked in
+ return (component.systemName === this.$scope.selectedComponent.systemName
+ || component.uuid === this.$scope.selectedComponent.uuid);
+ });
+ let latestVersion:string = latestVersionComponent ? latestVersionComponent.version : highestVersion;
+
+ if (highestVersion != latestVersion) { //highest is checked out - remove from options
+ this.$scope.editResourceVersion.allVersions = _.omit(this.$scope.editResourceVersion.allVersions, highestVersion);
+ }
+ }
+ this.$scope.editResourceVersion.changeVersion = this.$scope.currentComponent.selectedInstance.componentVersion;
+ };
+
+ private initScope = ():void => {
+ this.$scope.isLoading = false;
+ this.$scope.$parent.isLoading = false;
+ this.$scope.expandedSection = ['general', 'tags'];
+ //this.clearSelectedVersion();
+
+ this.$scope.$watch('selectedComponent', (component:Models.Components.Component) => {
+ if (this.$scope.isComponentInstanceSelected()) {
+ this.initEditResourceVersion();
+ }
+ });
+
+ this.$scope.changeResourceVersion = ():void => {
+ this.$scope.isLoading = true;
+ this.$scope.$parent.isLoading = true;
+
+ let onSuccess = (component:Models.Components.Component)=> {
+ this.$scope.isLoading = false;
+ this.$scope.$parent.isLoading = false;
+ this.$scope.setComponent(component);
+ this.$scope.updateSelectedComponent();
+
+ this.eventListenerService.notifyObservers(Sdc.Utils.Constants.GRAPH_EVENTS.ON_VERSION_CHANGED, this.$scope.currentComponent);
+ };
+
+ let onFailed = (error:any)=> {
+ this.$scope.isLoading = false;
+ this.$scope.$parent.isLoading = false;
+ console.log(error);
+ };
+
+ let componentUid:string = this.$scope.editResourceVersion.allVersions[this.$scope.editResourceVersion.changeVersion];
+ this.$scope.currentComponent.changeComponentInstanceVersion(componentUid).then(onSuccess, onFailed);
+ };
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/details/details-view.html b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/details/details-view.html
new file mode 100644
index 0000000000..6ae462760c
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/details/details-view.html
@@ -0,0 +1,129 @@
+<perfect-scrollbar include-padding="true" class="w-sdc-designer-sidebar-tab-content details">
+
+ <div class="w-sdc-designer-sidebar-section">
+ <loader data-display="isLoading"></loader>
+ <expand-collapse expanded-selector=".w-sdc-designer-sidebar-section-content.general" class="w-sdc-designer-sidebar-section-title">
+
+ General Info
+ <div class="w-sdc-designer-sidebar-section-title-icon"></div>
+ </expand-collapse>
+
+ <div class="w-sdc-designer-sidebar-section-content general">
+ <div class="i-sdc-designer-sidebar-section-content-item">
+ <span class="i-sdc-designer-sidebar-section-content-item-label">Type:</span>
+ <span class="i-sdc-designer-sidebar-section-content-item-value" data-tests-id="rightTab_componentType" data-ng-bind="selectedComponent.componentType"></span>
+ </div>
+ <div class="i-sdc-designer-sidebar-section-content-item" data-ng-if="selectedComponent.isResource()">
+ <span class="i-sdc-designer-sidebar-section-content-item-label">Resource Type:</span>
+ <span class="i-sdc-designer-sidebar-section-content-item-value" data-ng-if="selectedComponent.isResource()" data-ng-bind="selectedComponent.resourceType"
+ tooltips tooltip-content="&#8203;{{selectedComponent.resourceType | resourceTypeName}}"
+ data-tests-id="rightTab_resourceType"></span>
+ </div>
+ <div class="i-sdc-designer-sidebar-section-content-item">
+
+ <span class="i-sdc-designer-sidebar-section-content-item-label">Version:</span>
+ <span class="i-sdc-designer-sidebar-section-content-item-value"
+ data-ng-if="!isComponentInstanceSelected() || selectedComponent.isVl()" data-tests-id="rightTab_version" data-ng-bind="selectedComponent.version"></span>
+
+ <ng-form name="editForm" data-ng-if="isComponentInstanceSelected() && !selectedComponent.isVl()">
+ <select data-ng-model="editResourceVersion.changeVersion" name="changeVersion" data-ng-disabled="$parent.isViewOnly"
+ class="i-sdc-designer-sidebar-section-content-item-value i-sdc-form-select"
+ data-ng-class="{'minor': (editResourceVersion.changeVersion)%1}"
+ data-ng-change="changeResourceVersion()">
+ <option class="select-instance-version" data-ng-class="{'minor': key%1}"
+ ng-repeat="(key, value) in editResourceVersion.allVersions">{{key}}</option>
+ </select></ng-form>
+ </div>
+ <div class="i-sdc-designer-sidebar-section-content-item" data-ng-if="selectedComponent.categories && selectedComponent.categories[0]">
+ <span class="i-sdc-designer-sidebar-section-content-item-label">Category:</span>
+ <span class="i-sdc-designer-sidebar-section-content-item-value" data-ng-bind="selectedComponent.categories[0].name"
+ tooltips tooltip-content="&#8203;{{selectedComponent.categories[0].name}}"
+ data-tests-id="rightTab_category"></span>
+ </div>
+ <div class="i-sdc-designer-sidebar-section-content-item" data-ng-if="selectedComponent.categories && selectedComponent.categories[0] && selectedComponent.categories[0].subcategories">
+ <span class="i-sdc-designer-sidebar-section-content-item-label">Sub Category:</span>
+ <span class="i-sdc-designer-sidebar-section-content-item-value" data-ng-bind="selectedComponent.categories[0].subcategories[0].name"
+ tooltips tooltip-content="&#8203;{{selectedComponent.categories[0].subcategories[0].name}}"
+ data-tests-id="rightTab_subCategory"></span>
+ </div>
+ <div class="i-sdc-designer-sidebar-section-content-item">
+ <span class="i-sdc-designer-sidebar-section-content-item-label">Creation Date:</span>
+ <span class="i-sdc-designer-sidebar-section-content-item-value" data-ng-bind="selectedComponent.creationDate | date: 'MM/dd/yyyy'"
+ data-tests-id="rightTab_creationDate"></span>
+ </div>
+ <div class="i-sdc-designer-sidebar-section-content-item">
+ <span class="i-sdc-designer-sidebar-section-content-item-label">Author:</span>
+ <span class="i-sdc-designer-sidebar-section-content-item-value" data-ng-bind="selectedComponent.creatorFullName"
+ tooltips tooltip-content="&#8203;{{selectedComponent.creatorFullName}}"
+ data-tests-id="rightTab_author">
+ </span>
+ </div>
+ <div class="i-sdc-designer-sidebar-section-content-item" data-ng-if="selectedComponent.isService()">
+ <span class="i-sdc-designer-sidebar-section-content-item-label" translate="GENERAL_LABEL_PROJECT_CODE"></span>
+ <span class="i-sdc-designer-sidebar-section-content-item-value"
+ data-tests-id="rightTab_projectCode" data-ng-bind="selectedComponent.projectCode"></span>
+ </div>
+ <div class="i-sdc-designer-sidebar-section-content-item" data-ng-if="selectedComponent.isResource()">
+ <span class="i-sdc-designer-sidebar-section-content-item-label">Vendor Name:</span>
+ <span class="i-sdc-designer-sidebar-section-content-item-value" data-ng-bind="selectedComponent.vendorName"
+ tooltips tooltip-content="&#8203;{{selectedComponent.vendorName}}"
+ data-tests-id="rightTab_vendorName">
+ </span>
+ </div>
+ <div class="i-sdc-designer-sidebar-section-content-item" data-ng-if="selectedComponent.isResource()">
+ <span class="i-sdc-designer-sidebar-section-content-item-label">Vendor Release:</span>
+ <span class="i-sdc-designer-sidebar-section-content-item-value" data-ng-bind="selectedComponent.vendorRelease"
+ tooltips tooltip-class="tooltip-custom break-word-tooltip" tooltip-content="&#8203;{{selectedComponent.vendorRelease}}"
+ data-tests-id="rightTab_vendorRelease">
+ </span>
+ </div>
+ <div class="i-sdc-designer-sidebar-section-content-item">
+ <span class="i-sdc-designer-sidebar-section-content-item-label" translate="GENERAL_LABEL_CONTACT_ID"></span>
+ <span class="i-sdc-designer-sidebar-section-content-item-value" data-ng-bind="selectedComponent.contactId"
+ data-tests-id="rightTab_userId"></span>
+ </div>
+ <div class="i-sdc-designer-sidebar-section-content-item description">
+ <span class="i-sdc-designer-sidebar-section-content-item-label">Description:
+
+ <span class="i-sdc-designer-sidebar-section-content-description-item-value" ellipsis="selectedComponent.description" max-chars="55"
+ data-tests-id="rightTab_description"></span>
+ </span>
+ </div>
+
+ </div>
+ </div>
+
+ <div class="w-sdc-designer-sidebar-section additionalInformation">
+ <expand-collapse expanded-selector=".w-sdc-designer-sidebar-section-content.additionalInformation" class="w-sdc-designer-sidebar-section-title">
+ Additional Information
+ <div class="w-sdc-designer-sidebar-section-title-icon"></div>
+ </expand-collapse>
+
+ <div class="w-sdc-designer-sidebar-section-content additionalInformation">
+ <div class="i-sdc-designer-sidebar-section-content-item" data-ng-repeat="additionalInformation in selectedComponent.getAdditionalInformation() track by $index">
+ <span class="i-sdc-designer-sidebar-section-content-item-label additional-information" data-ng-bind="additionalInformation.key" tooltips tooltip-content="{{additionalInformation.key}}"></span>
+ <span class="i-sdc-designer-sidebar-section-content-item-label">:</span>
+ <span class="i-sdc-designer-sidebar-section-content-item-value additional-information" data-ng-bind="additionalInformation.value"
+ tooltips tooltip-class="tooltip-custom break-word-tooltip" tooltip-content="{{additionalInformation.value}}"></span>
+ </div>
+ </div>
+ </div>
+
+
+ <div class="w-sdc-designer-sidebar-section tags">
+ <expand-collapse expanded-selector=".w-sdc-designer-sidebar-section-content.tags" class="w-sdc-designer-sidebar-section-title">
+ Tags
+ <div class="w-sdc-designer-sidebar-section-title-icon"></div>
+ </expand-collapse>
+
+ <div class="w-sdc-designer-sidebar-section-content tags">
+ <div class="i-sdc-designer-sidebar-section-content-item">
+ <span class="i-sdc-designer-sidebar-section-content-item-tag" data-ng-repeat="tag in selectedComponent.tags track by $index" data-ng-bind="tag"
+ data-tests-id="rightTab_tag" tooltips tooltip-content="{{tag}}"></span>
+ </div>
+ </div>
+ </div>
+ </div>
+
+</perfect-scrollbar>
+
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/details/details.less b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/details/details.less
new file mode 100644
index 0000000000..e88e130379
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/details/details.less
@@ -0,0 +1,68 @@
+.w-sdc-designer-sidebar-tab-content.details {
+
+ .w-sdc-designer-sidebar-section-title + .w-sdc-designer-sidebar-section-content {
+ padding: 0 10px 0 18px;
+ }
+
+ .w-sdc-designer-sidebar-section-title.expanded + .w-sdc-designer-sidebar-section-content {
+ padding: 10px 10px 10px 18px;
+ }
+
+ .i-sdc-designer-sidebar-section-content-item-label {
+ font-weight: bold;
+ &.additional-information{
+ max-width:100px;
+ display: inline-block;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ vertical-align: bottom;
+ }
+
+ }
+
+
+
+ .i-sdc-designer-sidebar-section-content-item-value {
+ // .hyphenate;
+ padding-left: 10px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ display: inline-block;
+ max-width: 160px;
+ vertical-align:bottom;
+ font-weight: normal;
+ &.additional-information{
+ max-width:160px;
+ display: inline-block;
+ }
+ &.i-sdc-form-select {
+ .b_1;
+ border: 1px solid @border_color_f;
+ width: 210px;
+ max-width: 210px;
+ padding-left: 4px;
+
+ .select-instance-version {
+ .b_1;
+ &.minor {
+ .h_1;
+ }
+ }
+ }
+ &.minor {
+ .h_1;
+ }
+ }
+ .i-sdc-designer-sidebar-section-content-description-item-value{
+ max-width: none;
+ font-weight: normal;
+ }
+
+ .w-sdc-designer-sidebar-section.tags {
+ .i-sdc-designer-sidebar-section-content-item {
+ white-space: normal;
+ }
+ }
+
+}
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/properties-and-attributes/properties-view-model.ts b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/properties-and-attributes/properties-view-model.ts
new file mode 100644
index 0000000000..aef25c51ce
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/properties-and-attributes/properties-view-model.ts
@@ -0,0 +1,228 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../../../../../references"/>
+module Sdc.ViewModels {
+ 'use strict';
+
+ interface IResourcePropertiesAndAttributesViewModelScope extends ICompositionViewModelScope {
+ properties: Models.PropertiesGroup;
+ attributes: Models.AttributesGroup;
+ propertiesMessage: string;
+ addProperty(): void;
+ updateProperty(property:Models.PropertyModel): void;
+ deleteProperty(property:Models.PropertyModel): void;
+ viewAttribute(attribute:Models.AttributeModel): void;
+ groupNameByKey(key:string): string;
+ isPropertyOwner():boolean;
+ }
+
+ export class ResourcePropertiesViewModel {
+
+ static '$inject' = [
+ '$scope',
+ '$filter',
+ '$modal',
+ '$templateCache',
+ 'ModalsHandler'
+ ];
+
+
+ constructor(private $scope:IResourcePropertiesAndAttributesViewModelScope,
+ private $filter:ng.IFilterService,
+ private $modal:ng.ui.bootstrap.IModalService,
+ private $templateCache:ng.ITemplateCacheService,
+ private ModalsHandler: Utils.ModalsHandler) {
+
+ this.initScope();
+ }
+
+ private initComponentProperties = ():void => {
+ let result:Models.PropertiesGroup = {};
+
+ if(this.$scope.selectedComponent){
+ this.$scope.propertiesMessage = undefined;
+ if(this.$scope.isComponentInstanceSelected()){
+ if (this.$scope.currentComponent.selectedInstance.originType==='VF') {
+ // Temporally fix to hide properties for VF (UI stack when there are many properties)
+ this.$scope.propertiesMessage = "Note: properties for VF are disabled";
+ } else {
+ result[this.$scope.currentComponent.selectedInstance.uniqueId] = this.$scope.currentComponent.componentInstancesProperties[this.$scope.currentComponent.selectedInstance.uniqueId];
+ }
+ }else if(this.$scope.currentComponent.isService()){
+ // Temporally fix to hide properties for service (UI stack when there are many properties)
+ //result = this.$scope.currentComponent.componentInstancesProperties;
+ this.$scope.propertiesMessage = "Note: properties for service are disabled";
+ }else{
+ let key = this.$scope.selectedComponent.uniqueId;
+ result[key]= Array<Models.PropertyModel>();
+ let derived = Array<Models.PropertyModel>();
+ _.forEach(this.$scope.selectedComponent.properties, (property:Models.PropertyModel) => {
+ if(key == property.parentUniqueId){
+ result[key].push(property);
+ }else{
+ property.readonly = true;
+ derived.push(property);
+ }
+ });
+ if(derived.length){
+ result['derived']= derived;
+ }
+ }
+ this.$scope.properties = result;
+ }
+ };
+
+
+ private initComponentAttributes = ():void => {
+ let result:Models.AttributesGroup = {};
+
+ if(this.$scope.selectedComponent){
+ if(this.$scope.isComponentInstanceSelected()){
+ result[this.$scope.currentComponent.selectedInstance.uniqueId] = this.$scope.currentComponent.componentInstancesAttributes[this.$scope.currentComponent.selectedInstance.uniqueId];
+ }else if(this.$scope.currentComponent.isService()){
+ result = this.$scope.currentComponent.componentInstancesAttributes;
+ }
+ this.$scope.attributes = result;
+ }
+ };
+
+ private openEditPropertyModal = (property:Models.PropertyModel):void => {
+ let viewModelsHtmlBasePath:string = '/app/scripts/view-models/';
+
+ let modalOptions:ng.ui.bootstrap.IModalSettings = {
+ template: this.$templateCache.get(viewModelsHtmlBasePath + 'forms/property-form/property-form-view.html'),
+ controller: 'Sdc.ViewModels.PropertyFormViewModel',
+ size: 'sdc-l',
+ backdrop: 'static',
+ keyboard: false,
+ resolve: {
+ property: ():Models.PropertyModel => {
+ return property;
+ },
+ component: ():Models.Components.Component => {
+ return this.$scope.currentComponent;
+ },
+ filteredProperties: ():Array<Models.PropertyModel> => {
+ return this.$scope.selectedComponent.properties
+ }
+ }
+ };
+
+
+ let modalInstance:ng.ui.bootstrap.IModalServiceInstance = this.$modal.open(modalOptions);
+ modalInstance
+ .result
+ .then(():void => {
+ // this.initComponentProperties();
+ });
+ };
+
+ private openAttributeModal = (atrribute:Models.AttributeModel):void => {
+ let viewModelsHtmlBasePath:string = '/app/scripts/view-models/';
+
+ let modalOptions:ng.ui.bootstrap.IModalSettings = {
+ template: this.$templateCache.get(viewModelsHtmlBasePath + 'forms/attribute-form/attribute-form-view.html'),
+ controller: 'Sdc.ViewModels.AttributeFormViewModel',
+ size: 'sdc-md',
+ backdrop: 'static',
+ keyboard: false,
+ resolve: {
+ attribute: ():Models.AttributeModel => {
+ return atrribute;
+ },
+ component: ():Models.Components.Component => {
+ return this.$scope.currentComponent;
+ }
+ }
+ };
+ this.$modal.open(modalOptions);
+ };
+
+
+
+
+ private initScope = ():void => {
+ this.initComponentProperties();
+ this.initComponentAttributes();
+
+ this.$scope.$watchCollection('currentComponent.componentInstancesProperties', (newData:any):void => {
+ this.initComponentProperties();
+ });
+
+ this.$scope.$watchCollection('currentComponent.properties', (newData:any):void => {
+ this.initComponentProperties();
+ });
+
+ this.$scope.$watch('currentComponent.selectedInstance', (newInstance:Models.ComponentsInstances.ComponentInstance):void => {
+ if (angular.isDefined(newInstance)) {
+ this.initComponentProperties();
+ this.initComponentAttributes();
+ }
+ });
+
+ this.$scope.$watchCollection('currentComponent.componentInstancesAttributes', (newData:any):void => {
+ this.initComponentAttributes();
+ });
+
+ this.$scope.isPropertyOwner = ():boolean => {
+ return this.$scope.currentComponent && this.$scope.currentComponent.isResource() &&
+ !this.$scope.isComponentInstanceSelected();
+ };
+
+ this.$scope.addProperty = ():void => {
+ let property = new Models.PropertyModel();
+ this.openEditPropertyModal(property);
+ };
+
+ this.$scope.updateProperty = (property:Models.PropertyModel):void => {
+ this.openEditPropertyModal(property);
+ };
+
+ this.$scope.deleteProperty = (property:Models.PropertyModel):void => {
+
+ let onOk = ():void => {
+ this.$scope.currentComponent.deleteProperty(property.uniqueId);
+ };
+
+ let title:string = this.$filter('translate')("PROPERTY_VIEW_DELETE_MODAL_TITLE");
+ let message:string = this.$filter('translate')("PROPERTY_VIEW_DELETE_MODAL_TEXT", "{'name': '" + property.name + "'}");
+ this.ModalsHandler.openConfirmationModal(title, message, false).then(onOk);
+ };
+
+ this.$scope.viewAttribute = (attribute:Models.AttributeModel):void => {
+ this.openAttributeModal(attribute);
+ };
+
+ this.$scope.groupNameByKey = (key:string):string => {
+ switch (key){
+ case 'derived':
+ return "Derived";
+
+ case this.$scope.currentComponent.uniqueId:
+ return this.$filter("resourceName")(this.$scope.currentComponent.name);
+
+ default:
+ return this.$filter("resourceName")((_.find(this.$scope.currentComponent.componentInstances, {uniqueId:key})).name);
+ }
+ };
+
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/properties-and-attributes/properties-view.html b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/properties-and-attributes/properties-view.html
new file mode 100644
index 0000000000..3022ee6e90
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/properties-and-attributes/properties-view.html
@@ -0,0 +1,81 @@
+<perfect-scrollbar class="w-sdc-designer-sidebar-tab-content properties">
+ <div class="w-sdc-designer-sidebar-section">
+
+ <!--expand-collapse data-ng-if="isPropertyOwner() && !currentComponent.properties.length" expanded-selector=".w-sdc-composition-sidebar-section-content.{{currentComponent.name}}"
+ class="w-sdc-composition-sidebar-section-title">
+ <span class="w-sdc-composition-sidebar-section-title-text" tooltips tooltip-content="{{currentComponent.name | resourceName}}&nbsp;Properties"
+ data-ng-bind="(currentComponent.name | resourceName)+ ' Properties'"></span>
+ <div class="w-sdc-composition-sidebar-section-title-icon"></div>
+ </expand-collapse-->
+
+
+ <!--properties-->
+ <expand-collapse data-ng-repeat-start="(key, group) in properties"
+ expanded-selector=".w-sdc-designer-sidebar-section-content.properties.{{$index}}" class="w-sdc-designer-sidebar-section-title">
+ <span class="w-sdc-designer-sidebar-section-title-text" data-ng-bind="groupNameByKey(key) + ' Properties'"
+ tooltips tooltip-content="{{groupNameByKey(key)}}&nbsp;Properties"></span>
+ <div class="w-sdc-designer-sidebar-section-title-icon"></div>
+ </expand-collapse>
+
+ <div data-ng-repeat-end="" class="w-sdc-designer-sidebar-section-content properties {{$index}}"> <!--data-ng-show="isShowDetailsSection" -->
+ <div class="i-sdc-designer-sidebar-section-content-item">
+ <div class="i-sdc-designer-sidebar-section-content-item-property-and-attribute" data-tests-id="propertyRow"
+ data-ng-repeat="property in group | orderBy: 'name' track by $index">
+ <div>
+ <span class="i-sdc-designer-sidebar-section-content-item-property-and-attribute-label"
+ data-ng-class="{'hand enabled': !$parent.isViewOnly}"
+ tooltips tooltip-content="{{property.name}}"
+ data-ng-click="!$parent.isViewOnly && updateProperty(property)">{{property.name}}</span>
+ </div>
+ <div>
+ <span class="i-sdc-designer-sidebar-section-content-item-property-value" data-ng-if="isPropertyOwner()"
+ tooltips tooltip-content="{{property.defaultValue}}">{{property.defaultValue}}</span>
+ <span class="i-sdc-designer-sidebar-section-content-item-property-value" data-ng-if="!isPropertyOwner()"
+ tooltips tooltip-content="{{property.value}}">{{property.value}}</span>
+ </div>
+ <button class="i-sdc-designer-sidebar-section-content-item-button delete sprite e-sdc-small-icon-delete"
+ data-ng-if="!$parent.isViewOnly&&(isPropertyOwner() && !property.readonly)"
+ data-ng-click="deleteProperty(property)" type="button"></button>
+ </div>
+ </div>
+
+ </div>
+ <div class="w-sdc-designer-sidebar-section-footer">
+ <button class="w-sdc-designer-sidebar-section-footer-action tlv-btn blue" data-ng-click="addProperty()" type="button" data-ng-if="!$parent.isViewOnly && isPropertyOwner()">
+ Add Property
+ </button>
+ </div>
+
+
+ <!--attributes-->
+ <expand-collapse data-ng-repeat-start="(key, group) in attributes"
+ expanded-selector=".w-sdc-designer-sidebar-section-content.attributes.{{$index}}" class="w-sdc-designer-sidebar-section-title">
+ <span class="w-sdc-designer-sidebar-section-title-text" data-ng-bind="groupNameByKey(key) + ' Attributes'"
+ tooltips tooltip-content="{{groupNameByKey(key)}}&nbsp;Attributes"></span>
+ <div class="w-sdc-designer-sidebar-section-title-icon"></div>
+ </expand-collapse>
+
+ <div data-ng-repeat-end="" class="w-sdc-designer-sidebar-section-content attributes {{$index}}"> <!--data-ng-show="isShowDetailsSection" -->
+ <div class="i-sdc-designer-sidebar-section-content-item">
+ <div class="i-sdc-designer-sidebar-section-content-item-property-and-attribute"
+ data-ng-repeat="attribute in group | orderBy: 'name' track by $index">
+ <div>
+ <span class="i-sdc-designer-sidebar-section-content-item-property-and-attribute-label"
+ data-ng-class="{'hand enabled': !$parent.isViewOnly}"
+ tooltips tooltip-content="{{attribute.name}}"
+ data-ng-click="!$parent.isViewOnly && viewAttribute(attribute)"
+ data-tests-id="{{attribute.name}}-attr">{{attribute.name}}</span>
+ </div>
+ <div>
+ <span class="i-sdc-designer-sidebar-section-content-item-property-value" data-ng-if="isPropertyOwner()"
+ tooltips tooltip-content="{{attribute.defaultValue}}">{{attribute.defaultValue}}</span>
+ <span class="i-sdc-designer-sidebar-section-content-item-property-value" data-ng-if="!isPropertyOwner()"
+ tooltips tooltip-content="{{attribute.value}}" data-tests-id="value-of-{{attribute.name}}">{{attribute.value}}</span>
+ </div>
+ </div>
+ </div>
+
+ </div>
+
+ </div>
+</perfect-scrollbar>
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/properties-and-attributes/properties.less b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/properties-and-attributes/properties.less
new file mode 100644
index 0000000000..2ad87b9fca
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/properties-and-attributes/properties.less
@@ -0,0 +1,16 @@
+.w-sdc-designer-sidebar-tab-content.properties {
+ .i-sdc-designer-sidebar-section-content-item-property-and-attribute-label{
+ font-weight: bold;
+ }
+ .i-sdc-designer-sidebar-section-content-item-button.update{
+ right: 17px;
+ }
+ .i-sdc-designer-sidebar-section-content-item-button.delete{
+ right: 35px;
+ }
+
+ .w-sdc-designer-sidebar-properties-disabled {
+ .s_14_m;
+ padding: 20px 20px;
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/relations/relations-view-model.ts b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/relations/relations-view-model.ts
new file mode 100644
index 0000000000..119a59d5af
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/relations/relations-view-model.ts
@@ -0,0 +1,81 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../../../../../references"/>>
+module Sdc.ViewModels {
+ 'use strict';
+
+ interface IRelationsViewModelScope extends ICompositionViewModelScope {
+ isLoading: boolean;
+ $parent: ICompositionViewModelScope;
+ getRelation(requirement:any): any;
+ }
+
+ export class RelationsViewModel {
+
+ static '$inject' = [
+ '$scope',
+ '$filter'
+ ];
+
+ constructor(private $scope:IRelationsViewModelScope,
+ private $filter:ng.IFilterService) {
+ this.initScope();
+ }
+
+
+ private updateRC = ():void =>{
+ if(this.$scope.currentComponent) {
+ this.$scope.currentComponent.updateRequirementsCapabilities();
+ }
+ };
+
+ private initScope = ():void => {
+
+ this.$scope.isLoading = this.$scope.$parent.isLoading;
+
+ this.$scope.getRelation = (requirement:any):any => {
+
+ if(this.$scope.isComponentInstanceSelected() && this.$scope.currentComponent.componentInstancesRelations ) {
+ let relationItem = _.filter(this.$scope.currentComponent.componentInstancesRelations, (relation:any) => {
+ return relation.fromNode === this.$scope.currentComponent.selectedInstance.uniqueId &&
+ _.some(relation.relationships, {'requirement': requirement.name,
+ 'requirementOwnerId': requirement.ownerId});
+ });
+
+ if (relationItem && relationItem.length) {
+ return {
+ type: requirement.relationship.split('.').pop(),
+ requirementName: this.$filter('resourceName')(this.$scope.currentComponent.componentInstances[_.map
+ (this.$scope.currentComponent.componentInstances, "uniqueId").indexOf(relationItem[0].toNode)].name)
+ };
+ }
+ }
+ return null;
+ };
+
+ if(!this.$scope.isComponentInstanceSelected()) {
+ this.$scope.$watch('currentComponent.componentInstances + currentComponent.componentInstancesRelations', ():void => {
+ this.updateRC();
+ });
+
+ }
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/relations/relations-view.html b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/relations/relations-view.html
new file mode 100644
index 0000000000..72eaae27cf
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/relations/relations-view.html
@@ -0,0 +1,57 @@
+<perfect-scrollbar class="w-sdc-designer-sidebar-tab-content relations">
+
+ <div class="w-sdc-designer-sidebar-section w-sdc-designer-sidebar-section-relations">
+ <expand-collapse expanded-selector=".w-sdc-designer-sidebar-section-content.capabilities" class="w-sdc-designer-sidebar-section-title">
+ Capabilities
+ <div class="w-sdc-designer-sidebar-section-title-icon"></div>
+ </expand-collapse>
+
+ <div class="w-sdc-designer-sidebar-section-content capabilities">
+ <div class="i-sdc-designer-sidebar-section-content-item">
+ <div class="i-sdc-designer-sidebar-section-content-item-relations-group" data-ng-repeat="(key, value) in (isComponentInstanceSelected() ? currentComponent.selectedInstance.capabilities : selectedComponent.capabilities) track by $index">
+ <div class="i-sdc-designer-sidebar-section-content-item-relations" data-ng-repeat="capability in value track by $index">
+ <div class="i-sdc-designer-sidebar-section-content-item-relations-details">
+ <div class="i-sdc-designer-sidebar-section-content-item-relations-details-name">{{capability.name}}&nbsp;
+ <span ng-if="selectedComponent.isComplex() && capability.ownerName"
+ class="i-sdc-designer-sidebar-section-content-item-relations-details-ownerName"
+ tooltips tooltip-class="tooltip-custom break-word-tooltip"
+ tooltip-content="{{capability.ownerName | resourceName}}">&nbsp;&nbsp;{{capability.ownerName | resourceName}}</span></div>
+ <div class="i-sdc-designer-sidebar-section-content-item-relations-details-desc">{{capability.type}}</div>
+ </div></div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div class="w-sdc-designer-sidebar-section w-sdc-designer-sidebar-section-relations">
+ <expand-collapse expanded-selector=".w-sdc-designer-sidebar-section-content.requirements" class="w-sdc-designer-sidebar-section-title">
+ Requirements
+ <div class="w-sdc-designer-sidebar-section-title-icon"></div>
+ </expand-collapse>
+
+ <div class="w-sdc-designer-sidebar-section-content requirements">
+ <div class="i-sdc-designer-sidebar-section-content-item">
+ <div class="i-sdc-designer-sidebar-section-content-item-relations-group" data-ng-repeat="(key, value) in (isComponentInstanceSelected() ? currentComponent.selectedInstance.requirements : selectedComponent.requirements) track by $index">
+ <div class="i-sdc-designer-sidebar-section-content-item-relations" data-ng-repeat="requirement in value track by $index">
+ <div class="i-sdc-designer-sidebar-section-content-item-relations-details">
+ <div class="i-sdc-designer-sidebar-section-content-item-relations-details-name">{{requirement.name}}&nbsp;
+ <span ng-if="selectedComponent.isComplex() && requirement.ownerName"
+ class="i-sdc-designer-sidebar-section-content-item-relations-details-ownerName"
+ tooltips tooltip-class="tooltip-custom break-word-tooltip"
+ tooltip-content="{{requirement.ownerName | resourceName}}">&nbsp;&nbsp;{{requirement.ownerName | resourceName}}</span></div>
+ <div class="i-sdc-designer-sidebar-section-content-item-relations-details-desc">{{requirement.node}}
+ <div data-ng-if="getRelation(requirement) != null">
+ <div class="i-sdc-designer-sidebar-section-content-item-relations-details-indent-box"></div>
+ <div class="i-sdc-designer-sidebar-section-content-item-relations-details-child">
+ <span class="i-sdc-designer-sidebar-section-content-item-relations-details-desc">{{getRelation(requirement).type}} <br/></span>
+ <span class="i-sdc-designer-sidebar-section-content-item-relations-details-name">{{getRelation(requirement).requirementName}}</span>
+ </div>
+ </div>
+ </div>
+ </div></div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+</perfect-scrollbar>
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/relations/relations.less b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/relations/relations.less
new file mode 100644
index 0000000000..212b9785e9
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/relations/relations.less
@@ -0,0 +1,116 @@
+.w-sdc-designer-sidebar-tab-content.relations {
+
+ .w-sdc-designer-sidebar-section-content {
+ padding: 0;
+ }
+
+ .w-sdc-designer-sidebar-section-title {
+ &.expanded {
+ margin-bottom: 0;
+ }
+ }
+
+ .i-sdc-designer-sidebar-section-content-item-relations {
+ border-bottom: 1px solid @color_e;
+ padding: 10px 10px 10px 18px;
+ position: relative;
+ cursor: default;
+
+ .i-sdc-designer-sidebar-section-content-item-button {
+ top: 15px;
+ line-height: 10px;
+ }
+
+ &:hover {
+ background-color: @color_c;
+
+ .i-sdc-designer-sidebar-section-content-item-button {
+ display: block;
+
+ }
+
+ }
+
+ }
+ .w-sdc-designer-sidebar-section-relations:not(:last-child) .i-sdc-designer-sidebar-section-content-item-relations-group:last-child .i-sdc-designer-sidebar-section-content-item-relations:last-child {
+ border-bottom: none;
+ }
+
+
+ .i-sdc-designer-sidebar-section-content-item-relations.hand {
+ .hand;
+ }
+
+ .i-sdc-designer-sidebar-section-content-item-relations-group {
+ //border-bottom: 1px solid @color_e;
+ }
+
+ .i-sdc-designer-sidebar-section-content-item-relations-details {
+ display: inline-block;
+ margin-left: 5px;
+ vertical-align: middle;
+ }
+
+ .i-sdc-designer-sidebar-section-content-item-relations-details-name {
+ .b_1;
+ .bold;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ //width: 100%;
+ text-transform: capitalize;
+ max-width: 240px;
+ display: inline-block;
+ }
+
+ .i-sdc-designer-sidebar-section-content-item-relations-details-ownerName {
+ .b_13;
+ font-weight:400;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+
+ &:before{
+ .sprite;
+ .arrow-left;
+ content: '';
+ }
+ }
+
+ .i-sdc-designer-sidebar-section-content-item-relations-details-desc {
+ .p_1;
+ line-height: 14px;
+ word-wrap: break-word;
+ white-space: normal;
+
+ .i-sdc-designer-sidebar-section-content-item-relations-details-indent-box{
+ border-left: 1px #848586 solid;
+ height: 40px;
+ margin-left: 2px;
+ margin-top: 5px;
+ border-bottom: 1px #848586 solid;
+ width: 25px;
+ float: left;
+ }
+ .i-sdc-designer-sidebar-section-content-item-relations-details-child{
+ margin-top: 30px;
+ float: left;
+ padding-left: 10px;
+ max-width: 230px;
+ }
+ }
+
+ .i-sdc-designer-sidebar-section-content-item-relations-details-desc-label {
+ font-family: omnes-medium, sans-serif;
+ }
+
+ .i-sdc-designer-sidebar-section-content-item-relations-view {
+ position: absolute;
+ right: 0;
+ top: 22px;
+ display: none;
+ }
+
+}
+
+
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/structure/structure-view.html b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/structure/structure-view.html
new file mode 100644
index 0000000000..2070041990
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/structure/structure-view.html
@@ -0,0 +1,13 @@
+<perfect-scrollbar include-padding="true" class="w-sdc-designer-sidebar-tab-content">
+
+ <div class="w-sdc-designer-sidebar-section">
+ <expand-collapse expanded-selector=".w-sdc-designer-sidebar-section-content" class="w-sdc-designer-sidebar-section-title">
+ Composition
+ <div class="w-sdc-designer-sidebar-section-title-icon"></div>
+ </expand-collapse>
+
+ <div class="w-sdc-designer-sidebar-section-content" ng-show="selectedComponent.isComplex()">
+ <structure-tree component="selectedComponent"></structure-tree>
+ </div>
+ </div>
+</perfect-scrollbar>
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/structure/structure-view.ts b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/structure/structure-view.ts
new file mode 100644
index 0000000000..daeab7f2f3
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/composition/tabs/structure/structure-view.ts
@@ -0,0 +1,34 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../../../../../references"/>
+module Sdc.ViewModels {
+ 'use strict';
+
+ interface IStructureViewModel extends ICompositionViewModelScope {}
+
+ export class StructureViewModel {
+ static '$inject' = [
+ '$scope'
+ ];
+
+ constructor(private $scope:IStructureViewModel) {
+ }
+ }
+ }
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/deployment-artifacts/deployment-artifacts-view-model.ts b/catalog-ui/app/scripts/view-models/workspace/tabs/deployment-artifacts/deployment-artifacts-view-model.ts
new file mode 100644
index 0000000000..43511e2deb
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/deployment-artifacts/deployment-artifacts-view-model.ts
@@ -0,0 +1,253 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../../../references"/>
+module Sdc.ViewModels {
+ 'use strict';
+ import Resource = Sdc.Models.Components.Resource;
+ import ArtifactModel = Sdc.Models.ArtifactModel;
+
+ interface IDeploymentArtifactsViewModelScope extends IWorkspaceViewModelScope {
+ tableHeadersList: Array<any>;
+ reverse: boolean;
+ sortBy:string;
+ artifacts: Array<Models.ArtifactModel>;
+ editForm:ng.IFormController;
+ isLoading:boolean;
+ artifactDescriptions:any;
+ updateInProgress:boolean;
+
+ addOrUpdate(artifact:Models.ArtifactModel): void;
+ update(artifact:Models.ArtifactModel): void;
+ delete(artifact:Models.ArtifactModel): void;
+ sort(sortBy:string): void;
+ noArtifactsToShow():boolean;
+ getValidationPattern(validationType:string, parameterType?:string):RegExp;
+ validateJson(json:string):boolean;
+ resetValue(parameter:any):void;
+ viewModeOrCsarComponent():boolean;
+ isLicenseArtifact(artifact:Models.ArtifactModel): boolean;
+
+ }
+
+ export class DeploymentArtifactsViewModel {
+
+ static '$inject' = [
+ '$scope',
+ '$filter',
+ '$modal',
+ '$templateCache',
+ 'ValidationUtils',
+ 'ArtifactsUtils',
+ 'ModalsHandler'
+ ];
+
+ constructor(private $scope:IDeploymentArtifactsViewModelScope,
+ private $filter:ng.IFilterService,
+ private $modal:ng.ui.bootstrap.IModalService,
+ private $templateCache:ng.ITemplateCacheService,
+ private validationUtils:Sdc.Utils.ValidationUtils,
+ private artifactsUtils:Sdc.Utils.ArtifactsUtils,
+ private ModalsHandler:Utils.ModalsHandler) {
+ this.initScope();
+ this.$scope.updateSelectedMenuItem();
+ }
+
+ private initDescriptions = ():void => {
+ this.$scope.artifactDescriptions = {};
+ _.forEach(this.$scope.component.deploymentArtifacts, (artifact:Models.ArtifactModel):void => {
+ this.$scope.artifactDescriptions[artifact.artifactLabel] = artifact.description;
+ });
+ };
+
+
+ private setArtifact = (artifact:Models.ArtifactModel):void => {
+ if (artifact.heatParameters) {
+ artifact.heatParameters.forEach((parameter:any):void => {
+ if (!parameter.currentValue && parameter.defaultValue) {
+ parameter.currentValue = parameter.defaultValue;
+ } else if ("" === parameter.currentValue) {
+ parameter.currentValue = null;
+ }
+ });
+ }
+ if (!artifact.description || !this.$scope.getValidationPattern('string').test(artifact.description)) {
+ artifact.description = this.$scope.artifactDescriptions[artifact.artifactLabel];
+ }
+ };
+
+ private updateAll = ():void => {
+ let artifacts:Array<Models.ArtifactModel> = [];
+ _.forEach(this.$scope.component.deploymentArtifacts, (artifact:Models.ArtifactModel):void => {
+ if (artifact.selected) {
+ this.setArtifact(artifact);
+ artifacts.push(artifact);
+ }
+ });
+ this.$scope.component.updateMultipleArtifacts(artifacts);
+ };
+
+
+ private initScope = ():void => {
+ let self = this;
+ this.$scope.isLoading = false;
+ this.$scope.updateInProgress = false;
+ this.initDescriptions();
+ this.$scope.artifacts = <ArtifactModel[]>_.values(this.$scope.component.deploymentArtifacts);
+ this.$scope.setValidState(true);
+
+ this.$scope.tableHeadersList = [
+ {title: 'Name', property: 'artifactDisplayName'},
+ {title: 'Type', property: 'artifactType'},
+ {title: 'Deployment timeout', property: 'timeout'}
+ ];
+
+ this.$scope.isLicenseArtifact = (artifact:Models.ArtifactModel) :boolean => {
+ let isLicense:boolean = false;
+ if(this.$scope.component.isResource() && (<Resource>this.$scope.component).isCsarComponent()) {
+
+ isLicense = this.artifactsUtils.isLicenseType(artifact.artifactType);
+ }
+
+ return isLicense;
+ };
+
+ this.$scope.sort = (sortBy:string):void => {
+ this.$scope.reverse = (this.$scope.sortBy === sortBy) ? !this.$scope.reverse : false;
+ this.$scope.sortBy = sortBy;
+ };
+
+ this.$scope.getValidationPattern = (validationType:string, parameterType?:string):RegExp => {
+ return this.validationUtils.getValidationPattern(validationType, parameterType);
+ };
+
+ this.$scope.validateJson = (json:string):boolean => {
+ if(!json){
+ return true;
+ }
+ return this.validationUtils.validateJson(json);
+ };
+
+ this.$scope.viewModeOrCsarComponent = ():boolean => {
+ return this.$scope.isViewMode() || (this.$scope.component.isResource() && (<Resource>this.$scope.component).isCsarComponent());
+ };
+
+
+ this.$scope.addOrUpdate = (artifact:Models.ArtifactModel):void => {
+ artifact.artifactGroupType = 'DEPLOYMENT';
+ let artifactCopy = new Models.ArtifactModel(artifact);
+
+ let success = (response:any):void => {
+ self.$scope.artifactDescriptions[artifactCopy.artifactLabel] = artifactCopy.description;
+ self.$scope.artifacts = <ArtifactModel[]>_.values(self.$scope.component.deploymentArtifacts);
+ };
+
+ let error = (err:any):void =>{
+ console.log(err);
+ self.$scope.artifacts = <ArtifactModel[]>_.values(self.$scope.component.deploymentArtifacts);
+ };
+
+
+ this.ModalsHandler.openWizardArtifactModal(artifactCopy, self.$scope.component).then(success, error);
+ };
+
+ this.$scope.noArtifactsToShow = ():boolean => {
+ return !_.some(this.$scope.artifacts, 'esId');
+ };
+
+
+ this.$scope.resetValue = (parameter:any):void => {
+ if (!parameter.currentValue && parameter.defaultValue) {
+ parameter.currentValue = parameter.defaultValue;
+ }
+ else if ('boolean' == parameter.type) {
+ parameter.currentValue = parameter.currentValue.toUpperCase();
+ }
+ };
+
+
+ this.$scope.$watch('editForm.$valid', ():void => {
+ if (this.$scope.editForm) {
+ // this.$scope.setValidState(this.$scope.editForm.$valid);
+ }
+ });
+
+ this.$scope.update = (artifact:Models.ArtifactModel):void => {
+ if (false == this.$scope.isLoading) {
+ if (artifact.selected && !this.$scope.isViewMode()) {
+ this.$scope.isLoading = true;
+ this.$scope.updateInProgress = true;
+ let onSuccess = (responseArtifact:Models.ArtifactModel):void => {
+ this.$scope.artifactDescriptions[responseArtifact.artifactLabel] = responseArtifact.description;
+ this.$scope.artifacts = <ArtifactModel[]>_.values(this.$scope.component.deploymentArtifacts);
+ this.$scope.isLoading = false;
+ artifact.selected = !artifact.selected;
+ this.$scope.updateInProgress = false;
+ };
+
+ let onFailed = (error:any):void => {
+ console.log('Delete artifact returned error:', error);
+ this.$scope.isLoading = false;
+ artifact.selected = !artifact.selected;
+ this.$scope.updateInProgress = false;
+ };
+
+ this.setArtifact(artifact);
+ this.$scope.component.addOrUpdateArtifact(artifact).then(onSuccess, onFailed);
+ } else {
+ artifact.selected = !artifact.selected;
+
+ }
+ }
+ };
+
+ this.$scope.delete = (artifact:Models.ArtifactModel):void => {
+ let onOk = ():void => {
+ this.$scope.isLoading = true;
+ let onSuccess = ():void => {
+ this.$scope.isLoading = false;
+ this.$scope.artifacts = <ArtifactModel[]>_.values(this.$scope.component.deploymentArtifacts);
+ };
+
+ let onFailed = (error:any):void => {
+ this.$scope.isLoading = false;
+ console.log('Delete artifact returned error:', error);
+ };
+
+ this.$scope.component.deleteArtifact(artifact.uniqueId, artifact.artifactLabel).then(onSuccess, onFailed);
+ };
+
+ let title:string = self.$filter('translate')("ARTIFACT_VIEW_DELETE_MODAL_TITLE");
+ let message:string = self.$filter('translate')("ARTIFACT_VIEW_DELETE_MODAL_TEXT", "{'name': '" + artifact.artifactDisplayName + "'}");
+ this.ModalsHandler.openConfirmationModal(title, message, false).then(onOk);
+ };
+ };
+
+ public save = (callback:Function):void => {
+ this.updateAll();
+ this.$scope.setComponent(this.$scope.component);
+ callback(true);
+ };
+
+ public back = (callback:Function):void => {
+ this.$scope.setComponent(this.$scope.component);
+ callback(true);
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/deployment-artifacts/deployment-artifacts-view.html b/catalog-ui/app/scripts/view-models/workspace/tabs/deployment-artifacts/deployment-artifacts-view.html
new file mode 100644
index 0000000000..1547618134
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/deployment-artifacts/deployment-artifacts-view.html
@@ -0,0 +1,149 @@
+<div class="workspace-deployment-artifact">
+
+ <div data-tests-id="add-deployment-artifact-button" ng-if="!isViewMode()" data-ng-class="{'disabled': isDisableMode()}" data-tests-id="add-property-button" class="add-btn" data-ng-click="addOrUpdate({})">Add</div>
+
+ <div class="table-container-flex">
+
+ <div class="table" data-ng-class="{'view-mode': isViewMode()}">
+ <loader data-display="isLoading"></loader>
+ <div class="head flex-container">
+ <div class="table-header head-row hand flex-item" data-ng-repeat="header in tableHeadersList track by $index" data-ng-click="sort(header.property)">{{header.title}}
+ <span data-ng-if="sortBy === header.property" class="table-header-sort-arrow" data-ng-class="{'down': reverse, 'up':!reverse}"> </span>
+ </div>
+ <div class="table-no-text-header head-row flex-item"></div>
+ </div>
+
+ <form class="body" name="editForm">
+
+ <perfect-scrollbar scroll-y-margin-offset="0" include-padding="true" class="scrollbar-container">
+
+ <!-- Artifact row -->
+ <div ng-if="noArtifactsToShow()" data-ng-class="{'disabled': isDisableMode()}" class="no-row-text" translate="DEPLOYMENT_ARTIFACT_NO_ARTIFACTS_TO_DISPLAY"></div>
+ <div data-ng-repeat-start="artifact in artifacts | orderBy:sortBy:reverse track by $index"
+ class="flex-container data-row"
+ data-ng-class="{'selected': artifact.selected || undefined==artifact.selected && updateInProgress}"
+ data-ng-if="artifact.esId">
+
+ <div class="table-col-general flex-item" data-ng-click="update(artifact)">
+
+ <span class="sprite table-arrow" data-tests-id="{{artifact.artifactDisplayName}}" data-ng-class="{'opened': artifact.selected || undefined==artifact.selected && updateInProgress}"></span>
+ {{artifact.artifactDisplayName}}
+ </div>
+
+ <div class="table-col-general flex-item" data-tests-id="{{artifact.artifactType}}">
+ {{artifact.artifactType}}
+ </div>
+ <div class="table-col-general flex-item" data-tests-id="{{artifact.timeout}}">
+ {{artifact.timeout? artifact.timeout:''}}
+ </div>
+
+ <div class="table-btn-col flex-item">
+ <button class="table-edit-btn" data-tests-id="edit_{{artifact.artifactDisplayName}}"
+ data-ng-if="!isViewMode() && !artifact.isHEAT() && !artifact.isThirdParty() && !isLicenseArtifact(artifact)" data-ng-click="addOrUpdate(artifact)"></button>
+ <button class="table-delete-btn" data-tests-id="delete_{{artifact.artifactDisplayName}}"
+ data-ng-if="!isViewMode() && !artifact.isHEAT() && !artifact.isThirdParty() && !isLicenseArtifact(artifact)" data-ng-click="delete(artifact)"> </button>
+ <button class="table-download-btn" download-artifact data-tests-id="download_{{artifact.artifactDisplayName}}"
+ data-ng-if="artifact.artifactName" component="component" artifact="artifact"></button>
+
+
+ </div>
+ </div>
+ <div data-ng-repeat-end="" data-ng-if="artifact.selected || undefined==artifact.selected && updateInProgress" class="w-sdc-form item-opened">
+ <!-- Artifact panel opened -->
+
+ <!-- Description field -->
+ <div class="w-sdc-form-item" ng-form="descriptionForm" data-ng-class="{error:(descriptionForm.$dirty && descriptionForm.$invalid)}">
+ <label class="i-sdc-env-form-label required">Description</label>
+ <textarea class="i-sdc-form-textarea {{$index}}" data-ng-class="{'view-mode': isViewMode()}"
+ data-ng-maxlength="256"
+ maxlength="256"
+ data-ng-required="true"
+ name="description"
+ data-ng-model="artifact.description"
+ data-ng-model-options="{ debounce: 200 }"
+ data-ng-pattern="getValidationPattern('string')"
+ ng-readonly="isViewMode()"
+ data-tests-id="description">
+ </textarea>
+
+ <div class="input-error" data-ng-show="descriptionForm.$dirty && descriptionForm.$invalid">
+ <span ng-show="descriptionForm.$error.required" translate="ADD_ARTIFACT_ERROR_DESCRIPTION_REQUIRED"></span>
+ <span ng-show="descriptionForm.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '256' }"></span>
+ <span ng-show="descriptionForm.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span>
+ </div>
+
+ </div>
+
+ <!-- Parameters in 2 columns -->
+ <div class="w-sdc-form-columns-wrapper" data-ng-if="artifact.heatParameters">
+ <!-- Left column -->
+ <div class="w-sdc-form-column">
+ <div class="i-sdc-form-item" ng-form="parameterForm"
+ data-ng-repeat="parameter in artifact.heatParameters.slice(0, artifact.heatParameters.length%2+artifact.heatParameters.length/2) track by $index">
+ <label class="i-sdc-env-form-label" data-ng-class="{required:parameter.defaultValue}" tooltip-side="top" sdc-smart-tooltip>{{parameter.name +' (' + parameter.type + ')'}}</label>
+ <span class="parameter-description" tooltips tooltip-side="top" tooltip-content="{{parameter.description}}">?</span>
+ <input class="i-sdc-form-input" data-ng-class="{error:(parameterForm.currentValue.$invalid),'view-mode': isViewMode() }"
+ data-ng-model-options="{ debounce: 200 }"
+ data-ng-model="parameter.currentValue"
+ type="text"
+ name="currentValue"
+ data-ng-pattern="getValidationPattern(parameter.type, 'heat')"
+ data-ng-required="parameter.defaultValue"
+ data-ng-change="'json'==parameter.type && parameterForm.currentValue.$setValidity('pattern', validateJson(parameter.currentValue))"
+ data-ng-blur="!parameterForm.currentValue.$error.pattern && resetValue(parameter)"
+ />
+
+ <div class="input-error" data-ng-show="parameterForm.currentValue.$invalid">
+ <span ng-show="parameterForm.currentValue.$error.required" translate="VALIDATION_ERROR_REQUIRED" translate-values="{'field': 'Value'}"></span>
+ <span ng-show="parameterForm.currentValue.$error.pattern && parameter.type==='string'" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span>
+ <span ng-show="parameterForm.currentValue.$error.pattern && !(parameter.type==='string')" translate="VALIDATION_ERROR_TYPE" translate-values="{'type': '{{parameter.type}}'}"></span>
+ </div>
+ </div>
+ </div>
+
+ <!-- Right column -->
+ <div class="w-sdc-form-column">
+ <div class="i-sdc-form-item" ng-form="parameterForm" data-ng-repeat="parameter in artifact.heatParameters.slice(artifact.heatParameters.length%2+artifact.heatParameters.length/2) track by $index">
+ <label class="i-sdc-env-form-label" data-ng-class="{required:parameter.defaultValue}" tooltip-side="top" sdc-smart-tooltip>{{parameter.name +' (' + parameter.type + ')'}}</label>
+ <span class="parameter-description" tooltips tooltip-side="top" tooltip-content="{{parameter.description}}">?</span>
+ <input class="i-sdc-form-input" data-ng-class="{error:(parameterForm.currentValue.$invalid), 'view-mode': isViewMode()}"
+ data-ng-model-options="{ debounce: 200 }"
+ data-ng-model="parameter.currentValue"
+ type="text"
+ name="currentValue"
+ data-ng-pattern="getValidationPattern(parameter.type, 'heat')"
+ data-ng-required="parameter.defaultValue"
+ data-ng-change="'json'==parameter.type && parameterForm.currentValue.$setValidity('pattern', validateJson(parameter.currentValue))"
+ data-ng-blur="!parameterForm.currentValue.$error.pattern && resetValue(parameter)"
+ />
+
+ <div class="input-error" data-ng-show="parameterForm.currentValue.$invalid">
+ <span ng-show="parameterForm.currentValue.$error.required" translate="VALIDATION_ERROR_REQUIRED" translate-values="{'field': 'Value'}"></span>
+ <span ng-show="parameterForm.currentValue.$error.pattern && parameter.type==='string'" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span>
+ <span ng-show="parameterForm.currentValue.$error.pattern && !(parameter.type==='string')" translate="VALIDATION_ERROR_TYPE" translate-values="{'type': '{{parameter.type}}'}"></span>
+ </div>
+ </div>
+ </div>
+
+
+ </div><!-- Close: Parameters in 2 columns -->
+ </div><!-- Close: Artifact panel opened -->
+
+ <!-- Add artifacts buttons -->
+ <button class="add-button" data-ng-repeat="artifact in artifacts track by $index"
+ type="button"
+ data-ng-show="!artifact.esId"
+ data-ng-if="!viewModeOrCsarComponent()"
+ data-ng-class="{'disabled': isDisableMode() || component.isCsarComponent()}"
+ data-tests-id="{{artifact.artifactDisplayName}} deployment_artifact"
+ translate="DEPLOYMENT_ARTIFACT_BUTTON_ADD_HEAT"
+ translate-values="{'name': '{{artifact.artifactDisplayName}}'}"
+ data-ng-click="addOrUpdate(artifact)"></button>
+
+ <!-- Top add button -->
+ <button class="add-button" type="button" data-ng-if="!isViewMode()" data-ng-class="{'disabled': isDisableMode()}" translate="DEPLOYMENT_ARTIFACT_BUTTON_ADD_OTHER" data-ng-click="addOrUpdate({})"></button>
+ </perfect-scrollbar>
+ </form>
+ </div>
+ </div>
+</div>
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/deployment-artifacts/deployment-artifacts.less b/catalog-ui/app/scripts/view-models/workspace/tabs/deployment-artifacts/deployment-artifacts.less
new file mode 100644
index 0000000000..9f90a47d5a
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/deployment-artifacts/deployment-artifacts.less
@@ -0,0 +1,102 @@
+.workspace-deployment-artifact {
+ width: 93%;
+ display: inline-block;
+ .table-container-flex .table .body .data-row + div.item-opened {
+ align-items: center;
+ padding: 10px 40px 10px 30px;
+ }
+
+ .w-sdc-classic-btn {
+ float: right;
+ margin-bottom: 10px;
+ }
+
+ .table {
+ height:490px;
+ margin-bottom: 0;
+ }
+
+ .parameter-description {
+ .circle(18px, @color_p);
+ content: '?';
+ line-height: 18px;
+ vertical-align: middle;
+ margin-left: 5px;
+ cursor: default;
+ display: inline-block;
+ position: absolute;
+ top: 16px;
+ }
+
+ .table-container-flex {
+
+ margin-top: 27px;
+
+ .flex-item:nth-child(1) {
+ flex-grow: 15;
+ .hand;
+ span.table-arrow {
+ margin-right: 7px;
+ }
+ }
+
+ .flex-item:nth-child(2) {
+ flex-grow: 6;
+ }
+
+ .flex-item:nth-child(3) {
+ flex-grow: 9;
+ }
+
+ .flex-item:nth-child(4) {
+ flex-grow: 3;
+ padding-top: 10px;
+ }
+ }
+ .w-sdc-form{
+ text-align: left;
+
+ .w-sdc-env-params{
+ border-top: 1px solid #cdcdcd;
+ margin: 25px 0 10px 0;
+ }
+
+ .i-sdc-form-textarea {
+ border: 1px solid @color_e;
+ min-height: 60px;
+ padding: 10px 13px;
+ width: 100%;
+ resize: none;
+
+ }
+
+ .w-sdc-form-item {
+ &.error {
+ .i-sdc-form-input,
+ .i-sdc-form-select,
+ .i-sdc-form-textarea {
+ border-color: @color_h;
+ outline: none;
+ box-sizing: border-box;
+ }
+ }
+ }
+
+ .i-sdc-env-form-label{
+ font-family: @font-omnes-medium;
+ color: @main_color_m;
+ overflow: hidden;
+ max-width: 450px;
+ text-overflow: ellipsis;
+ display: inline-block;
+ white-space: nowrap;
+ margin-top: 14px;
+
+ &.required::before {
+ color: #f33;
+ content: '*';
+ margin-right: 4px;
+ }
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/deployment/deployment-view-model.ts b/catalog-ui/app/scripts/view-models/workspace/tabs/deployment/deployment-view-model.ts
new file mode 100644
index 0000000000..f8afc0b758
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/deployment/deployment-view-model.ts
@@ -0,0 +1,127 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../../../references"/>
+module Sdc.ViewModels {
+ 'use strict';
+
+ export interface IDeploymentViewModelScope extends IWorkspaceViewModelScope {
+
+ currentComponent: Models.Components.Component;
+ selectedComponent: Models.Components.Component;
+ isLoading: boolean;
+ sharingService:Sdc.Services.SharingService;
+ sdcMenu:Models.IAppMenu;
+ version:string;
+ isViewOnly:boolean;
+ tabs:Array<Models.Tab>;
+
+ setComponent(component: Models.Components.Component);
+ isComponentInstanceSelected():boolean;
+ updateSelectedComponent(): void
+ openUpdateModal();
+ deleteSelectedComponentInstance():void;
+ onBackgroundClick():void;
+ setSelectedInstance(componentInstance: Models.ComponentsInstances.ComponentInstance): void;
+ printScreen():void;
+
+ }
+
+ export class DeploymentViewModel {
+
+ static '$inject' = [
+ '$scope',
+ 'sdcMenu',
+ 'MenuHandler',
+ '$modal',
+ '$templateCache',
+ '$state',
+ 'Sdc.Services.SharingService',
+ '$filter',
+ 'Sdc.Services.CacheService',
+ 'ComponentFactory',
+ 'ChangeLifecycleStateHandler',
+ 'LeftPaletteLoaderService',
+ 'ModalsHandler'
+ ];
+
+ constructor(private $scope:IDeploymentViewModelScope,
+ private sdcMenu:Models.IAppMenu,
+ private MenuHandler: Utils.MenuHandler,
+ private $modal:ng.ui.bootstrap.IModalService,
+ private $templateCache:ng.ITemplateCacheService,
+ private $state:ng.ui.IStateService,
+ private sharingService:Services.SharingService,
+ private $filter:ng.IFilterService,
+ private cacheService:Services.CacheService,
+ private ComponentFactory: Utils.ComponentFactory,
+ private ChangeLifecycleStateHandler: Sdc.Utils.ChangeLifecycleStateHandler,
+ private LeftPaletteLoaderService: Services.Components.LeftPaletteLoaderService,
+ private ModalsHandler: Sdc.Utils.ModalsHandler) {
+
+ this.$scope.setValidState(true);
+ this.initScope();
+ this.$scope.updateSelectedMenuItem();
+ }
+
+
+ private initComponent = ():void => {
+
+ this.$scope.currentComponent = this.$scope.component;
+ this.$scope.selectedComponent = this.$scope.currentComponent;
+ this.updateUuidMap();
+ this.$scope.isViewOnly = this.$scope.isViewMode();
+ };
+
+
+ private updateUuidMap = ():void => {
+ /**
+ * In case user press F5, the page is refreshed and this.sharingService.currentEntity will be undefined,
+ * but after loadService or loadResource this.sharingService.currentEntity will be defined.
+ * Need to update the uuidMap with the new resource or service.
+ */
+ this.sharingService.addUuidValue(this.$scope.currentComponent.uniqueId,this.$scope.currentComponent.uuid);
+ };
+
+ private initRightTabs = ()=> {
+ if(this.$scope.currentComponent.groups){
+
+ let hierarchyTab = new Models.Tab('/app/scripts/view-models/tabs/hierarchy/hierarchy-view.html', 'Sdc.ViewModels.HierarchyViewModel', 'hierarchy', this.$scope.currentComponent, 'hierarchy');
+ this.$scope.tabs = Array<Models.Tab>();
+ this.$scope.tabs.push(hierarchyTab)
+ }
+
+ }
+ private initScope = ():void => {
+
+ this.$scope.sharingService = this.sharingService;
+ this.$scope.sdcMenu = this.sdcMenu;
+ this.$scope.isLoading = false;
+
+ this.$scope.version = this.cacheService.get('version');
+ this.initComponent();
+
+ this.$scope.setComponent = (component: Models.Components.Product):void => {
+ this.$scope.currentComponent = component;
+ }
+
+ this.initRightTabs();
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/deployment/deployment-view.html b/catalog-ui/app/scripts/view-models/workspace/tabs/deployment/deployment-view.html
new file mode 100644
index 0000000000..9e26656f5f
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/deployment/deployment-view.html
@@ -0,0 +1,10 @@
+<div class="deployment-view">
+ <div class="w-sdc-deployment-canvas" data-ng-class="{sidebaractive: displayDesignerRightSidebar}">
+ <!--<module-graph data-tests-id="canvas" current-component="currentComponent" is-view-only="isViewOnly" is-loading="isLoading"></module-graph>-->
+ <deployment-graph component="currentComponent" is-view-only="isViewOnly"></deployment-graph>
+ </div>
+
+ <div class="w-sdc-deployment-right-bar">
+ <sdc-tabs tabs="tabs" is-view-only="isViewOnly"></sdc-tabs>
+ </div>
+</div>
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/deployment/deployment.less b/catalog-ui/app/scripts/view-models/workspace/tabs/deployment/deployment.less
new file mode 100644
index 0000000000..0439ccd0fa
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/deployment/deployment.less
@@ -0,0 +1,33 @@
+.deployment-view{
+
+ display: inline-block;
+ text-align: left;
+ align-items: left;
+ padding: 0;
+ width: 100%;
+ height: 100%;
+
+ .w-sdc-deployment-canvas {
+ .noselect;
+ .bg_c;
+ bottom: 0;
+ width: 100%;
+ height: 100%;
+
+ .view-mode{
+ background-color: #f8f8f8;
+ border:0;
+ }
+ }
+
+ .w-sdc-deployment-right-bar {
+
+ .noselect;
+ bottom: 0;
+ position: absolute;
+ right: 0px;
+ transition: right 0.2s;
+ z-index: 10000;
+ top: @action_nav_height;
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/distribution/disribution-status-modal/disribution-status-modal-view-model.ts b/catalog-ui/app/scripts/view-models/workspace/tabs/distribution/disribution-status-modal/disribution-status-modal-view-model.ts
new file mode 100644
index 0000000000..c0d6aba915
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/distribution/disribution-status-modal/disribution-status-modal-view-model.ts
@@ -0,0 +1,67 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../../../../references"/>
+module Sdc.ViewModels {
+ 'use strict';
+
+ interface IDistributionStatusModalViewModelScope {
+ distribution:Models.Distribution;
+ status:string;
+ getStatusCount(distributionComponent:Array<Models.DistributionComponent>):any;
+ getUrlName(url:string):string;
+ modalDitributionStatus:ng.ui.bootstrap.IModalServiceInstance;
+ footerButtons: Array<any>;
+ close(): void;
+ }
+
+ export class DistributionStatusModalViewModel {
+
+ static '$inject' = ['$scope','$modalInstance', 'data'];
+
+ constructor(private $scope:IDistributionStatusModalViewModelScope,
+ private $modalInstance:ng.ui.bootstrap.IModalServiceInstance,
+ private data:any
+ ) {
+ this.initScope();
+ }
+
+ private initScope = ():void => {
+ this.$scope.distribution = this.data.distribution;
+ this.$scope.status = this.data.status;
+ this.$scope.modalDitributionStatus = this.$modalInstance;
+
+ this.$scope.getUrlName = (url:string):string =>{
+ let urlName:string = _.last(url.split('/'));
+ return urlName;
+ };
+
+ this.$scope.close = ():void => {
+ this.$modalInstance.close();
+ };
+
+ this.$scope.footerButtons = [
+ {'name': 'Close', 'css': 'blue', 'callback': this.$scope.close }
+ ];
+
+ };
+
+
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/distribution/disribution-status-modal/disribution-status-modal-view.html b/catalog-ui/app/scripts/view-models/workspace/tabs/distribution/disribution-status-modal/disribution-status-modal-view.html
new file mode 100644
index 0000000000..b3393e99e0
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/distribution/disribution-status-modal/disribution-status-modal-view.html
@@ -0,0 +1,126 @@
+<sdc-modal modal="modalDitributionStatus" type="classic" class="w-sdc-classic-top-line-modal" buttons="footerButtons" header="Distribution by Status" show-close-button="true">
+
+ <div class="w-sdc-distribution-view">
+ <div class="w-sdc-distribution-view-header">
+
+ </div>
+
+
+ <perfect-scrollbar include-padding="true" class="w-sdc-distribution-view-content">
+ <div class="w-sdc-distribution-view-content-section w-sdc-distribute-parent-block">
+ <ul>
+ <li class="w-sdc-distribute-parent-block" >
+ <div class="w-sdc-distribute-row w-sdc-distribute-row-extends extends">
+
+ <div class="w-sdc-distribute-row-content">
+ <div class="w-sdc-distribute-content">
+ <div class="title-section item-1">
+ <div class="title">Distribution ID</div>
+ <div data-ng-bind="distribution.distributionID"></div>
+ </div>
+ <div class="title-section item-2">
+ <div class="title" translate="DISTRIBUTION_VIEW_TITLE_USER_ID"></div>
+ <div data-ng-bind="distribution.userId"></div>
+ </div>
+ <div class="title-section item-3">
+ <div class="title">Time[UTC]:</div>
+ <div
+ data-ng-bind="distribution.timestamp | stringToDateFilter | date: 'MM/dd/yyyy h:mma':'UTC'"></div>
+ </div>
+ <div class="title-section item-4">
+ <span class="sprite-new status-icon" data-ng-class="distribution.deployementStatus"></span>
+ <span class="sprite-new" data-ng-bind="distribution.deployementStatus"></span>
+ </div>
+ </div>
+ <div class="w-sdc-distribute-status-block" data-ng-show="distribution.statusCount">
+ <div class="status-item-1">Status: {{status}} <span data-ng-bind="(distribution.distributionComponents | filter:status:true).length"
+ class="blue-font"></span></div>
+
+ </div>
+ </div>
+ </div>
+
+ <ul class="w-sdc-distribute-components-block disable-hover">
+ <li data-ng-repeat="(omfComponentID,omfComponentList) in distribution.distributionComponents | orderBy: '-timestamp' | filter:status:true | groupBy:'omfComponentID'"
+ class="disable-hover">
+ <div class="w-sdc-distribute-row omf-component-row w-sdc-distribute-row-extends "
+ data-ng-class="{'extends': omfComponentListExtends}">
+ <div class="w-sdc-distribution-arrow-btn" data-ng-click="omfComponentListExtends=!omfComponentListExtends"
+ ng-class="{'extends': omfComponentListExtends}"
+ data-ng-init="omfComponentListExtends=false"
+ ></div>
+ <div class="w-sdc-distribute-status-block">
+ <div class="status-item-1">{{omfComponentID}} <span class="blue-font">{{omfComponentList.length}}</span>
+ </div>
+ </div>
+ </div>
+ <div data-ng-show="omfComponentListExtends"
+ class="w-sdc-distribute-omfComponent-block disable-hover">
+ <div class="w-sdc-distribute-row-extends disable-hover">
+ <div class="disable-hover">
+ <div class="w-sdc-distribute-row omfComponent-table-head">
+ <div class="title item-1">Component ID</div>
+ <div class="title item-2">Artifact Name</div>
+ <div class="title item-3">URL</div>
+ <div class="title item-4">Time(UTC)</div>
+ <div class="title item-5">Status</div>
+ </div>
+
+ <div class="w-sdc-distribute-row omfComponent-table-row"
+ data-ng-repeat-start="(url,urlList) in omfComponentList | orderBy: '-timestamp' | groupBy:'url'"
+ data-ng-class="urlListExtends?'extends row-{{$index}}':'row-{{$index}}'" >
+ <div class="w-sdc-distribute-cell item-1">
+ <div class="w-sdc-distribution-arrow-btn" data-ng-click="urlListExtends=!urlListExtends"
+ data-ng-class="{'extends': urlListExtends}"
+ data-ng-init="urlListListExtends=false"
+ ></div>
+ {{urlList[0].omfComponentID}}
+ </div>
+ <div class="w-sdc-distribute-cell item-2" sdc-smart-tooltip>
+ {{getUrlName(urlList[0].url)}}
+ </div>
+ <div class="w-sdc-distribute-cell item-3 disable-hover">
+ <div sdc-smart-tooltip class="distribution-url">{{urlList[0].url}}</div>
+ <div sdc-smart-tooltip title="Copy url" clipboard text="urlList[0].url"
+ class="sprite-new link-btn copy-link disable-hover"></div>
+ </div>
+ <div class="w-sdc-distribute-cell item-4"><span
+ data-ng-bind="urlList[0].timestamp | date: 'MM/dd/yyyy h:mma':'UTC'"></span>
+ </div>
+ <div class="w-sdc-distribute-cell item-5">{{urlList[0].status}}</div>
+ </div>
+
+
+ <div data-ng-repeat-end data-ng-show="urlListExtends" class="disable-hover">
+ <div class="w-sdc-distribute-row extends disable-hover">
+ <ul data-ng-show="urlListExtends"
+ class="w-sdc-distribute-url-block disable-hover">
+ <li data-ng-repeat="distributionComponent in urlList | orderBy: '-timestamp'"
+ class="disable-hover">
+ <span
+ data-ng-bind="distributionComponent.timestamp | date: 'MM/dd/yyyy h:mma':'UTC'"
+ class="disable-hover"></span>
+ <span
+ class="disable-hover">{{distributionComponent.status}}</span>
+ <span
+ class="disable-hover reason" data-ng-show="distributionComponent.status == 'NOT_NOTIFIED'">Reason: Component has determined artifact is not needed.</span>
+ <span
+ class="disable-hover reason" data-ng-show="distributionComponent.errorReason">Reason: {{distributionComponent.errorReason}}</span>
+ </li>
+ </ul>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </li>
+ </ul>
+ </li>
+ </ul>
+ </div>
+
+ </perfect-scrollbar>
+ </div>
+
+
+</sdc-modal>
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/distribution/disribution-status-modal/disribution-status-modal.less b/catalog-ui/app/scripts/view-models/workspace/tabs/distribution/disribution-status-modal/disribution-status-modal.less
new file mode 100644
index 0000000000..02321b6e2f
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/distribution/disribution-status-modal/disribution-status-modal.less
@@ -0,0 +1,33 @@
+.w-sdc-classic-top-line-modal {
+
+ .w-sdc-modal-head {
+ // border-bottom: none;
+ }
+ .w-sdc-distribution-view {
+
+ .w-sdc-distribution-view-content {
+ height: 500px;
+ }
+
+ .w-sdc-distribution-view-content-section {
+
+ .w-sdc-distribute-parent-block {
+ .w-sdc-distribute-components-block {
+
+ .omf-component-row {
+ .w-sdc-distribute-status-block {
+ margin-left: 0;
+ }
+
+ }
+ div {
+ padding-left: 0;
+ }
+ }
+
+ }
+
+ }
+ }
+}
+
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/distribution/distribution-view-model.ts b/catalog-ui/app/scripts/view-models/workspace/tabs/distribution/distribution-view-model.ts
new file mode 100644
index 0000000000..219585fc3d
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/distribution/distribution-view-model.ts
@@ -0,0 +1,135 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../../../references"/>
+
+module Sdc.ViewModels {
+ 'use strict';
+
+ interface IDistributionViewModel extends IWorkspaceViewModelScope{
+ modalDistribution:ng.ui.bootstrap.IModalServiceInstance;
+ service: Models.Components.Service;
+ distributions : Array<Models.Distribution>;
+ showComponents(distribution:Models.Distribution): void;
+ markAsDeployed(distribution:Models.Distribution): void;
+ getStatusCount(distributionComponent:Array<Models.DistributionComponent>):any;
+ initDistributions():void;
+ getUrlName(url:string):string;
+ close(): void;
+ openDisributionStatusModal:Function;
+ }
+
+ export class DistributionViewModel{
+
+ static '$inject' = [
+ '$scope',
+ 'ModalsHandler'
+
+ ];
+
+ constructor(
+ private $scope:IDistributionViewModel,
+ private ModalsHandler: Sdc.Utils.ModalsHandler
+ ){
+ this.initScope();
+ this.$scope.setValidState(true);
+ this.$scope.updateSelectedMenuItem();
+ }
+
+ private initScope = (): void => {
+ this.$scope.service = <Models.Components.Service>this.$scope.component;
+
+
+ // Open Distribution status modal
+ this.$scope.openDisributionStatusModal = (distribution: Models.Distribution,status:string):void => {
+ this.ModalsHandler.openDistributionStatusModal(distribution,status).then(()=>{
+ // OK
+ }, ()=>{
+ // ERROR
+ });
+ };
+
+
+ this.$scope.showComponents = (distribution: Models.Distribution): void => {
+ let onError = (response) => {
+ console.info('onError showComponents',response);
+ };
+ let onSuccess = (distributionComponents: Array<Models.DistributionComponent>) => {
+ distribution.distributionComponents = distributionComponents;
+ distribution.statusCount = this.$scope.getStatusCount(distribution.distributionComponents);
+ // distribution.components = this.aggregateDistributionComponent(distributionComponents);;
+ };
+ this.$scope.service.getDistributionsComponent(distribution.distributionID).then(onSuccess, onError);
+ };
+
+ this.$scope.getStatusCount = (distributionComponent:Array<Models.DistributionComponent>):any => {
+ return _.countBy(distributionComponent, 'status')
+ };
+
+ this.$scope.getUrlName = (url:string):string =>{
+ let urlName:string = _.last(url.split('/'));
+ return urlName;
+ };
+
+ this.$scope.markAsDeployed = (distribution: Models.Distribution): void => {
+ let onError = (response) => {
+ console.info('onError markAsDeployed',response);
+ };
+ let onSuccess = (result: any) => {
+ distribution.deployementStatus = 'Deployed';
+ };
+ this.$scope.service.markAsDeployed(distribution.distributionID).then(onSuccess, onError);
+
+ };
+
+ this.$scope.initDistributions = (): void => {
+ let onError = (response) => {
+ console.info('onError initDistributions',response);
+ };
+ let onSuccess = (distributions: Array<Models.Distribution>) => {
+ this.$scope.distributions = distributions;
+ };
+ this.$scope.service.getDistributionsList().then(onSuccess, onError);
+ };
+
+ this.$scope.initDistributions();
+
+ };
+
+
+ private aggregateDistributionComponent = (distributionComponents:Array<Models.DistributionComponent>):any =>{
+ let aggregateDistributions:Utils.Dictionary<string,Utils.Dictionary<string,Array<Models.DistributionComponent>>> = new Utils.Dictionary<string,Utils.Dictionary<string,Array<Models.DistributionComponent>>>();
+ let tempAggregateDistributions:any= _.groupBy(distributionComponents,'omfComponentID');
+ let aa = new Utils.Dictionary<string,Array<Models.DistributionComponent>>();
+
+ let tempAggregate:any;
+ _.forEach(tempAggregateDistributions,(distributionComponents:Array<Models.DistributionComponent>,omfComponentID:string)=>{
+
+ let urls:any = _.groupBy(distributionComponents,'url');
+ aggregateDistributions.setValue(omfComponentID,urls);
+ // aggregateDistributions[omfComponentID] = ;
+
+ });
+ console.log(aggregateDistributions);
+ return aggregateDistributions;
+ };
+
+
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/distribution/distribution-view.html b/catalog-ui/app/scripts/view-models/workspace/tabs/distribution/distribution-view.html
new file mode 100644
index 0000000000..1ab0f1e111
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/distribution/distribution-view.html
@@ -0,0 +1,171 @@
+<div class="w-sdc-distribution-view">
+ <div class="w-sdc-distribution-view-header">
+ <div class="w-sdc-distribution-view-title">DISTRIBUTION <span data-ng-bind="'[' + distributions.length +']'"
+ class="blue-font"></span></div>
+ <div class="header-spacer"></div>
+ <div class="top-search">
+ <input type="text"
+ class="search-text"
+ data-tests-id="searchTextbox"
+ placeholder="Search"
+ data-ng-model="searchBind"
+ data-tests-id="main-menu-input-search"
+ ng-model-options="{ debounce: 500 }"/>
+ <span class="w-sdc-search-icon magnification"></span>
+ </div>
+ <div class="sprite-new refresh-btn" data-tests-id="refreshButton" data-ng-click="initDistributions()" sdc-smart-tooltip=""
+ title="Refresh"></div>
+ </div>
+
+
+ <perfect-scrollbar include-padding="true" class="w-sdc-distribution-view-content">
+ <div class="w-sdc-distribution-view-content-section" data-tests-id="ditributionTable">
+ <ul>
+ <li data-ng-repeat="item in distributions | orderBy: '-timestamp' | filter:searchBind"
+ data-ng-init="item.dateFormat = ( item.timestamp | stringToDateFilter | date: 'MM/dd/yyyy h:mma':'UTC' )"
+ class="w-sdc-distribute-parent-block" data-tests-id="record_{{$index}}" data-ng-class="{'extends': item.showDetails}">
+ <div class="w-sdc-distribute-row w-sdc-distribute-row-extends"
+ data-ng-class="{'extends': item.showDetails && item.distributionComponents.length}">
+ <div class="w-sdc-distribution-arrow-btn" data-tests-id="ShowRecordButton_{{$index}}" data-ng-click="showComponents(item); item.showDetails=!item.showDetails"
+ data-ng-class="{'extends': item.showDetails}"
+ ></div>
+ <div class="w-sdc-distribute-row-content">
+ <div class="w-sdc-distribute-content">
+ <div class="title-section item-1">
+ <div class="title">Distribution ID</div>
+ <div data-ng-bind="item.distributionID"></div>
+ </div>
+ <div class="title-section item-2">
+ <div class="title" translate="DISTRIBUTION_VIEW_TITLE_USER_ID"></div>
+ <div data-ng-bind="item.userId"></div>
+ </div>
+ <div class="title-section item-3">
+ <div class="title">Time[UTC]:</div>
+ <div
+ data-ng-bind="item.dateFormat"></div>
+ </div>
+ <div class="title-section item-4">
+ <span class="sprite-new status-icon" data-ng-class="item.deployementStatus"></span>
+ <span class="sprite-new" data-ng-bind="item.deployementStatus"></span>
+ </div>
+ <div>
+ <div class="sprite-new distribution-bth item-5"
+ data-ng-class="{'disable':item.deployementStatus==='Deployed'}"
+ data-ng-click="(item.deployementStatus==='Deployed') || markAsDeployed(item)"></div>
+ </div>
+ </div>
+ <div class="w-sdc-distribute-status-block" data-ng-show="item.statusCount">
+ <div class="status-item-1">Total Artifacts:<span data-ng-bind="(item.statusCount.NOT_NOTIFIED || 0) + (item.statusCount.NOTIFIED || 0) "
+ class="blue-font" data-tests-id="totalArtifacts_{{$index}}"></span></div>
+ <div class="status-item-2 " ><sapn class="link" data-ng-click="openDisributionStatusModal(item,'NOTIFIED')">Notified:</sapn><span
+ data-ng-bind="item.statusCount.NOTIFIED || 0" class="blue-font" data-tests-id="notified_{{$index}}"></span></div>
+ <div class="status-item-3 link" ><sapn class="link" data-ng-click="openDisributionStatusModal(item,'DOWNLOAD_OK')">Downloaded:</sapn><span
+ data-ng-bind="item.statusCount.DOWNLOAD_OK || 0" class="blue-font" data-tests-id="downloaded_{{$index}}"></span></div>
+ <div class="status-item-4 link"><sapn class="link" data-ng-click="openDisributionStatusModal(item,'DEPLOY_OK')">Deployed:</sapn><span
+ data-ng-bind="item.statusCount.DEPLOY_OK || 0" class="blue-font" data-tests-id="deployed_{{$index}}" ></span><span
+ data-ng-class="{'deployed':(item.statusCount.DEPLOY_OK > 0)}"></span></div>
+ <div class="status-item-5 link" ><sapn class="link" data-ng-click="openDisributionStatusModal(item,'NOT_NOTIFIED')">Not Notified:</sapn><span
+ data-ng-bind="item.statusCount.NOT_NOTIFIED || 0" class="blue-font" data-tests-id="NotNotified_{{$index}}"></span></div>
+ <div class="status-item-6"><sapn class="link" data-ng-click="openDisributionStatusModal(item,'DOWNLOAD_ERROR')" >Errors:</sapn><span
+ data-ng-bind="item.statusCount.DOWNLOAD_ERROR || 0" class="red-font "></span><span
+ data-ng-class="{'error':(item.statusCount.DOWNLOAD_ERROR > 0)}" data-tests-id="errors_{{$index}}"></span></div>
+ </div>
+ </div>
+ </div>
+
+ <ul data-ng-show="item.showDetails && item.distributionComponents.length"
+ class="w-sdc-distribute-components-block disable-hover">
+
+ <li data-ng-repeat="(omfComponentID,omfComponentList) in item.distributionComponents | orderBy: '-timestamp' | filter:searchBind | groupBy:'omfComponentID' "
+ class="disable-hover"
+ data-ng-init="statusCount = getStatusCount(omfComponentList);">
+ <div class="w-sdc-distribute-row omf-component-row w-sdc-distribute-row-extends"
+ data-ng-class="{'extends': omfComponentListExtends}">
+ <div class="w-sdc-distribution-arrow-btn" data-ng-click="omfComponentListExtends=!omfComponentListExtends"
+ ng-class="{'extends': omfComponentListExtends}"
+ data-ng-init="omfComponentListExtends=false"
+ ></div>
+ <div class="w-sdc-distribute-status-block">
+ <div class="status-item-1">{{omfComponentID}} <span class="blue-font">{{(statusCount.NOT_NOTIFIED || 0) + (statusCount.NOTIFIED || 0) }}</span>
+ </div>
+ <div class="status-item-2">Notified:<span data-ng-bind="statusCount.NOTIFIED || 0"
+ class="blue-font"></span></div>
+ <div class="status-item-3">Downloaded:<span
+ data-ng-bind="statusCount.DOWNLOAD_OK || 0" class="blue-font"></span></div>
+ <div class="status-item-4">Deployed:<span data-ng-bind="statusCount.DEPLOY_OK || 0"
+ class="blue-font"></span><span
+ data-ng-class="{'deployed':(statusCount.DEPLOY_OK > 0)}"></span></div>
+ <div class="status-item-5">Not Notified:<span
+ data-ng-bind="statusCount.NOT_NOTIFIED || 0" class="blue-font"></span></div>
+ <div class="status-item-6">Errors:<span
+ data-ng-bind="statusCount.DOWNLOAD_ERROR || 0" class="red-font"></span><span
+ data-ng-class="{'error':(statusCount.DOWNLOAD_ERROR > 0)}"></span></div>
+ </div>
+ </div>
+ <div data-ng-show="omfComponentListExtends"
+ class="w-sdc-distribute-omfComponent-block disable-hover">
+ <div class="w-sdc-distribute-row-extends disable-hover">
+ <div class="disable-hover">
+ <div class="w-sdc-distribute-row omfComponent-table-head">
+ <div class="title item-1">Component ID</div>
+ <div class="title item-2">Artifact Name</div>
+ <div class="title item-3">URL</div>
+ <div class="title item-4">Time(UTC)</div>
+ <div class="title item-5">Status</div>
+ </div>
+
+ <div class="w-sdc-distribute-row omfComponent-table-row"
+ data-ng-repeat-start="(url,urlList) in omfComponentList | orderBy: '-timestamp' | groupBy:'url'"
+ data-ng-class="urlListExtends?'extends row-{{$index}}':'row-{{$index}}'">
+ <div class="w-sdc-distribute-cell item-1">
+ <div class="w-sdc-distribution-arrow-btn" data-ng-click="urlListExtends=!urlListExtends"
+ data-ng-class="{'extends': urlListExtends}"
+ data-ng-init="urlListListExtends=false"
+ ></div>
+ {{urlList[0].omfComponentID}}
+ </div>
+ <div class="w-sdc-distribute-cell item-2" sdc-smart-tooltip>
+ {{getUrlName(urlList[0].url)}}
+ </div>
+ <div class="w-sdc-distribute-cell item-3 disable-hover">
+ <div sdc-smart-tooltip class="distribution-url">{{urlList[0].url}}</div>
+ <div sdc-smart-tooltip title="Copy url" clipboard text="urlList[0].url"
+ class="sprite-new link-btn copy-link disable-hover"></div>
+ </div>
+ <div class="w-sdc-distribute-cell item-4"><span
+ data-ng-bind="urlList[0].timestamp | date: 'MM/dd/yyyy h:mma':'UTC'"></span>
+ </div>
+ <div class="w-sdc-distribute-cell item-5">{{urlList[0].status}}</div>
+ </div>
+
+
+ <div data-ng-repeat-end data-ng-show="urlListExtends" class="disable-hover" >
+ <div class="w-sdc-distribute-row extends disable-hover">
+ <ul data-ng-show="urlListExtends"
+ class="w-sdc-distribute-url-block disable-hover">
+ <li data-ng-repeat="distributionComponent in urlList | orderBy: '-timestamp'"
+ class="disable-hover">
+ <span
+ data-ng-bind="distributionComponent.timestamp | date: 'MM/dd/yyyy h:mma':'UTC'"
+ class="disable-hover"></span>
+ <span
+ class="disable-hover">{{distributionComponent.status}}</span>
+ <span
+ class="disable-hover reason" data-ng-show="distributionComponent.status == 'NOT_NOTIFIED'">Reason: Component has determined artifact is not needed.</span>
+ <span
+ class="disable-hover reason" data-ng-show="distributionComponent.errorReason">Reason: {{distributionComponent.errorReason}}</span>
+ </li>
+ </ul>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </li>
+ </ul>
+ </li>
+ </ul>
+ </div>
+
+ </perfect-scrollbar>
+</div>
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/distribution/distribution.less b/catalog-ui/app/scripts/view-models/workspace/tabs/distribution/distribution.less
new file mode 100644
index 0000000000..8ad8c1793e
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/distribution/distribution.less
@@ -0,0 +1,361 @@
+
+.w-sdc-distribution-view {
+ text-align: left;
+
+ .g_1;
+ min-height: 500px;
+
+ .w-sdc-distribution-view-distributed-green-text {
+ .l_9;
+ .bold;
+ }
+ .w-sdc-distribution-view-distributed-error-red-text {
+ .h_9;
+ .bold;
+ }
+
+ .bg_c;
+ vertical-align: top;
+ padding: 30px 10px;
+ width: 100%;
+
+ .w-sdc-distribution-view-header {
+ display: flex;
+ -webkit-justify-content: space-between;
+ margin: 0 25px 5px 40px;
+
+ .header-spacer {
+ flex-grow: 5;
+ }
+ }
+
+ .top-search {
+ position: relative;
+ input {
+ &.search-text {
+ height: 26px;
+ line-height: 26px;
+ margin: 0 18px 4px 20px;
+ padding-right: 25px;
+ }
+
+ }
+ .magnification {
+ top: 8px;
+ right: 25px;
+ }
+ }
+
+ .w-sdc-distribution-view-content {
+ .perfect-scrollbar;
+ padding: 0 25px 0 0px;
+ margin-bottom: 25px;
+ height: 700px;
+ overflow: hidden;
+ position: relative;
+
+ }
+
+ .w-sdc-distribution-view-title {
+ .s_14_r;
+
+ line-height: 30px;
+
+ span {
+ padding-left: 5px;
+ }
+ }
+
+ .blue-font {
+ .a_14_m;
+
+ }
+
+ .red-font {
+ .q_14_m;
+ }
+
+ .w-sdc-distribution-view-block {
+ div {
+ display: inline-block;
+ }
+ }
+
+ .w-sdc-distribution-view-content-section {
+ ul {
+ list-style-type: none;
+ }
+
+ .distribution-bth {
+ .hand;
+ &.disabled {
+ cursor: none;
+ }
+ }
+
+ .copy-link {
+ padding-right: 19px;
+ margin-left: 8px;
+ cursor: pointer;
+
+ }
+
+ .w-sdc-distribute-row-extends {
+ border-Left: solid 4px transparent;
+ &.extends {
+ border-left: solid 4px @main_color_c;
+ border-bottom: 1px solid @border_color_f;
+ margin-bottom: 10px;
+ }
+ }
+ .w-sdc-distribute-parent-block {
+ border: 1px solid @main_color_o;;
+ width: 100%;
+ margin-bottom: 6px;
+
+ .status-icon {
+ vertical-align: middle;
+ margin-bottom: 4px;
+ }
+
+ &.extends {
+ background-color: @tlv_color_t;
+ }
+
+ :not(.disable-hover):hover {
+ background-color: @tlv_color_u;
+ }
+
+ .title-section {
+ display: inline-block;
+ margin-right: 10px;
+ flex-basis: 0;
+ }
+
+ .title {
+ .l_12_m;
+ font-weight: bold;
+ }
+ .w-sdc-distribute-content {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-left: 10px;
+ }
+
+ .w-sdc-distribution-arrow-btn {
+ .sprite-new;
+ .arrow-up-small;
+ margin: 0 6px;
+ }
+ .extends.w-sdc-distribution-arrow-btn {
+ -webkit-transform: rotate(180deg);
+ -ms-transform: rotate(180deg);
+ transform: rotate(180deg);
+ }
+
+ .w-sdc-distribute-row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+
+ .w-sdc-distribute-row-content {
+ margin: 15px 31px 10px 0;
+ width: 100%;
+ .w-sdc-distribute-status-block {
+ border-top: solid 1px @main_color_o;
+ }
+ .item-1 {
+ flex-grow: 2;
+ }
+ .item-2 {
+ flex-grow: 1;
+ }
+ .item-3 {
+ flex-grow: 1;
+ }
+ .item-4 {
+ flex-grow: 1;
+ }
+ .item-5 {
+ flex-grow: 1;
+ }
+ }
+ }
+
+ .w-sdc-distribute-status-block {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin: 10px 5px 0 5px;
+ padding: 5px 5px 0 5px;;
+ width: 100%;
+ div {
+ border-left: 1px solid @main_color_o;
+ padding: 0 12px;
+ }
+
+ .link {
+ .a_14_m;
+ cursor: pointer;
+ &:hover{
+ text-decoration: underline;
+ .b_14_m;
+ }
+ }
+
+ span {
+ padding: 2px;
+ }
+
+ .deployed {
+ margin-left: 10px;
+ .sprite-new;
+ .success-circle-small;
+ }
+
+ .error {
+ .q_14_m;
+ margin-left: 10px;
+ .sprite-new;
+ .error-icon;
+ }
+
+ .status-item-1 {
+ border-left: 0;
+ }
+
+ .status-item-6 {
+ flex-grow: 1;
+ border-left: none;
+ text-align: right;
+ }
+ }
+
+ .w-sdc-distribute-components-block {
+ padding: 0;
+ padding-bottom: 5px;
+ list-style-type: none;
+
+ li {
+ margin: 5px 2px;
+ }
+
+ .omf-component-row {
+ border: 1px solid @border_color_f;
+ padding-left: 3px;
+ background-color: white;
+ margin: 0 30px;
+ &.extends {
+ padding-left: 0;
+ border-Left: solid 4px @main_color_c;
+
+ }
+
+ .w-sdc-distribute-status-block {
+ margin: 5px;
+ padding: 5px;
+ }
+
+ .blue-font {
+ .a_16_m;
+
+ }
+
+ &:hover {
+ background-color: @tlv_color_u;
+ }
+
+ }
+
+ }
+
+ .w-sdc-distribute-omfComponent-block {
+ background-color: white;
+ margin: 0 30px;
+ padding: 8px 10px;
+ border: 1px solid @border_color_f;
+
+ .omfComponent-table-head {
+ margin-bottom: 5px;
+ background-color: @tlv_color_u;
+ .title {
+ padding: 6px 10px;
+ border-left: 1px solid @border_color_f;
+ &:first-child {
+ border: none;
+ }
+ }
+ }
+
+ .omfComponent-table-row {
+ border-bottom: 1px solid @border_color_f;
+ &.row-0 {
+ border-top: 1px solid @border_color_f;
+ }
+ .w-sdc-distribute-cell {
+ padding: 10px;
+ border-left: 1px solid @border_color_f;
+ &:last-child {
+ border-right: 1px solid @border_color_f;
+ }
+ &.item-5 {
+ .m_14_m;
+ }
+ }
+ }
+
+ .distribution-url {
+
+ }
+
+ .w-sdc-distribute-row.extends {
+ border-Left: solid 4px @main_color_c;
+ .item-1 {
+ border: none;
+ }
+
+ }
+
+ .item-1 {
+ width: 20%;
+ }
+ .item-2 {
+ width: 20%;
+ }
+
+ .item-3 {
+ width: 24%;
+ display: flex;
+ }
+
+ .item-4 {
+ width: 18%;
+ }
+
+ .item-5 {
+ width: 18%;
+ }
+ }
+
+ .w-sdc-distribute-url-block {
+
+ padding: 10px 15px;
+ border: none;
+ border-right: 1px solid @border_color_f;
+ border-bottom: 1px solid @border_color_f;
+ width: 100%;
+ li {
+ border: none;
+ span {
+ padding-right: 30px;
+ .m_12_r;
+ }
+ }
+
+ }
+ }
+
+ }
+}
+
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/general/general-view-model.ts b/catalog-ui/app/scripts/view-models/workspace/tabs/general/general-view-model.ts
new file mode 100644
index 0000000000..f613648596
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/general/general-view-model.ts
@@ -0,0 +1,379 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../../../references"/>
+module Sdc.ViewModels {
+
+ 'use strict';
+ import Resource = Sdc.Models.Components.Resource;
+ import ISubCategory = Sdc.Models.ISubCategory;
+ import IMainCategory = Sdc.Models.IMainCategory;
+ import ResourceType = Sdc.Utils.Constants.ResourceType;
+
+ export class Validation {
+ validationPattern:RegExp;
+ contactIdValidationPattern:RegExp;
+ tagValidationPattern:RegExp;
+ vendorValidationPattern:RegExp;
+ commentValidationPattern:RegExp;
+ projectCodeValidationPattern:RegExp;
+ }
+
+ export interface IGeneralScope extends IWorkspaceViewModelScope {
+ validation:Validation;
+ editForm:ng.IFormController;
+ categories: Array<IMainCategory>;
+ latestCategoryId: string;
+ latestVendorName: string;
+ importedFileExtension:any;
+ isCreate:boolean;
+ isShowFileBrowse:boolean;
+ isShowOnboardingSelectionBrowse:boolean;
+ importedToscaBrowseFileText:string;
+ importCsarProgressKey:string;
+ browseFileLabel:string;
+
+
+ onToscaFileChange():void
+ validateField(field:any):boolean;
+ validateName(isInit:boolean): void;
+ calculateUnique(mainCategory:string, subCategory:string):string; // Build unique string from main and sub category
+ onVendorNameChange(oldVendorName:string): void;
+ convertCategoryStringToOneArray(category:string, subcategory:string):Array<Models.IMainCategory>;
+ onCategoryChange():void;
+ openOnBoardingModal():void;
+ initCategoreis():void;
+ }
+
+ export class GeneralViewModel {
+
+ static '$inject' = [
+ '$scope',
+ 'Sdc.Services.CacheService',
+ 'ValidationPattern',
+ 'ContactIdValidationPattern',
+ 'TagValidationPattern',
+ 'VendorValidationPattern',
+ 'CommentValidationPattern',
+ 'ValidationUtils',
+ 'sdcConfig',
+ 'ProjectCodeValidationPattern',
+ '$state',
+ 'ModalsHandler',
+ 'EventListenerService',
+ 'Notification',
+ 'Sdc.Services.ProgressService',
+ '$interval',
+ '$filter',
+ '$timeout'
+ ];
+
+ constructor(private $scope:IGeneralScope,
+ private cacheService:Services.CacheService,
+ private ValidationPattern:RegExp,
+ private ContactIdValidationPattern:RegExp,
+ private TagValidationPattern:RegExp,
+ private VendorValidationPattern:RegExp,
+ private CommentValidationPattern:RegExp,
+ private ValidationUtils:Sdc.Utils.ValidationUtils,
+ private sdcConfig:Models.IAppConfigurtaion,
+ private ProjectCodeValidationPattern:RegExp,
+ private $state:ng.ui.IStateService,
+ private ModalsHandler: Sdc.Utils.ModalsHandler,
+ private EventListenerService:Services.EventListenerService,
+ private Notification:any,
+ private progressService:Sdc.Services.ProgressService,
+ protected $interval:any,
+ private $filter:ng.IFilterService,
+ private $timeout:ng.ITimeoutService
+ ){
+
+ this.registerToSuccessSaveEvent();
+ this.initScopeValidation();
+ this.initScopeMethods();
+ this.initScope();
+ this.$scope.updateSelectedMenuItem();
+ }
+
+ private registerToSuccessSaveEvent = ():void => {
+ // Register to save success to show notification to user.
+ this.EventListenerService.registerObserverCallback(Utils.Constants.EVENTS.ON_WORKSPACE_SAVE_BUTTON_SUCCESS, this.showSuccessNotificationMessage);
+
+ };
+
+ private showSuccessNotificationMessage = ():void => {
+ // In case we import CSAR. Notify user when import VF was finished.
+ this.Notification.success({
+ message: this.$filter('translate')("IMPORT_VF_MESSAGE_CREATE_FINISHED_DESCRIPTION"),
+ title: this.$filter('translate')("IMPORT_VF_MESSAGE_CREATE_FINISHED_TITLE")
+ });
+
+ //set the form Pristine after save to reset the unsaved changes (whit for dom reload)
+ this.$timeout(()=> {
+ if(this.$scope.editForm) {
+ this.$scope.editForm.$setPristine();
+ }
+ }, 500);
+
+ };
+
+
+
+ private initScopeValidation = ():void => {
+ this.$scope.validation = new Validation();
+ this.$scope.validation.validationPattern = this.ValidationPattern;
+ this.$scope.validation.contactIdValidationPattern = this.ContactIdValidationPattern;
+ this.$scope.validation.tagValidationPattern = this.TagValidationPattern;
+ this.$scope.validation.vendorValidationPattern = this.VendorValidationPattern;
+ this.$scope.validation.commentValidationPattern = this.CommentValidationPattern;
+ this.$scope.validation.projectCodeValidationPattern = this.ProjectCodeValidationPattern;
+ };
+
+ private initScope = ():void => {
+
+ // Work around to change the csar version
+ if (this.cacheService.get(Utils.Constants.CHANGE_COMPONENT_CSAR_VERSION_FLAG)) {
+ (<Resource>this.$scope.component).csarVersion = this.cacheService.get(Utils.Constants.CHANGE_COMPONENT_CSAR_VERSION_FLAG);
+ }
+
+ this.$scope.importedToscaBrowseFileText = this.$scope.component.name + " (" + (<Resource>this.$scope.component).csarVersion + ")";
+ this.$scope.importCsarProgressKey = "importCsarProgressKey";
+ this.$scope.browseFileLabel = this.$scope.component.isResource() && (<Resource>this.$scope.component).resourceType===ResourceType.VF? "Upload file" : "Upload VFC";
+ this.$scope.progressService = this.progressService;
+
+ // Workaround to short vendor name to 25 chars
+ // onboarding send 27 chars, and the validation pattern is 25 chars.
+ if (this.$scope.component.vendorName){
+ this.$scope.component.vendorName = this.$scope.component.vendorName.substr(0, 25);
+ }
+
+ // Init UIModel
+ this.$scope.component.tags = _.without(this.$scope.component.tags, this.$scope.component.name);
+
+ // Init categories
+ this.$scope.initCategoreis();
+
+ // Init the decision if to show file browse.
+ this.$scope.isShowFileBrowse = false;
+ if (this.$scope.component.isResource()){
+ let resource:Sdc.Models.Components.Resource = <Sdc.Models.Components.Resource>this.$scope.component;
+ console.log(resource.name + ": " + resource.csarUUID);
+ if (resource.importedFile){ // Component has imported file.
+ this.$scope.isShowFileBrowse = true;
+ }
+ if (this.$scope.isEditMode() && resource.resourceType== ResourceType.VF && !resource.csarUUID){
+ this.$scope.isShowFileBrowse = true;
+ }
+ };
+
+ // Init the decision if to show onboarding
+ this.$scope.isShowOnboardingSelectionBrowse = false;
+ if (this.$scope.component.isResource() &&
+ this.$scope.isEditMode() &&
+ (<Resource>this.$scope.component).resourceType== ResourceType.VF &&
+ (<Resource>this.$scope.component).csarUUID) {
+ this.$scope.isShowOnboardingSelectionBrowse = true;
+ }
+
+ //init file extensions based on the file that was imported.
+ if (this.$scope.component.isResource() && (<Resource>this.$scope.component).importedFile){
+ let fileName:string = (<Resource>this.$scope.component).importedFile.filename;
+ let fileExtension:string = fileName.split(".").pop();
+ if (this.sdcConfig.csarFileExtension.indexOf(fileExtension.toLowerCase()) !== -1){
+ this.$scope.importedFileExtension = this.sdcConfig.csarFileExtension;
+ (<Resource>this.$scope.component).importedFile.filetype="csar";
+ } else if (this.sdcConfig.toscaFileExtension.indexOf(fileExtension.toLowerCase()) !== -1){
+ (<Resource>this.$scope.component).importedFile.filetype="yaml";
+ this.$scope.importedFileExtension = this.sdcConfig.toscaFileExtension;
+ }
+ }else if(this.$scope.isEditMode()&& (<Resource>this.$scope.component).resourceType === ResourceType.VF){
+ this.$scope.importedFileExtension = this.sdcConfig.csarFileExtension;
+ //(<Resource>this.$scope.component).importedFile.filetype="csar";
+ }
+
+ this.$scope.setValidState(true);
+
+ this.$scope.calculateUnique = (mainCategory:string, subCategory:string):string => {
+ let uniqueId:string = mainCategory;
+ if (subCategory) {
+ uniqueId += "_#_" + subCategory; // Set the select category combobox to show the selected category.
+ }
+ return uniqueId;
+ };
+
+ //TODO remove this after handling contact in UI
+ if(this.$scope.component.isProduct() && this.$scope.isCreateMode()){
+ (<Models.Components.Product>this.$scope.component).contacts = [];
+ (<Models.Components.Product>this.$scope.component).contacts.push(this.cacheService.get("user").userId);
+ }else if(this.$scope.isCreateMode()){
+ this.$scope.component.contactId = this.cacheService.get("user").userId;
+ }
+
+ };
+
+ // Convert category string MainCategory_#_SubCategory to Array with one item (like the server except)
+ private convertCategoryStringToOneArray = ():Array<Models.IMainCategory> => {
+ let tmp = this.$scope.component.selectedCategory.split("_#_");
+ let mainCategory = tmp[0];
+ let subCategory = tmp[1];
+
+ // Find the selected category and add the relevant sub category.
+ let selectedMainCategory:IMainCategory = <Models.IMainCategory>_.find(this.$scope.categories, function (item) {
+ return item["name"] === mainCategory;
+
+ });
+
+ let mainCategoryClone = angular.copy(selectedMainCategory);
+ if (subCategory) {
+ let selectedSubcategory = <Models.ISubCategory>_.find(selectedMainCategory.subcategories, function (item) {
+ return item["name"] === subCategory;
+ });
+ mainCategoryClone['subcategories'] = [angular.copy(selectedSubcategory)];
+ }
+ let tmpSelected = <Models.IMainCategory> mainCategoryClone;
+
+ let result:Array<Models.IMainCategory> = [];
+ result.push(tmpSelected);
+
+ return result;
+ };
+
+ private updateComponentNameInBreadcrumbs = ():void => {
+ //update breadcrum after changing name
+ this.$scope.breadcrumbsModel[1].updateSelectedMenuItemText(this.$scope.component.getComponentSubType() + ': ' + this.$scope.component.name);
+ this.$scope.updateMenuComponentName(this.$scope.component.name);
+ };
+
+ private initScopeMethods = ():void => {
+
+ this.$scope.initCategoreis = ():void => {
+ if (this.$scope.componentType === Utils.Constants.ComponentType.RESOURCE) {
+ this.$scope.categories = this.cacheService.get('resourceCategories');
+
+ }
+ if (this.$scope.componentType === Utils.Constants.ComponentType.SERVICE) {
+ this.$scope.categories = this.cacheService.get('serviceCategories');
+ }
+ }
+
+ this.$scope.validateField = (field:any):boolean => {
+ if (field && field.$dirty && field.$invalid) {
+ return true;
+ }
+ return false;
+ };
+
+ this.$scope.openOnBoardingModal=():void => {
+ let csarUUID = (<Resource>this.$scope.component).csarUUID;
+ this.ModalsHandler.openOnboadrdingModal('Update', csarUUID).then(()=>{
+ // OK
+ this.$scope.uploadFileChangedInGeneralTab();
+ }, ()=>{
+ // ERROR
+ });
+ };
+
+ this.$scope.validateName = (isInit:boolean):void => {
+ if (isInit === undefined) {
+ isInit = false;
+ }
+
+ let name = this.$scope.component.name;
+ if (!name || name === "") {
+ if (this.$scope.editForm
+ && this.$scope.editForm["componentName"]
+ && this.$scope.editForm["componentName"].$error) {
+
+ // Clear the error name already exists
+ this.$scope.editForm["componentName"].$setValidity('nameExist', true);
+ }
+
+ return;
+ }
+ //?????????????????????????
+ let subtype:string = Utils.Constants.ComponentType.RESOURCE == this.$scope.componentType ? this.$scope.component.getComponentSubType() : undefined;
+
+ let onFailed = (response) => {
+ //console.info('onFaild', response);
+ //this.$scope.isLoading = false;
+ };
+
+ let onSuccess = (validation:Models.IValidate) => {
+ this.$scope.editForm["componentName"].$setValidity('nameExist', validation.isValid);
+ if(validation.isValid){
+ //update breadcrumb after changing name
+ this.updateComponentNameInBreadcrumbs();
+ }
+ };
+
+ if (isInit) {
+ // When page is init after update
+ if (this.$scope.component.name !== this.$scope.originComponent.name) {
+ if (!(this.$scope.componentType===Utils.Constants.ComponentType.RESOURCE && (<Resource>this.$scope.component).csarUUID!==undefined)
+ ){
+ this.$scope.component.validateName(name, subtype).then(onSuccess, onFailed);
+ }
+ }
+ } else {
+ // Validating on change (has debounce)
+ if (this.$scope.editForm
+ && this.$scope.editForm["componentName"]
+ && this.$scope.editForm["componentName"].$error
+ && !this.$scope.editForm["componentName"].$error.pattern
+ && this.$scope.component.name !== this.$scope.originComponent.name
+ ) {
+ if (!(this.$scope.componentType===Utils.Constants.ComponentType.RESOURCE && (<Resource>this.$scope.component).csarUUID!==undefined)
+ ){
+ this.$scope.component.validateName(name, subtype).then(onSuccess, onFailed);
+ }
+ } else if (this.$scope.component.name === this.$scope.originComponent.name) {
+ // Clear the error
+ this.$scope.editForm["componentName"].$setValidity('nameExist', true);
+ }
+ }
+ };
+
+ this.$scope.$watchCollection('component.name', (newData:any):void => {
+ this.$scope.validateName(false);
+ });
+
+ // Notify the parent if this step valid or not.
+ this.$scope.$watch("editForm.$valid", (newVal, oldVal) => {
+ this.$scope.setValidState(newVal);
+ });
+
+ this.$scope.$watch("editForm.$dirty", (newVal, oldVal) => {
+ if (newVal!==oldVal) {
+ this.$state.current.data.unsavedChanges = newVal && !this.$scope.isCreateMode();
+ }
+ });
+
+ this.$scope.onCategoryChange = ():void => {
+ this.$scope.component.categories = this.convertCategoryStringToOneArray();
+ this.$scope.component.icon = Utils.Constants.DEFAULT_ICON;
+ };
+
+ this.$scope.onVendorNameChange = (oldVendorName:string):void => {
+ if (this.$scope.component.icon === oldVendorName) {
+ this.$scope.component.icon = Utils.Constants.DEFAULT_ICON;
+ }
+ };
+ };
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/general/general-view.html b/catalog-ui/app/scripts/view-models/workspace/tabs/general/general-view.html
new file mode 100644
index 0000000000..1c1d4fedad
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/general/general-view.html
@@ -0,0 +1,307 @@
+<div include-padding="true" class="sdc-workspace-general-step">
+
+ <form novalidate class="w-sdc-form" name="editForm">
+
+ <div class="w-sdc-form-section-container">
+
+ <!--------------------- IMPORT TOSCA FILE USING BROWSE (ALSO VFC) -------------------->
+ <div class="i-sdc-form-item" ng-if="isShowFileBrowse">
+ <label class="i-sdc-form-label required">{{browseFileLabel}}</label>
+ <file-upload id="fileUploadElement"
+ class="i-sdc-form-input"
+ element-name="fileElement"
+ element-disabled="{{!isCreateMode()&&!(isEditMode()&&component.resourceType=='VF')}} || {{isViewMode()}}"
+ form-element="editForm"
+ file-model="component.importedFile"
+ on-file-changed-in-directive="uploadFileChangedInGeneralTab"
+ extensions="{{importedFileExtension}}"
+ default-text="'Browse to select file'"
+ data-ng-class="{'error':!(isEditMode()&&component.resourceType=='VF') && (!editForm.fileElement.$valid || !component.importedFile.filename)}"></file-upload>
+ </div>
+
+ <!--------------------- IMPORT TOSCA FILE USING ONBOARDING -------------------->
+ <div class="i-sdc-form-item" ng-if="isShowOnboardingSelectionBrowse">
+ <label class="i-sdc-form-label required">Select VSP</label>
+ <div class="i-sdc-form-file-upload i-sdc-form-input">
+ <span class="i-sdc-form-file-name" data-tests-id="filename">{{(fileModel && fileModel.filename) || importedToscaBrowseFileText}}</span>
+ <div class="i-sdc-form-file-upload-x-btn" ng-click="cancel()" data-ng-show="fileModel.filename && fileModel.filename!=='' && elementDisabled!=='true'"></div>
+ <input type="button" name="fileElement"/>
+ <div class="file-upload-browse-btn" data-ng-click="openOnBoardingModal()" data-tests-id="browseButton">Browse</div>
+ </div>
+ </div>
+
+ <div class="input-error-file-upload" data-ng-show="component.importedFile && (!editForm.fileElement.$valid || !component.importedFile.filename)">
+ <!-- editForm.fileElement.$error.required <== Can not use this, because the browse is done from outside for the first time -->
+ <span ng-show="!(isEditMode()&&component.resourceType=='VF')&&!component.importedFile.filename" translate="NEW_SERVICE_RESOURCE_ERROR_TOSCA_FILE_REQUIRED"></span><!-- Required -->
+ <span ng-show="editForm.fileElement.$error.maxsize" translate="VALIDATION_ERROR_MAX_FILE_SIZE"></span>
+ <span ng-show="editForm.fileElement.$error.filetype" translate="NEW_SERVICE_RESOURCE_ERROR_VALID_TOSCA_EXTENSIONS" translate-values="{'extensions': '{{importedFileExtension}}' }"></span>
+ <span ng-show="editForm.fileElement.$error.emptyFile" translate="VALIDATION_ERROR_EMPTY_FILE"></span>
+ </div>
+ <!--------------------- IMPORT TOSCA FILE -------------------->
+
+ <div class="w-sdc-form-columns-wrapper">
+
+ <div class="w-sdc-form-column">
+
+ <!--------------------- NAME -------------------->
+ <div class="i-sdc-form-item" data-ng-class="{'error': validateField(editForm.componentName)}">
+ <label class="i-sdc-form-label required">Name</label>
+ <input class="i-sdc-form-input"
+ data-ng-class="{'view-mode': isViewMode()}"
+ name="componentName"
+ data-ng-init="isCreateMode() && validateName(true)"
+ data-ng-maxlength="{{component.isProduct()?'25':'50'}}"
+ maxlength="{{component.isProduct()?'25':'50'}}"
+ data-ng-minlength="{{component.isProduct()?'4':'0'}}"
+ minlength="{{component.isProduct()?'4':'0'}}"
+ data-ng-model="component.name"
+ type="text"
+ data-required
+ data-ng-model-options="{ debounce: 500 }"
+ data-ng-pattern="validation.validationPattern"
+ data-ng-disabled="component.isAlreadyCertified()"
+ data-tests-id="name"
+ autofocus
+ ng-readonly="isViewMode()"
+ />
+
+ <div class="input-error" data-ng-show="validateField(editForm.componentName)">
+ <span ng-show="editForm.componentName.$error.required" translate="NEW_SERVICE_RESOURCE_ERROR_RESOURCE_NAME_REQUIRED"></span>
+ <span ng-show="editForm.componentName.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '50' }"></span>
+ <span ng-show="editForm.componentName.$error.minlength" translate="VALIDATION_ERROR_MIN_LENGTH" translate-values="{'min': '4' }"></span>
+ <span ng-show="editForm.componentName.$error.nameExist" translate="NEW_SERVICE_RESOURCE_ERROR_NAME_EXISTS"></span>
+ <span ng-show="editForm.componentName.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span>
+ </div>
+ </div>
+ <!--------------------- NAME -------------------->
+
+ <!--------------------- FULL NAME -------------------->
+ <div ng-if="component.isProduct()" class="i-sdc-form-item" data-ng-class="{'error': validateField(editForm.fullName)}">
+ <label class="i-sdc-form-label required">Full Name</label>
+ <input class="i-sdc-form-input"
+ name="fullName"
+ data-ng-class="{'view-mode': isViewMode()}"
+ data-ng-change="validateName()"
+ data-ng-maxlength="100"
+ maxlength="100"
+ data-ng-minlength="4"
+ minlength="4"
+ data-ng-model="component.fullName"
+ type="text"
+ data-required
+ data-ng-model-options="{ debounce: 500 }"
+ data-ng-pattern="validation.commentValidationPattern"
+ data-tests-id="fullName"
+ autofocus
+ ng-readonly="isViewMode()"
+ />
+
+ <div class="input-error" data-ng-show="validateField(editForm.fullName)">
+ <span ng-show="editForm.fullName.$error.required" translate="NEW_SERVICE_RESOURCE_ERROR_RESOURCE_NAME_REQUIRED"></span>
+ <span ng-show="editForm.fullName.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '50' }"></span>
+ <span ng-show="editForm.fullName.$error.minlength" translate="VALIDATION_ERROR_MIN_LENGTH" translate-values="{'min': '4' }"></span>
+ <span ng-show="editForm.fullName.$error.nameExist" translate="NEW_SERVICE_RESOURCE_ERROR_NAME_EXISTS"></span>
+ <span ng-show="editForm.fullName.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span>
+ </div>
+ </div>
+ <!--------------------- NAME -------------------->
+
+ <!--------------------- DESCRIPTION -------------------->
+ <div class="i-sdc-form-item"
+ data-ng-class="{'error': validateField(editForm.description)}">
+ <label class="i-sdc-form-label required">Description</label>
+ <textarea class="description"
+ name="description"
+ data-ng-class="{'view-mode': isViewMode()}"
+ data-ng-maxlength="1024"
+ data-required
+ data-ng-model="component.description"
+ data-ng-model-options="{ debounce: 500 }"
+ data-ng-pattern="validation.commentValidationPattern"
+ maxlength="1024"
+ data-tests-id="description"></textarea>
+ <!-- placeholder="Description here..." -->
+
+ <div class="input-error" data-ng-show="validateField(editForm.description)">
+ <span ng-show="editForm.description.$error.required" translate="NEW_SERVICE_RESOURCE_ERROR_RESOURCE_DESCRIPTION_REQUIRED"></span>
+ <span ng-show="editForm.description.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '1024' }"></span>
+ <span ng-show="editForm.description.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span>
+ </div>
+ </div>
+ <!--------------------- DESCRIPTION -------------------->
+
+ <!--------------------- CATEGORIES -------------------->
+ <div class="i-sdc-form-item"
+ data-ng-class="{'error': validateField(editForm.category)}"
+ data-ng-if="!component.isProduct()">
+ <loader data-display="!categories && !initCategoreis()" relative="true"></loader>
+ <label class="i-sdc-form-label required">Category</label>
+ <select class="i-sdc-form-select"
+ data-required
+ name="category"
+ data-ng-class="{'view-mode': isViewMode()}"
+ data-ng-change="onCategoryChange()"
+ data-ng-disabled="component.isAlreadyCertified() || (component.isCsarComponent() && component.selectedCategory && component.selectedCategory!=='')"
+ data-ng-model="component.selectedCategory"
+ data-tests-id="selectGeneralCategory"
+ >
+ <option value="">Select category</option>
+ <optgroup ng-if="component.isResource()" data-ng-repeat="mainCategory in categories | orderBy:['name']" label="{{mainCategory.name}}" data-tests-id="{{mainCategory.name}}">
+ <option data-ng-repeat="subCategory in mainCategory.subcategories track by $index"
+ data-ng-selected="component.selectedCategory === calculateUnique(mainCategory.name,subCategory.name)"
+ data-tests-id="{{subCategory.name}}"
+ value="{{calculateUnique(mainCategory.name, subCategory.name)}}">{{subCategory.name}}
+
+ </option>
+ </optgroup>
+ <option ng-if="component.isService()" data-ng-repeat="mainCategory in categories | orderBy:['name']"
+ data-ng-selected="component.selectedCategory===mainCategory.name"
+ value="{{mainCategory.name}}"
+ data-tests-id="{{mainCategory.name}}">{{mainCategory.name}}</option>
+ </select>
+
+ <div class="input-error" data-ng-show="validateField(editForm.category)">
+ <span ng-show="editForm.category.$error.required" translate="NEW_SERVICE_RESOURCE_ERROR_CATEGORY_REQUIRED"></span>
+ </div>
+ </div>
+ <!--------------------- CATEGORIES -------------------->
+
+ <!--------------------- PROJECT CODE -------------------->
+ <div class="i-sdc-form-item" data-ng-if="!component.isResource()"
+ data-ng-class="{'error': validateField(editForm.projectCode)}">
+ <label class="i-sdc-form-label required" translate="GENERAL_LABEL_PROJECT_CODE"></label>
+ <input class="i-sdc-form-input" type="text"
+ data-ng-model="component.projectCode"
+ data-ng-class="{'view-mode': isViewMode()}"
+ data-ng-model-options="{ debounce: 500 }"
+ data-ng-maxlength="128"
+ data-required
+ name="projectCode"
+ data-ng-pattern="validation.projectCodeValidationPattern"
+ maxlength="50"
+ data-tests-id="projectCode"
+ />
+
+ <div class="input-error" data-ng-show="validateField(editForm.projectCode)">
+ <span ng-show="editForm.projectCode.$error.required" translate="NEW_SERVICE_RESOURCE_ERROR_PROJECT_CODE_REQUIRED"></span>
+ <span ng-show="editForm.projectCode.$error.pattern" translate="NEW_SERVICE_RESOURCE_ERROR_PROJECT_CODE_NOT_VALID"></span>
+ </div>
+ </div>
+
+ <!--------------------- VENDOR NAME -------------------->
+ <div ng-if="component.isResource()" class="i-sdc-form-item" data-ng-class="{'error': validateField(editForm.vendorName)}">
+ <label class="i-sdc-form-label required">Vendor</label>
+ <input class="i-sdc-form-input" type="text"
+ data-ng-class="{'view-mode': isViewMode()}"
+ data-ng-model="component.vendorName"
+ data-ng-model-options="{ debounce: 500 }"
+ data-ng-maxlength="25"
+ data-required
+ ng-click="oldValue = component.vendorName"
+ name="vendorName"
+ data-ng-change="onVendorNameChange(oldValue)"
+ data-ng-pattern="validation.vendorValidationPattern"
+ maxlength="25"
+ data-ng-disabled="component.isAlreadyCertified() || (component.isCsarComponent() && component.vendorName && component.vendorName!=='')"
+ data-tests-id="vendorName"
+ />
+
+ <div class="input-error" data-ng-show="validateField(editForm.vendorName)">
+ <span ng-show="editForm.vendorName.$error.required" translate="NEW_SERVICE_RESOURCE_ERROR_VENDOR_NAME_REQUIRED"></span>
+ <span ng-show="editForm.vendorName.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '25' }"></span>
+ <span ng-show="editForm.vendorName.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span>
+ </div>
+
+ </div>
+
+ <!--------------------- VENDOR NAME -------------------->
+
+ <!--------------------- VENDOR RELEASE -------------------->
+ <div ng-if="component.isResource()"
+ class="i-sdc-form-item"
+ data-ng-class="{'error': validateField(editForm.vendorRelease)}">
+ <label class="i-sdc-form-label required">Vendor Release</label>
+ <input class="i-sdc-form-input" type="text"
+ data-ng-class="{'view-mode': isViewMode()}"
+ data-ng-model="component.vendorRelease"
+ data-ng-model-options="{ debounce: 500 }"
+ data-ng-maxlength="25"
+ data-required
+ name="vendorRelease"
+ data-ng-pattern="validation.vendorValidationPattern"
+ maxlength="25"
+ data-ng-disabled="component.isCsarComponent() && component.vendorRelease && component.vendorRelease!==''"
+ data-tests-id="vendorRelease"
+ />
+
+ <div class="input-error" data-ng-show="validateField(editForm.vendorRelease)">
+ <span ng-show="editForm.vendorRelease.$error.required" translate="NEW_SERVICE_RESOURCE_ERROR_VENDOR_RELEASE_REQUIRED"></span>
+ <span ng-show="editForm.vendorRelease.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '128' }"></span>
+ <span ng-show="editForm.vendorRelease.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span>
+ </div>
+ </div>
+ <!--------------------- VENDOR RELEASE -------------------->
+
+
+
+ </div><!-- Close w-sdc-form-column -->
+
+ <div class="w-sdc-form-column">
+
+ <!--------------------- RESOURCE TAGS -------------------->
+ <div class="i-sdc-form-item" data-ng-class="{'error': validateField(editForm.tags)}">
+ <label class="i-sdc-form-label">Tags</label>
+
+ <sdc-tags form-element="editForm" element-name="tags" max-tags="20" class="i-sdc-form-item-tags"
+ sdc-disabled="isViewMode()"
+ tags="component.tags"
+ pattern="validation.tagValidationPattern"
+ special-tag="component.name"></sdc-tags>
+
+ <div class="input-error" data-ng-show="validateField(editForm.tags)">
+ <span ng-show="editForm.tags.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span>
+ <span ng-show="editForm.tags.$error.nameExist" translate="NEW_SERVICE_RESOURCE_ERROR_TAG_NAME_EXIST"></span>
+ </div>
+ </div>
+ <!--------------------- RESOURCE TAGS -------------------->
+
+ <!--------------------- CONTACT ID -------------------->
+ <div class="i-sdc-form-item" data-ng-class="{'error': validateField(editForm.contactId)}">
+ <label class="i-sdc-form-label " data-ng-class="{'required':!component.isProduct()}" translate="GENERAL_LABEL_CONTACT_ID"></label>
+ <input class="i-sdc-form-input" type="text" data-ng-if="!component.isProduct()"
+ data-ng-model="component.contactId"
+ data-ng-class="{'view-mode': isViewMode()}"
+ data-ng-required="!component.isProduct()"
+ name="contactId"
+ data-ng-pattern="validation.contactIdValidationPattern"
+ data-ng-model-options="{ debounce: 500 }"
+ data-tests-id="userId"
+ maxlength="50"
+ />
+ <input class="i-sdc-form-input" type="text" data-ng-if="component.isProduct()"
+ data-ng-model="component.contacts[0]"
+ data-ng-class="{'view-mode': isViewMode()}"
+ data-ng-required="!component.isProduct()"
+ name="contactId"
+ data-ng-pattern="validation.contactIdValidationPattern"
+ data-ng-model-options="{ debounce: 500 }"
+ data-tests-id="userId"
+ maxlength="50"
+ />
+
+ <div class="input-error" data-ng-show="validateField(editForm.contactId)">
+ <span ng-show="editForm.contactId.$error.required" translate="NEW_SERVICE_RESOURCE_ERROR_CONTACT_REQUIRED"></span>
+ <span ng-show="editForm.contactId.$error.pattern" translate="NEW_SERVICE_RESOURCE_ERROR_CONTACT_NOT_VALID"></span>
+ </div>
+ </div>
+ <!--------------------- CONTACT ID -------------------->
+
+
+ </div><!-- Close w-sdc-form-column -->
+ </div>
+
+ </div><!-- Close w-sdc-form-section-container -->
+
+ </form>
+
+</div>
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/general/general.less b/catalog-ui/app/scripts/view-models/workspace/tabs/general/general.less
new file mode 100644
index 0000000000..1861d02e98
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/general/general.less
@@ -0,0 +1,64 @@
+.sdc-workspace-general-step {
+
+ .w-sdc-form {
+ padding: 0;
+
+ .i-sdc-form-file-upload{
+ input[type="button"] {
+ cursor: pointer;
+ display: block;
+ filter: alpha(opacity=0);
+ width: 100px;
+ height: 30px;
+ opacity: 0;
+ position: absolute;
+ right: 0;
+ text-align: right;
+ top: 0;
+ }
+
+ .file-upload-browse-btn {
+ .noselect;
+ .bg_n;
+ padding: 4px 6px;
+ cursor: pointer;
+ z-index: 999;
+ width: 100px;
+ height: 28px;
+ text-align: center;
+
+ &.disabled {
+ cursor: default;
+ }
+ }
+ }
+
+ .w-sdc-form-section-container {
+ text-align: center;
+ }
+
+ .i-sdc-form-item {
+ &.upload {
+ margin-top: 0;
+ width: auto;
+ padding: 10px;
+ }
+ }
+
+ .template-desc {
+ border: 1px dashed @border_color_f;
+ height: 130px;
+ overflow: hidden;
+ padding: 10px 6px 6px 6px;
+ margin-top: 10px;
+ }
+
+ .sdc-tag .tag {
+ max-width: 225px;
+ }
+
+ }
+
+}
+
+
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/icons/icons-view-model.ts b/catalog-ui/app/scripts/view-models/workspace/tabs/icons/icons-view-model.ts
new file mode 100644
index 0000000000..a591641d0a
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/icons/icons-view-model.ts
@@ -0,0 +1,131 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/**
+ * Created by obarda on 4/4/2016.
+ */
+/// <reference path="../../../../references"/>
+module Sdc.ViewModels {
+ 'use strict';
+
+ export interface IIconsScope extends IWorkspaceViewModelScope {
+ icons : Array<string>;
+ iconSprite: string;
+ setComponentIcon(iconSrc:string): void;
+ }
+
+ export class IconsViewModel {
+
+ static '$inject' = [
+ '$scope',
+ 'Sdc.Services.AvailableIconsService',
+ 'ComponentFactory',
+ '$state'
+ ];
+
+ constructor(private $scope:IIconsScope,
+ private availableIconsService:Services.AvailableIconsService,
+ private ComponentFactory:Sdc.Utils.ComponentFactory,
+ private $state:ng.ui.IStateService) {
+
+
+ this.initScope();
+ this.initIcons();
+ this.$scope.updateSelectedMenuItem();
+ this.$scope.iconSprite = this.$scope.component.iconSprite;
+
+ if (this.$scope.component.isResource()) {
+ this.initVendor();
+ }
+ }
+
+ private initialIcon:string = this.$scope.component.icon;
+ private initIcons = ():void => {
+
+ // For subcategories that where created by admin, there is no icons
+ this.$scope.icons = new Array<string>();
+ if (this.$scope.component.categories && this.$scope.component.categories.length > 0) {
+
+ _.forEach(this.$scope.component.categories, (category:Models.IMainCategory):void => {
+ if (category.icons) {
+ this.$scope.icons = this.$scope.icons.concat(category.icons);
+ }
+ if (category.subcategories) {
+ _.forEach(category.subcategories, (subcategory:Models.ISubCategory):void => {
+ if (subcategory.icons) {
+ this.$scope.icons = this.$scope.icons.concat(subcategory.icons);
+ }
+ });
+ }
+ });
+ }
+
+ if (this.$scope.component.isResource()) {
+ let resourceType:string = this.$scope.component.getComponentSubType();
+ if (resourceType === 'VL') {
+ this.$scope.icons = ['vl'];
+ }
+ if (resourceType === 'CP') {
+ this.$scope.icons = ['cp'];
+ }
+ }
+
+ if (this.$scope.icons.length === 0) {
+ this.$scope.icons = this.availableIconsService.getIcons(this.$scope.component.componentType);
+ }
+ //we always add the defual icon to the list
+ this.$scope.icons.push('defaulticon');
+ };
+
+ private initVendor = ():void => {
+ let vendors:Array<string> = this.availableIconsService.getIcons(this.$scope.component.componentType).slice(5, 19);
+ let vendorName = this.$scope.component.vendorName.toLowerCase();
+ if ('at&t' === vendorName) {
+ vendorName = 'att';
+ }
+ if ('nokia' === vendorName) {
+ vendorName = 'nokiasiemens';
+ }
+
+ let vendor:string = _.find(vendors, (vendor:string)=> {
+ return vendor.replace(/[_]/g, '').toLowerCase() === vendorName;
+ });
+
+ if (vendor && this.$scope.icons.indexOf(vendor) === -1) {
+ this.$scope.icons.push(vendor);
+ }
+ };
+
+ private initScope():void {
+ this.$scope.icons = [];
+ this.$scope.setValidState(true);
+ //if(this.$scope.component.icon === Utils.Constants.DEFAULT_ICON){
+ // //this.$scope.setValidState(false);
+ //}
+
+ this.$scope.setComponentIcon = (iconSrc:string):void => {
+ this.$state.current.data.unsavedChanges = !this.$scope.isViewMode() && (iconSrc != this.initialIcon);
+ this.$scope.component.icon = iconSrc;
+ // this.$scope.setValidState(true);
+ };
+
+ }
+
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/icons/icons-view.html b/catalog-ui/app/scripts/view-models/workspace/tabs/icons/icons-view.html
new file mode 100644
index 0000000000..aac14e0e84
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/icons/icons-view.html
@@ -0,0 +1,26 @@
+<div class="workspace-icons">
+
+ <form novalidate class="w-sdc-form" name="iconForm">
+ <label class="i-sdc-form-label icons-label required">Icons</label>
+ <div class="selected-icon-container" data-ng-class="{'view-mode': isViewMode()}">
+ <div class="i-sdc-form-item-suggested-icon large selected-icon {{iconSprite}} {{component.icon}}"
+ data-ng-class="{ 'disable': isViewMode() }"
+ ng-model="component.icon"
+ tooltips tooltip-content='{{component.icon | translate}}'
+ >
+ </div>
+ </div>
+ <div data-ng-class="{'view-mode': isViewMode()}" class="icons-text">Select one of the icons below for the asset</div>
+ <div class="i-sdc-form-item suggested-icons-container" data-ng-class="{'view-mode no-pointer-events' : isViewMode()}">
+ <div class ="suggested-icon-wrapper" ng-class="component.icon==='{{iconSrc}}' ? 'selected' : '' " data-ng-repeat="iconSrc in icons track by $index">
+ <div class="i-sdc-form-item-suggested-icon large {{iconSprite}} {{iconSrc}}" data-ng-class="component.isAlreadyCertified() || isViewMode() ? 'disable':'hand'"
+ ng-model="component.icon"
+ data-tests-id="{{iconSrc}} iconBox"
+ data-ng-click="!component.isAlreadyCertified() && setComponentIcon(iconSrc)"
+ tooltips tooltip-content='{{iconSrc | translate}}'
+ >
+ </div>
+ </div>
+ </div>
+ </form>
+</div>
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/icons/icons.less b/catalog-ui/app/scripts/view-models/workspace/tabs/icons/icons.less
new file mode 100644
index 0000000000..65f946f395
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/icons/icons.less
@@ -0,0 +1,65 @@
+.workspace-icons {
+
+ width: 89%;
+ display: inline-block;
+ text-align: center;
+ align-items: center;
+
+ .w-sdc-form {
+ padding-top: 0px;
+ padding-bottom: 0px;
+ .selected-icon-container {
+ text-align: left;
+ border: 1px solid #cfcfcf;
+ clear: both;
+ margin-bottom: 30px;
+ padding: 2px 0px 5px 5px;
+ .selected-icon {
+ margin: 8px 5px 0px 6px;
+ }
+ }
+
+ .suggested-icons-container {
+ text-align: left;
+ border: 1px solid #cfcfcf;
+ clear: both;
+ padding: 2px 0px 5px 5px;
+ height: 340px;
+ margin-bottom: 0px;
+
+ .suggested-icon-wrapper {
+ margin: 8px 5px 0px 6px;
+ display: inline-block;
+
+ &.selected {
+ border: 2px solid @main_color_a;
+ border-radius: 35px;
+ display: inline-block;
+ line-height: 0px;
+ padding: 3px;
+ }
+
+ }
+ .suggested-icon {
+ // margin: 8px 5px 0px 6px;
+ display: inline-block;
+ &.disable{
+ opacity: 0.4;
+ }
+ }
+ }
+
+ .icons-label {
+ float: left;
+ }
+
+ .icons-text {
+ text-align: left;
+ line-height: 32px;
+ padding-left: 10px;
+ width: 100%;
+ border: 1px solid #cfcfcf;
+ border-bottom: none;
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/information-artifacts/information-artifacts-view-model.ts b/catalog-ui/app/scripts/view-models/workspace/tabs/information-artifacts/information-artifacts-view-model.ts
new file mode 100644
index 0000000000..3a048c1879
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/information-artifacts/information-artifacts-view-model.ts
@@ -0,0 +1,150 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../../../references"/>
+module Sdc.ViewModels {
+ 'use strict';
+ import ArtifactModel = Sdc.Models.ArtifactModel;
+
+ export interface IInformationArtifactsScope extends IWorkspaceViewModelScope {
+ artifacts: Array<Models.ArtifactModel>;
+ tableHeadersList: Array<any>;
+ artifactType: string;
+ isResourceInstance:boolean;
+ downloadFile:Models.IFileDownload;
+ isLoading:boolean;
+ sortBy:string;
+ reverse:boolean;
+
+ getTitle(): string;
+ addOrUpdate(artifact:Models.ArtifactModel): void;
+ delete(artifact:Models.ArtifactModel): void;
+ download(artifact:Models.ArtifactModel): void;
+ clickArtifactName(artifact:any):void;
+ openEditEnvParametersModal(artifactResource:Models.ArtifactModel):void;
+ sort(sortBy:string): void;
+ showNoArtifactMessage():boolean;
+ }
+
+ export class InformationArtifactsViewModel {
+
+ static '$inject' = [
+ '$scope',
+ '$filter',
+ '$modal',
+ '$templateCache',
+ 'Sdc.Services.SharingService',
+ '$state',
+ 'sdcConfig',
+ 'ModalsHandler'
+ ];
+
+ constructor(private $scope:IInformationArtifactsScope,
+ private $filter:ng.IFilterService,
+ private $modal:ng.ui.bootstrap.IModalService,
+ private $templateCache:ng.ITemplateCacheService,
+ private sharingService:Sdc.Services.SharingService,
+ private $state:any,
+ private sdcConfig:Models.IAppConfigurtaion,
+ private ModalsHandler:Utils.ModalsHandler) {
+ this.initScope();
+ this.$scope.updateSelectedMenuItem();
+ }
+
+
+ private getMappedObjects():any {
+ return {
+ normal: this.$scope.component.artifacts
+ };
+ }
+
+ private initScope = ():void => {
+ let self = this;
+ this.$scope.isLoading = false;
+ this.$scope.sortBy = 'artifactDisplayName';
+ this.$scope.reverse = false;
+ this.$scope.setValidState(true);
+ this.$scope.artifactType = 'normal';
+ this.$scope.getTitle = ():string => {
+ return this.$filter("resourceName")(this.$scope.component.name) + ' Artifacts';
+
+ };
+
+ this.$scope.tableHeadersList = [
+ {title: 'Name', property: 'artifactDisplayName'},
+ {title: 'Type', property: 'artifactType'}
+ ];
+
+ this.$scope.artifacts = <ArtifactModel[]>_.values(this.$scope.component.artifacts);
+ this.$scope.sort = (sortBy:string):void => {
+ this.$scope.reverse = (this.$scope.sortBy === sortBy) ? !this.$scope.reverse : false;
+ this.$scope.sortBy = sortBy;
+ };
+
+
+ this.$scope.addOrUpdate = (artifact:Models.ArtifactModel):void => {
+ artifact.artifactGroupType = 'INFORMATIONAL';
+ this.ModalsHandler.openWizardArtifactModal(artifact, this.$scope.component).then(() => {
+ this.$scope.artifacts = <ArtifactModel[]>_.values(this.$scope.component.artifacts);
+ });
+ };
+
+ this.$scope.showNoArtifactMessage = ():boolean => {
+ let artifacts:any = [];
+ artifacts = _.filter(this.$scope.artifacts, (artifact:Models.ArtifactModel)=> {
+ return artifact.esId;
+ });
+
+ if (artifacts.length === 0) {
+ return true;
+ }
+ return false;
+ };
+
+ this.$scope.delete = (artifact:Models.ArtifactModel):void => {
+
+ let onOk = ():void => {
+ this.$scope.isLoading = true;
+ let onSuccess = ():void => {
+ this.$scope.isLoading = false;
+ this.$scope.artifacts = <ArtifactModel[]>_.values(this.$scope.component.artifacts);
+ };
+
+ let onFailed = (error:any):void => {
+ console.log('Delete artifact returned error:', error);
+ this.$scope.isLoading = false;
+ };
+
+ this.$scope.component.deleteArtifact(artifact.uniqueId, artifact.artifactLabel).then(onSuccess, onFailed);
+ };
+
+ let title:string = this.$filter('translate')("ARTIFACT_VIEW_DELETE_MODAL_TITLE");
+ let message:string = this.$filter('translate')("ARTIFACT_VIEW_DELETE_MODAL_TEXT", "{'name': '" + artifact.artifactDisplayName + "'}");
+ this.ModalsHandler.openConfirmationModal(title, message, false).then(onOk);
+ };
+
+ this.$scope.clickArtifactName = (artifact:any) => {
+ if (!artifact.esId) {
+ this.$scope.addOrUpdate(artifact);
+ }
+
+ };
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/information-artifacts/information-artifacts-view.html b/catalog-ui/app/scripts/view-models/workspace/tabs/information-artifacts/information-artifacts-view.html
new file mode 100644
index 0000000000..790117b2fd
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/information-artifacts/information-artifacts-view.html
@@ -0,0 +1,57 @@
+<div class="workspace-information-artifact">
+ <div data-tests-id="add-information-artifact-button" ng-if="!isViewMode()"
+ data-ng-class="{'disabled': isDisableMode()}"
+ data-tests-id="addGrey" class="add-btn" data-ng-click="addOrUpdate({})" type="button">Add </div>
+ <div class="table-container-flex">
+ <div class="table" data-ng-class="{'view-mode': isViewMode()}">
+ <div class="head flex-container">
+ <div class="table-header head-row hand flex-item" ng-repeat="header in tableHeadersList track by $index" data-ng-click="sort(header.property)">{{header.title}}
+ <span data-ng-show="sortBy === header.property" class="table-header-sort-arrow" data-ng-class="{'down': reverse, 'up':!reverse}"> </span>
+ </div>
+ <div class="table-no-text-header head-row flex-item"></div>
+ </div>
+ <div class="body">
+ <perfect-scrollbar suppress-scroll-x="true" scroll-y-margin-offset="0" include-padding="true" class="scrollbar-container">
+ <div data-ng-if="showNoArtifactMessage()" class="no-row-text" data-ng-class="{'disabled': isDisableMode()}">
+ There are no information artifacts to display
+ </div>
+ <div data-ng-repeat-start="artifact in artifacts| orderBy:sortBy:reverse track by $index" data-tests-id="InformationalArtifactRow"
+ class="flex-container data-row"
+ data-ng-class="{'selected': artifact.selected}"
+ data-ng-if="artifact.esId">
+
+ <div class="table-col-general flex-item" data-ng-click="artifact.selected = !artifact.selected">
+ <span class="sprite table-arrow" data-ng-class="{'opened': artifact.selected}" data-tests-id="{{artifact.artifactDisplayName}}"></span>
+ {{artifact.artifactDisplayName}}
+ </div>
+
+ <div class="table-col-general flex-item" data-ng-click="artifact.selected = !artifact.selected" data-tests-id="{{artifact.artifactType}}">
+ {{artifact.artifactType}}
+ </div>
+
+ <div class="table-btn-col flex-item">
+ <button class="table-edit-btn" data-tests-id="edit_{{artifact.artifactDisplayName}}" data-ng-if="!isViewMode() && !artifact.isThirdParty()" data-ng-click="addOrUpdate(artifact)" data-ng-class="{'disabled': isDisableMode()}"></button>
+ <button class="table-delete-btn" data-tests-id="delete_{{artifact.artifactDisplayName}}" data-ng-if="!isViewMode() && !artifact.isThirdParty()" data-ng-click="delete(artifact)" data-ng-class="{'disabled': isDisableMode()}"> </button>
+ <button class="table-download-btn" download-artifact data-tests-id="download_{{artifact.artifactDisplayName}}"
+ data-ng-if="artifact.artifactName" component="component" artifact="artifact"></button>
+ </div>
+ </div>
+ <div data-ng-repeat-end="" data-ng-if="artifact.selected" class="item-opened" data-tests-id="{{artifact.artifactDisplayName}}Description" data-ng-bind="artifact.description"></div>
+ <button class="add-button" data-ng-repeat="artifact in artifacts track by $index"
+ data-ng-show="!artifact.esId"
+ data-tests-id="{{artifact.artifactDisplayName}}"
+ ng-if="!isViewMode()"
+ data-ng-class="{'disabled': isDisableMode()}"
+ translate="DEPLOYMENT_ARTIFACT_BUTTON_ADD_HEAT"
+ translate-values="{'name': '{{artifact.artifactDisplayName}}'}"
+ data-ng-click="addOrUpdate(artifact)"></button>
+ <button class="add-button"
+ ng-if="!isViewMode()"
+ data-ng-class="{'disabled': isDisableMode()}"
+ translate="DEPLOYMENT_ARTIFACT_BUTTON_ADD_OTHER"
+ data-ng-click="addOrUpdate({})"></button>
+ </perfect-scrollbar>
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/information-artifacts/information-artifacts.less b/catalog-ui/app/scripts/view-models/workspace/tabs/information-artifacts/information-artifacts.less
new file mode 100644
index 0000000000..d3fe14d945
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/information-artifacts/information-artifacts.less
@@ -0,0 +1,47 @@
+.workspace-information-artifact {
+ width: 93%;
+ display: inline-block;
+ .w-sdc-classic-btn {
+ float: right;
+ margin-bottom: 10px;
+ }
+
+ .table{
+ height: 490px;
+ margin-bottom: 0;
+ }
+
+ .table-container-flex {
+ margin-top: 27px;
+
+ .item-opened{
+ word-wrap: break-word;
+ }
+
+
+ .flex-item:nth-child(1) {
+ flex-grow: 15;
+ .hand;
+ span.table-arrow {
+ margin-right: 7px;
+ }
+ }
+
+ .flex-item:nth-child(2) {
+ flex-grow: 6;
+ }
+
+ .flex-item:nth-child(3) {
+ flex-grow: 3;
+ padding-top: 10px;
+ }
+
+ .flex-item:nth-child(4) {
+ flex-grow: 1;
+ }
+
+ }
+
+}
+
+
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/inputs/inputs.less b/catalog-ui/app/scripts/view-models/workspace/tabs/inputs/inputs.less
new file mode 100644
index 0000000000..76a82c69ee
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/inputs/inputs.less
@@ -0,0 +1,286 @@
+.workspace-inputs {
+
+ .sdc-workspace-container .w-sdc-main-right-container .w-sdc-main-container-body-content {
+ padding: 25px 8% 0px 8%;
+ }
+
+ width: 100%;
+ display: flex;
+
+ .text {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ padding-left: 15px;
+ }
+
+ .title-text {
+ color: @main_color_m;
+ .f-type._13_m;
+ .bold;
+ }
+
+ .title-blue-text {
+ color: @main_color_a;
+ .f-type._13_m;
+
+ &.property-name-text {
+ padding-right: 13px;
+ border-right: 1px solid rgba(120, 136, 148, 0.26);
+ flex-grow: 1;
+ }
+ }
+
+ .instance-name-text {
+ flex-grow: 1;
+ }
+
+ ng-include {
+ width: 45%;
+ }
+
+ .w-sdc-inputs-search {
+ padding: 10px 20px 20px 0;
+ white-space: nowrap;
+ position: relative;
+ width: 60%;
+ height: 64px;
+
+ .inputs-search-icon {
+ top: 9px;
+ right: 11px;
+ }
+
+ .magnification-white {
+ .sprite-new;
+ .search-white-icon;
+ .hand;
+ }
+
+ .search-icon-container {
+ width: 35px;
+ height: 30px;
+ background-color: @main_color_a;
+ white-space: nowrap;
+ float: right;
+ position: relative;
+ bottom: 31px;
+ right: 1px;
+ border-radius: 0px 4px 4px 0px;
+ .hand
+ }
+ }
+
+ .total-inputs-count {
+ width: 100%;
+ font-weight: bold;
+ text-align: left;
+ }
+
+ .new-input-button {
+ margin: 9px 0 0 0;
+ }
+
+ .w-sdc-inputs-search-input {
+ border: 1px solid @color_e;
+ .border-radius(4px);
+ height: 32px;
+ margin: 0;
+ padding: 0px 28px 3px 10px;
+ vertical-align: 4px;
+ width: 100%;
+ outline: none;
+ font-style: italic;
+ }
+
+ .w-sdc-classic-btn {
+ float: right;
+ margin-bottom: 10px;
+ }
+
+ .prop-to-input-button {
+ position: absolute;
+ top: 50%;
+ margin-right: -20px;
+ margin-bottom: -10px;
+
+ }
+
+ .property-row {
+ border-bottom: 1px solid @border_color_d;
+ padding-left: 10px;
+ .property-name-container {
+ display: flex;
+ flex-grow: 4;
+ }
+
+ .type-schema-container {
+ flex-grow: 1;
+ border-left: 1px solid @border_color_d;
+ text-align: left;
+ line-height: 30px;
+ text-transform: capitalize;
+ width: 10px;
+ }
+
+ }
+
+ .table-container-flex {
+
+ .flex-item {
+ line-height: 22px;
+ }
+ .expand-collapse-table-row {
+
+ &.expanded {
+ .flex-container {
+ .expand-collapse-inputs-table-icon {
+ transform: rotate(180deg);
+ left: 0px;
+ }
+ }
+ }
+
+ .data-row {
+ background: @tlv_color_u;
+ .hand;
+ align-items: center;
+ padding: 0 12px;
+ margin: 1px 0px 1px 0px;
+ min-height: 40px;
+ }
+
+ .data-row:hover {
+ .bg_j;
+ }
+
+ .input-row {
+ padding-left: 45px;
+ background: @tlv_color_t;
+ border: @main_color_o solid 1px;
+ align-items: center;
+ .hand;
+ margin: 1px 0px 1px 0px;
+
+ &.service-input-row {
+ background: @tlv_color_u;
+ &.new-input {
+ background: @tlv_color_v;
+ }
+ }
+ &.selected {
+ background-color: @tlv_color_v;
+ }
+ .flex-item {
+ min-height: 60px;
+ padding: 0 15px;
+ }
+ .input-check-box {
+ padding-right: 10px;
+ margin-top: 9px;
+ }
+ &>.title-text{
+ text-align: start;
+ padding: 4px 0 0 35px;
+ }
+
+ .expand-collapse-inputs-table-icon{
+ margin-top: 15px;
+ }
+
+ }
+
+ .input-row:hover {
+ .bg_j;
+ }
+
+
+ }
+ }
+
+ .table {
+ height: 640px;
+ margin-bottom: 0;
+ clear: both;
+
+ .empty-row {
+ padding: 3px;
+ }
+
+ .flex-item {
+ line-height: 22px;
+ }
+
+ .table-header {
+
+ line-height: 14px;
+ background-color: @main_color_a;
+ color: @main_color_p;
+ text-align: left;
+ padding: 7px 5px 7px 10px;
+ .f-type._14_m;
+ }
+ .head {
+ background-color: #e6e6e6;
+ }
+
+ .property-row:hover{
+ background-color: @func_color_r;
+ }
+
+ .body {
+ .scrollbar-container {
+ .perfect-scrollbar;
+ max-height: 610px;
+ }
+
+ .expand-collapse-inputs-table-icon {
+ .hand;
+ .sprite-new;
+ .arrow-up;
+ transition: .3s all;
+ position: relative;
+ left: 8px;
+ border: none !important;
+ padding: 0px 10px 0px 10px;
+ }
+
+ .table-col-text {
+ margin-left: 14px;
+ }
+ }
+ }
+
+ .inputs-header {
+ width: 100%;
+ position: relative;
+ bottom: 31px;
+ }
+
+ .inputs-tables-container {
+ width: 100%;
+ min-width: 100%;
+ display: flex;
+ }
+
+ .inputs-button-container {
+ width: 8%;
+ min-width: 8%;
+ display: flex;
+
+ .right-arrow-btn {
+ .sprite-new;
+ .blue-right-arrow-circle;
+ margin: auto;
+ cursor: pointer;
+ }
+ }
+
+ .table-container-flex {
+ margin-top: 27px;
+ width: 46%;
+ min-width: 46%;
+ display: inline-block;
+ float: left;
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/inputs/resource-input/resource-inputs-view-model.ts b/catalog-ui/app/scripts/view-models/workspace/tabs/inputs/resource-input/resource-inputs-view-model.ts
new file mode 100644
index 0000000000..2dc1b1d9ff
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/inputs/resource-input/resource-inputs-view-model.ts
@@ -0,0 +1,145 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../../../../references"/>
+module Sdc.ViewModels {
+ 'use strict';
+ import Dictionary = Sdc.Utils.Dictionary;
+ import InputModel = Sdc.Models.InputModel;
+
+ export interface IInputsViewModelScope extends IWorkspaceViewModelScope {
+ InstanceInputsProperties:Models.InstanceInputsPropertiesMapData; //this is tha map object that hold the selected inputs and the inputs we already used
+ vfInstancesList: Array<Models.ComponentsInstances.ComponentInstance>;
+ component:Models.Components.Resource;
+
+ onArrowPressed():void;
+ getInputPropertiesForInstance(instanceId:string, instance:Models.ComponentsInstances.ComponentInstance): ng.IPromise<boolean> ;
+ loadInputPropertiesForInstance(instanceId:string, input:Models.InputModel): ng.IPromise<boolean> ;
+ loadInputInputs(input:Models.InputModel): ng.IPromise<boolean>;
+ }
+
+ export class ResourceInputsViewModel {
+
+ static '$inject' = [
+ '$scope',
+ '$q'
+ ];
+
+ constructor(private $scope:IInputsViewModelScope, private $q: ng.IQService) {
+ this.initScope();
+ }
+
+ private initScope = (): void => {
+
+ this.$scope.InstanceInputsProperties = new Models.InstanceInputsPropertiesMapData();
+ this.$scope.vfInstancesList = this.$scope.component.componentInstances;
+
+ // Need to cast all inputs to InputModel for the search to work
+ let tmpInputs:Array<Models.InputModel> = new Array<Models.InputModel>();
+ _.each(this.$scope.component.inputs, (input):void => {
+ tmpInputs.push(new Models.InputModel(input));
+ });
+ this.$scope.component.inputs = tmpInputs;
+ // This function is not supported for resource
+ //this.$scope.component.getComponentInputs();
+
+ /*
+ * When clicking on instance input in the left or right table, this function will load all properties of the selected input
+ */
+ this.$scope.getInputPropertiesForInstance = (instanceId:string, instance:Models.ComponentsInstances.ComponentInstance): ng.IPromise<boolean> => {
+ let deferred = this.$q.defer();
+ instance.properties = this.$scope.component.componentInstancesProperties[instanceId];
+ deferred.resolve(true);
+ return deferred.promise;
+ };
+
+ /*
+ * When clicking on input in the right table, this function will load all inputs of the selected input
+ */
+ this.$scope.loadInputInputs = (input:Models.InputModel): ng.IPromise<boolean> => {
+ let deferred = this.$q.defer();
+
+ let onSuccess = () => { deferred.resolve(true); };
+ let onError = () => { deferred.resolve(false); };
+
+ if(!input.inputs) {
+ this.$scope.component.getResourceInputInputs(input.uniqueId).then(onSuccess, onError);
+ } else {
+ deferred.resolve(true);
+ }
+ return deferred.promise;
+ };
+
+ /*
+ * When clicking on instance input in the left or right table, this function will load all properties of the selected input
+ */
+ this.$scope.loadInputPropertiesForInstance = (instanceId:string, input:Models.InputModel): ng.IPromise<boolean> => {
+ let deferred = this.$q.defer();
+
+ let onSuccess = (properties:Array<Models.PropertyModel>) => {
+ input.properties = properties;
+ deferred.resolve(true);
+ };
+
+ let onError = () => {
+ deferred.resolve(false)
+ };
+
+ if(!input.properties) {
+ this.$scope.component.getComponentInstanceInputProperties(instanceId, input.uniqueId).then(onSuccess, onError);
+ } else {
+ deferred.resolve(true);
+ }
+ return deferred.promise;
+ };
+
+ /*
+ * When pressing the arrow, we create service inputs from the inputs selected
+ */
+ this.$scope.onArrowPressed = ():void => {
+ let onSuccess = (inputsCreated: Array<Models.InputModel>) => {
+
+ //disabled all the inputs in the left table
+ _.forEach(this.$scope.InstanceInputsProperties, (properties:Array<Models.PropertyModel>) => {
+ _.forEach(properties, (property:Models.PropertyModel) => {
+ property.isAlreadySelected = true;
+ });
+ });
+
+ // Adding color to the new inputs (right table)
+ _.forEach(inputsCreated, (input) => {
+ input.isNew = true;
+ });
+
+ // Removing color to the new inputs (right table)
+ setTimeout(() => {
+ _.forEach(inputsCreated, (input) => {
+ input.isNew = false;
+ });
+ this.$scope.$apply();
+ }, 3000);
+ };
+
+ this.$scope.component.createInputsFormInstances(this.$scope.InstanceInputsProperties).then(onSuccess);
+ };
+
+ }
+
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/inputs/resource-input/resource-inputs-view.html b/catalog-ui/app/scripts/view-models/workspace/tabs/inputs/resource-input/resource-inputs-view.html
new file mode 100644
index 0000000000..7cdf5a2fa4
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/inputs/resource-input/resource-inputs-view.html
@@ -0,0 +1,136 @@
+<div class="workspace-inputs">
+ <div class="table-container-flex">
+ <div class="w-sdc-inputs-search pull-left hideme">
+ <input type="text" class="w-sdc-inputs-search-input" placeholder="Search"/>
+ <div class="search-icon-container">
+ <span class="w-sdc-search-icon inputs-search-icon magnification-white"></span>
+ </div>
+ </div>
+ <div class="table">
+ <div class="table-header">VFC instances inputs</div>
+ <div class="body">
+ <div class="table-loader" ng-class="{'tlv-loader large loader': isLoading}"></div>
+ <perfect-scrollbar scroll-y-margin-offset="0" class="scrollbar-container">
+
+ <expand-collapse expanded-selector=".vf-instance-list.{{$index}}"
+ class="expand-collapse-table-row"
+ load-data-function="getInputPropertiesForInstance(instance.uniqueId, instance)"
+ is-close-on-init="true"
+ data-ng-repeat-start="instance in vfInstancesList track by $index">
+ <div class="flex-container data-row">
+ <div class="expand-collapse-inputs-table-icon"></div>
+ <div class="table-col-general flex-item text">
+ <span class="title-text">{{instance.name}}</span>
+ </div>
+ </div>
+
+ </expand-collapse>
+
+ <div data-ng-repeat-end="" class="vf-instance-list {{$index}}">
+
+ <div class="empty-row" ng-if="instance.properties.length===0">No properties to display</div>
+
+ <div ng-repeat="property in instance.properties track by $index">
+ <div class="property-row flex-container">
+ <div class="flex-item text property-name-container">
+ <span class="title-blue-text property-name-text">{{property.name}}</span>
+ <span class="text instance-name-text">{{property.name}}</span>
+ </div>
+ <div class="type-schema-container">
+ <div class="text">
+ <span>{{property.type}}</span>
+ </div>
+ </div>
+ <div class="type-schema-container">
+ <div class="text">
+ <span>{{property.schema.property.type}} </span>
+ </div>
+ </div>
+ <!--<sdc-checkbox
+ class="type-schema-container input-check-box"
+ disabled ="property.isAlreadySelected"
+ sdc-checklist-model="InstanceInputsProperties[instance.uniqueId]"
+ sdc-checklist-value="property"
+ data-ng-click="$event.stopPropagation()"></sdc-checkbox>-->
+ </div>
+ </div>
+
+ </div>
+
+ </perfect-scrollbar>
+ </div>
+ </div>
+ </div>
+
+ <div class="inputs-button-container pull-left">
+ <!--<div ng-click="onArrowPressed()" class="right-arrow-btn"></div>-->
+ </div>
+
+ <div class="table-container-flex">
+ <div class="w-sdc-inputs-search pull-left">
+ <input type="text" class="w-sdc-inputs-search-input" data-ng-model="search.filterTerm" placeholder="Search" data-ng-model-options="{debounce: 200}"/>
+ <div class="search-icon-container">
+ <span class="w-sdc-search-icon inputs-search-icon magnification-white"></span>
+ </div>
+ </div>
+ <div class="table">
+ <div class="body">
+ <div class="table-header">Resource instance inputs</div>
+ <perfect-scrollbar scroll-y-margin-offset="0" include-padding="true" class="scrollbar-container">
+ <expand-collapse expanded-selector=".resource-inputs.{{$index}}"
+ class="expand-collapse-table-row"
+ load-data-function="loadInputPropertiesForInstance(resourceInput.uniqueId, resourceInput)"
+ is-close-on-init="true"
+ data-ng-repeat-start="resourceInput in component.inputs | filter:search track by $index ">
+ <div class="input-row service-input-row">
+ <div class="title-text">{{resourceInput.name}}</div>
+ <div class="flex-container" ng-class="resourceInput.isNew ? 'new-input': 'service-input-row'">
+ <div class="expand-collapse-inputs-table-icon"></div>
+ <div class="flex-item">
+ <div>
+ <span class="title-text">Description:</span>
+ <span>{{resourceInput.description}}</span>
+ </div>
+ </div>
+ <div class="flex-item ">
+ <div class="text">
+ <span class="title-text">VF Instance:</span>
+ <span>{{resourceInput.name}}</span>
+ </div>
+ <div class="text">
+ <span class="title-text">Type:</span>
+ <span>{{resourceInput.type}} </span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </expand-collapse>
+
+ <div data-ng-repeat-end="" class="input-inputs-list resource-inputs {{$index}}">
+ <div class="empty-row" ng-if="resourceInput.properties.length===0">No properties to display</div>
+ <div ng-repeat="property in resourceInput.properties track by $index">
+ <div class="property-row flex-container">
+ <div class="flex-item text property-name-container">
+ <span
+ class="title-blue-text property-name-text">{{property.name}}</span>
+ <span class="text instance-name-text">{{property.name}}</span>
+ </div>
+ <div class="type-schema-container">
+ <div class="text">
+ <span>{{property.type}}</span>
+ </div>
+ </div>
+ <div class="type-schema-container">
+ <div class="text">
+ <span>{{property.schema.property.type}} </span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ </perfect-scrollbar>
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/inputs/resource-input/resource-inputs.less b/catalog-ui/app/scripts/view-models/workspace/tabs/inputs/resource-input/resource-inputs.less
new file mode 100644
index 0000000000..ebb32fbdb2
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/inputs/resource-input/resource-inputs.less
@@ -0,0 +1,9 @@
+.workspace-inputs {
+
+ .property-row {
+ .input-check-box {
+ text-align: center;
+ }
+ }
+
+}
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/inputs/service-input/service-inputs-view-model.ts b/catalog-ui/app/scripts/view-models/workspace/tabs/inputs/service-input/service-inputs-view-model.ts
new file mode 100644
index 0000000000..6c8391720a
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/inputs/service-input/service-inputs-view-model.ts
@@ -0,0 +1,246 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../../../../references"/>
+module Sdc.ViewModels {
+ 'use strict';
+ import IAngularEvent = angular.IAngularEvent;
+ import ComponentInstance = Sdc.Models.ComponentsInstances.ComponentInstance;
+
+
+ interface IServiceInputsViewModelScope extends IWorkspaceViewModelScope {
+
+ vfInstancesList: Array<ComponentInstance>;
+ selectedInputs:Array<Models.InputModel>;
+ instanceInputsMap:Models.InstancesInputsMapData; //this is tha map object that hold the selected inputs and the inputs we already used
+ component:Models.Components.Service;
+ sdcMenu:Models.IAppMenu;
+
+ onArrowPressed():void;
+ loadComponentInputs(): void;
+ loadInstanceInputs(instance:ComponentInstance): ng.IPromise<boolean> ;
+ loadInputPropertiesForInstance(instanceId:string, input:Models.InputModel): ng.IPromise<boolean> ;
+ loadInputInputs(input:Models.InputModel): ng.IPromise<boolean>;
+ deleteInput(input:Models.InputModel):void
+ }
+
+ export class ServiceInputsViewModel {
+
+ static '$inject' = [
+ '$scope',
+ '$q',
+ 'ModalsHandler'
+ ];
+
+ constructor(private $scope:IServiceInputsViewModelScope,
+ private $q: ng.IQService,
+ private ModalsHandler: Sdc.Utils.ModalsHandler) {
+ this.initScope();
+ }
+
+ /*
+ * When loading the screen again, we need to disabled the inputs that already created on the service,
+ * we do that by comparing the service input name, to the instance name + '_' + the resource instance input name.
+ */
+ private disableEnableSelectedInputs = (instance: ComponentInstance): void => {
+
+ let alreadySelectedInput = new Array<Models.InputModel>();
+ _.forEach(instance.inputs, (input:Models.InputModel) => {
+ let expectedServiceInputName = instance.normalizedName + '_' + input.name;
+ let inputAlreadyInService: Models.InputModel = _.find(this.$scope.component.inputs, (serviceInput: Models.InputModel) => {
+ return serviceInput.name === expectedServiceInputName;
+ });
+ if(inputAlreadyInService) {
+ input.isAlreadySelected = true;
+ alreadySelectedInput.push(input);
+ } else {
+ input.isAlreadySelected = false;
+ }
+ });
+ this.$scope.instanceInputsMap[instance.uniqueId] = alreadySelectedInput;
+ };
+
+ private initScope = (): void => {
+
+ this.$scope.instanceInputsMap = new Models.InstancesInputsMapData();
+ this.$scope.isLoading = true;
+ this.$scope.selectedInputs = new Array<Models.InputModel>();
+
+ // Why do we need this? we call this later.
+ //this.$scope.component.getComponentInputs();
+
+ let onSuccess = (componentInstances:Array<ComponentInstance>) => {
+ console.log("component instances loaded: ", componentInstances);
+ this.$scope.vfInstancesList = componentInstances;
+ this.$scope.isLoading = false;
+ };
+
+ //This function will get al component instance for the left table - in future the instances will be filter according to search text
+ this.$scope.component.getComponentInstancesFilteredByInputsAndProperties().then(onSuccess);
+
+ // This function will get the service inputs for the right table
+ this.$scope.component.getComponentInputs();
+
+
+ /*
+ * When clicking on instance in the left table, this function will load all instance inputs
+ */
+ this.$scope.loadInstanceInputs = (instance:ComponentInstance): ng.IPromise<boolean> => {
+ let deferred = this.$q.defer();
+
+ let onSuccess = (inputs:Array<Models.InputModel>) => {
+ instance.inputs = inputs;
+ this.disableEnableSelectedInputs(instance);
+ deferred.resolve(true);
+ };
+
+ let onError = () => {
+ deferred.resolve(false);
+ };
+
+ if(!instance.inputs) {
+ this.$scope.component.getComponentInstanceInputs(instance.uniqueId, instance.componentUid).then(onSuccess, onError);
+ this.disableEnableSelectedInputs(instance);
+ } else {
+ deferred.resolve(true);
+ }
+ return deferred.promise;
+ };
+
+ /*
+ * When clicking on instance input in the left or right table, this function will load all properties of the selected input
+ */
+ this.$scope.loadInputPropertiesForInstance = (instanceId:string, input:Models.InputModel): ng.IPromise<boolean> => {
+ let deferred = this.$q.defer();
+
+ let onSuccess = (properties:Array<Models.PropertyModel>) => {
+ input.properties = properties;
+ deferred.resolve(true);
+ };
+
+ let onError = () => {
+ deferred.resolve(false)
+ };
+
+ if(!input.properties) {
+ this.$scope.component.getComponentInstanceInputProperties(instanceId, input.uniqueId).then(onSuccess, onError);
+ } else {
+ deferred.resolve(true);
+ }
+ return deferred.promise;
+ };
+
+ /*
+ * When clicking on input in the right table, this function will load all inputs of the selected input
+ */
+ this.$scope.loadInputInputs = (input:Models.InputModel): ng.IPromise<boolean> => {
+ let deferred = this.$q.defer();
+
+ let onSuccess = () => { deferred.resolve(true); };
+ let onError = () => { deferred.resolve(false); };
+
+ if(!input.inputs) { // Caching, if exists do not get it.
+ this.$scope.component.getServiceInputInputs(input.uniqueId).then(onSuccess, onError);
+ } else {
+ deferred.resolve(true);
+ }
+ return deferred.promise;
+ };
+
+ /*
+ * When pressing the arrow, we create service inputs from the inputs selected
+ */
+ this.$scope.onArrowPressed = ():void => {
+ let onSuccess = (inputsCreated: Array<Models.InputModel>) => {
+
+ //disabled all the inputs in the left table
+ _.forEach(this.$scope.instanceInputsMap, (inputs:Array<Models.InputModel>, instanceId:string) => {
+ _.forEach(inputs, (input:Models.InputModel) => {
+ input.isAlreadySelected = true;
+ });
+ });
+
+ this.addColorToItems(inputsCreated);
+ };
+
+ this.$scope.component.createInputsFormInstances(this.$scope.instanceInputsMap).then(onSuccess);
+ };
+
+ this.$scope.deleteInput = (input: Models.InputModel):void => {
+
+ var onDelete = ():void => {
+ var onSuccess = (deletedInput: Models.InputModel, componentInstanceId:string):void => {
+ // Remove from component.inputs the deleted input (service inputs)
+ var remainingServiceInputs:Array<Models.InputModel> = _.filter(this.$scope.component.inputs, (input:Models.InputModel):boolean => {
+ return input.uniqueId !== deletedInput.uniqueId;
+ });
+ this.$scope.component.inputs = remainingServiceInputs;
+
+ // Find the instance that contains the deleted input, and set disable|enable the deleted input
+ var deletedInputComponentInstance:ComponentInstance = _.find(this.$scope.vfInstancesList, (instanceWithChildToDelete:ComponentInstance):boolean => {
+ return instanceWithChildToDelete.uniqueId === componentInstanceId;
+ });
+ this.disableEnableSelectedInputs(deletedInputComponentInstance);
+ };
+
+ var onFailed = (error:any) : void => {
+ console.log("Error deleting input");
+ };
+
+ this.addColorToItems([input]);
+
+ // Get service inputs of input (so after delete we will know the component instance)
+ this.$scope.loadInputInputs(input).then((result:boolean):void=>{
+ if (result && input.inputs.length>0) {
+ var componentInstanceId:string = input.inputs[0].componentInstanceId;
+ this.$scope.component.deleteServiceInput(input.uniqueId).then((deletedInput: Models.InputModel):void => {
+ onSuccess(deletedInput, componentInstanceId);
+ }, onFailed);
+ }
+ });
+ };
+
+ // Get confirmation modal text from menu.json
+ var state = "deleteInput";
+ var title:string = this.$scope.sdcMenu.alertMessages[state].title;
+ var message:string = this.$scope.sdcMenu.alertMessages[state].message.format([input.name]);
+
+ // Open confirmation modal
+ this.ModalsHandler.openAlertModal(title, message).then(onDelete);
+ }
+ };
+
+ private addColorToItems = (inputsCreated:Array<Models.InputModel>):void => {
+
+ // Adding color to the new inputs (right table)
+ _.forEach(inputsCreated, (input) => {
+ input.isNew = true;
+ });
+
+ // Removing color to the new inputs (right table)
+ setTimeout(() => {
+ _.forEach(inputsCreated, (input) => {
+ input.isNew = false;
+ });
+ this.$scope.$apply();
+ }, 3000);
+ };
+
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/inputs/service-input/service-inputs-view.html b/catalog-ui/app/scripts/view-models/workspace/tabs/inputs/service-input/service-inputs-view.html
new file mode 100644
index 0000000000..bf15a70322
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/inputs/service-input/service-inputs-view.html
@@ -0,0 +1,205 @@
+<div class="workspace-inputs">
+ <div class="table-container-flex">
+ <div class="w-sdc-inputs-search pull-left hideme">
+ <input type="text" class="w-sdc-inputs-search-input" placeholder="Search"/>
+ <div class="search-icon-container">
+ <span class="w-sdc-search-icon inputs-search-icon magnification-white"></span>
+ </div>
+ </div>
+ <div class="table">
+ <div class="table-header">Resource instance inputs</div>
+ <div class="body">
+ <div class="table-loader" ng-class="{'tlv-loader large loader': isLoading}"></div>
+ <perfect-scrollbar scroll-y-margin-offset="0" class="scrollbar-container">
+
+ <expand-collapse expanded-selector=".vf-instance-list.{{$index}}"
+ class="expand-collapse-table-row"
+ load-data-function="loadInstanceInputs(instance)"
+ is-close-on-init="true"
+ data-ng-repeat-start="instance in vfInstancesList track by $index">
+ <div class="flex-container data-row">
+ <div class="expand-collapse-inputs-table-icon"></div>
+ <div class="table-col-general flex-item text" data-tests-id="inputs-vf-instance-{{$index}}">
+ <span class="title-text">{{instance.name}}</span>
+ </div>
+ </div>
+
+ </expand-collapse>
+
+ <div data-ng-repeat-end="" class="vf-instance-list {{$index}}">
+
+ <expand-collapse expanded-selector=".input-list.{{$parent.$index}}-{{$index}}"
+ class="expand-collapse-table-row"
+ load-data-function="loadInputPropertiesForInstance(instance.uniqueId, input)"
+ is-close-on-init="true"
+ data-ng-repeat-start="input in instance.inputs track by $index">
+ <div class="input-row" ng-class="{'selected': selectedInput.uniqueId === input.uniqueId}">
+ <div class="title-text">{{input.name}}</div>
+ <div class="flex-container">
+ <div class="expand-collapse-inputs-table-icon"></div>
+ <div class="flex-item">
+
+ <div>
+ <span class="title-text">Description:</span>
+ <span tooltips tooltip-content="{{input.description}}">{{input.description}}</span>
+ </div>
+ </div>
+ <div class="flex-item ">
+ <div class="text">
+ <span class="title-text">VF Instance:</span>
+ <span tooltips tooltip-content="{{instance.name}}">{{instance.name}}</span>
+ </div>
+ <div class="text">
+ <span class="title-text">Type:</span>
+ <span tooltips tooltip-content="{{input.type}}">{{input.type}} </span>
+ </div>
+ </div>
+ <sdc-checkbox
+ class="input-check-box"
+ disabled ="input.isAlreadySelected || isViewMode()"
+ sdc-checklist-model="instanceInputsMap[instance.uniqueId]"
+ sdc-checklist-value="input"
+ data-tests-id="inputs-checkbox-{{$index}}"
+ data-ng-click=" $event.stopPropagation()"></sdc-checkbox>
+ </div>
+ </div>
+
+
+ </expand-collapse>
+
+ <div data-ng-repeat-end="" class="input-list {{$parent.$index}}-{{$index}}">
+ <div class="empty-row" ng-if="input.properties.length===0">No properties to display</div>
+
+ <div ng-repeat="property in input.properties track by $index">
+ <div class="property-row flex-container">
+ <div class="flex-item text property-name-container">
+ <span class="title-blue-text property-name-text" tooltips tooltip-content="{{property.name}}">{{property.name}}</span>
+ <span class="text instance-name-text" tooltips tooltip-content="{{property.name}}">{{property.name}}</span>
+ </div>
+ <div class="type-schema-container">
+ <div class="text">
+ <span tooltips tooltip-content="{{property.type}}">{{property.type}}</span>
+ </div>
+ </div>
+ <div class="type-schema-container">
+ <div class="text">
+ <span tooltips tooltip-content="{{property.schema.property.type}}">{{property.schema.property.type}} </span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </perfect-scrollbar>
+ </div>
+ </div>
+ </div>
+
+ <div class="inputs-button-container pull-left">
+ <div ng-click="onArrowPressed()" class="right-arrow-btn" data-tests-id="add-inputs-to-service-button"></div>
+ </div>
+
+ <div class="table-container-flex">
+ <div class="w-sdc-inputs-search pull-left">
+ <input type="text" class="w-sdc-inputs-search-input" data-ng-model="search.filterTerm" placeholder="Search" data-ng-model-options="{debounce: 200}"/>
+ <div class="search-icon-container">
+ <span class="w-sdc-search-icon inputs-search-icon magnification-white"></span>
+ </div>
+ </div>
+ <div class="table">
+ <div class="body">
+ <div class="table-header">Service Inputs</div>
+ <perfect-scrollbar scroll-y-margin-offset="0" include-padding="true" class="scrollbar-container">
+ <expand-collapse expanded-selector=".service-inputs.{{$index}}"
+ class="expand-collapse-table-row"
+ load-data-function="loadInputInputs(serviceInput)"
+ is-close-on-init="true"
+ data-ng-repeat-start="serviceInput in component.inputs | filter:search track by $index ">
+ <div class="input-row service-input-row " data-tests-id="service-input-{{$index}}" ng-class="serviceInput.isNew ? 'new-input': ''">
+ <div class="title-text">{{serviceInput.name}}</div>
+ <div class="flex-container">
+ <div class="expand-collapse-inputs-table-icon"></div>
+ <div class="flex-item">
+ <div>
+ <span class="title-text">Description:</span>
+ <span tooltips tooltip-content="{{serviceInput.description}}">{{serviceInput.description}}</span>
+ </div>
+ </div>
+ <div class="flex-item ">
+ <div class="text">
+ <span class="title-text">VF Instance:</span>
+ <span tooltips tooltip-content="{{serviceInput.name}}">{{serviceInput.name}}</span>
+ </div>
+ <div class="text">
+ <span class="title-text">Type:</span>
+ <span tooltips tooltip-content="{{serviceInput.type}}">{{serviceInput.type}} </span>
+ </div>
+ </div>
+ <div class="delete">
+ <span class="sprite-new delete-icon remove-input-icon"
+ data-ng-class="{'disabled': isViewMode()}"
+ data-ng-click="deleteInput(serviceInput); $event.stopPropagation();"
+ data-tests-id="delete-input-{{$index}}"></span>
+ </div>
+ </div>
+ </div>
+ </expand-collapse>
+
+ <div data-ng-repeat-end="" class="service-inputs {{$index}}">
+ <expand-collapse expanded-selector=".input-inputs-list.{{$parent.$index}}-{{$index}}"
+ class="expand-collapse-table-row"
+ load-data-function="loadInputPropertiesForInstance(input.componentInstanceId, input)"
+ is-close-on-init="true"
+ data-ng-repeat-start="input in serviceInput.inputs track by $index">
+ <div class="input-row">
+ <div class="title-text">{{input.name}}</div>
+ <div class="flex-container">
+ <div class="expand-collapse-inputs-table-icon"></div>
+ <div class="flex-item">
+ <div>
+ <span class="title-text">Description:</span>
+ <span tooltips tooltip-content="{{input.description}}">{{input.description}}</span>
+ </div>
+ </div>
+ <div class="flex-item ">
+ <div class="text">
+ <span class="title-text">VF Instance:</span>
+ <span tooltips tooltip-content="{{instance.componentInstanceName}}">{{instance.componentInstanceName}}</span>
+ </div>
+ <div class="text">
+ <span class="title-text">Type:</span>
+ <span tooltips tooltip-content="{{input.type}}">{{input.type}} </span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </expand-collapse>
+
+ <div data-ng-repeat-end="" class="input-inputs-list {{$parent.$index}}-{{$index}}">
+ <div class="empty-row" ng-if="input.properties.length===0">No properties to display</div>
+ <div ng-repeat="property in input.properties track by $index">
+ <div class="property-row flex-container">
+ <div class="flex-item text property-name-container">
+ <span
+ class="title-blue-text property-name-text" tooltips tooltips-content="{{property.name}}">{{property.name}}</span>
+ <span class="text instance-name-text" tooltips tooltips-content="{{property.name}}">{{property.name}}</span>
+ </div>
+ <div class="type-schema-container">
+ <div class="text">
+ <span tooltips tooltips-content="{{property.type}}">{{property.type}}</span>
+ </div>
+ </div>
+ <div class="type-schema-container">
+ <div class="text">
+ <span tooltips tooltips-content="{{property.schema.property.type}}">{{property.schema.property.type}} </span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </perfect-scrollbar>
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/inputs/service-input/service-inputs.less b/catalog-ui/app/scripts/view-models/workspace/tabs/inputs/service-input/service-inputs.less
new file mode 100644
index 0000000000..11e613b56e
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/inputs/service-input/service-inputs.less
@@ -0,0 +1,54 @@
+.workspace-inputs {
+
+ .service-inputs-view {
+
+ .table-container-flex {
+ width:100% !important;
+ }
+
+ .table-loader {
+ position: relative;
+ top:215px;
+ }
+
+ }
+
+ .infinite-scroll {
+
+ overflow-y: scroll;
+ overflow-x: hidden;
+ max-height: 400px;
+ }
+
+ .class_with_css_props_leading_to_a_scroll {
+ height: 100%;
+ overflow-y: auto;
+ }
+
+ .table-container-flex {
+ .expand-collapse-table-row {
+ .service-input-row {
+ padding-left: 15px;
+ border: none;
+ border-bottom: rgba(120, 136, 148, 0.26) solid 1px;
+
+ .delete {
+ width: 50px;
+ padding: 0;
+ position: relative;
+ }
+
+ .remove-input-icon {
+ position: absolute;
+ top: 12px;
+ right: 18px;
+ }
+
+ .remove-input-icon:hover {
+ .delete-icon-hover;
+ }
+ }
+ }
+ }
+
+}
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/management-workflow/management-workflow-view-model.ts b/catalog-ui/app/scripts/view-models/workspace/tabs/management-workflow/management-workflow-view-model.ts
new file mode 100644
index 0000000000..2fab118378
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/management-workflow/management-workflow-view-model.ts
@@ -0,0 +1,128 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../../../references"/>
+module Sdc.ViewModels {
+ 'use strict';
+
+ export interface IManagementWorkflowViewModelScope extends IWorkspaceViewModelScope {
+ vendorModel:VendorModel;
+ }
+
+ export class VendorModel {
+ artifacts: Models.ArtifactGroupModel;
+ serviceID: string;
+ readonly: boolean;
+ sessionID: string;
+ requestID: string;
+ diagramType: string;
+ participants:Array<participant>;
+
+ constructor(artifacts: Models.ArtifactGroupModel, serviceID:string, readonly:boolean, sessionID:string,
+ requestID:string, diagramType:string, participants:Array<participant>){
+ this.artifacts = artifacts;
+ this.serviceID = serviceID;
+ this.readonly = readonly;
+ this.sessionID = sessionID;
+ this.requestID = requestID;
+ this.diagramType = diagramType;
+ this.participants = participants;
+ }
+ }
+
+ export class ManagementWorkflowViewModel {
+
+ static '$inject' = [
+ '$scope',
+ 'uuid4'
+ ];
+
+ constructor(private $scope:IManagementWorkflowViewModelScope,
+ private uuid4:any) {
+
+ this.initScope();
+ this.$scope.updateSelectedMenuItem();
+ }
+
+
+ private static getParticipants():Array<participant> {
+ return [
+ {
+ "id": "1",
+ "name": "Customer"},
+ {
+ "id": "2",
+ "name": "CCD"
+ },
+ {
+ "id": "3",
+ "name": "Infrastructure"
+ },
+ {
+ "id": "4",
+ "name": "MSO"
+ },
+ {
+ "id": "5",
+ "name": "SDN-C"
+ },
+ {
+ "id": "6",
+ "name": "A&AI"
+ },
+ {
+ "id": "7",
+ "name": "APP-C"
+ },
+ {
+ "id": "8",
+ "name": "Cloud"
+ },
+ {
+ "id": "9",
+ "name": "DCAE"
+ },
+ {
+ "id": "10",
+ "name": "ALTS"
+ },
+ {
+ "id": "11",
+ "name": "VF"
+ }
+ ]
+ }
+
+
+ private initScope():void {
+ this.$scope.vendorModel = new VendorModel(
+ this.$scope.component.artifacts.filteredByType(Utils.Constants.ArtifactType.THIRD_PARTY_RESERVED_TYPES.WORKFLOW),
+ this.$scope.component.uniqueId,
+ this.$scope.isViewMode(),
+ this.$scope.user.userId,
+ this.uuid4.generate(),
+ Utils.Constants.ArtifactType.THIRD_PARTY_RESERVED_TYPES.WORKFLOW,
+ ManagementWorkflowViewModel.getParticipants()
+ );
+
+ this.$scope.thirdParty = true;
+ this.$scope.setValidState(true);
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/management-workflow/management-workflow-view.html b/catalog-ui/app/scripts/view-models/workspace/tabs/management-workflow/management-workflow-view.html
new file mode 100644
index 0000000000..bd196daec8
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/management-workflow/management-workflow-view.html
@@ -0,0 +1,3 @@
+<div class="workspace-management-workflow">
+ <punch-out name="'sequence-diagram'" data="vendorModel" user="user" on-event="onVendorEvent"></punch-out>
+</div>
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/network-call-flow/network-call-flow-view-model.ts b/catalog-ui/app/scripts/view-models/workspace/tabs/network-call-flow/network-call-flow-view-model.ts
new file mode 100644
index 0000000000..064f1c5896
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/network-call-flow/network-call-flow-view-model.ts
@@ -0,0 +1,80 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../../../references"/>
+module Sdc.ViewModels {
+ 'use strict';
+
+ export interface INetworkCallFlowViewModelScope extends IWorkspaceViewModelScope {
+ vendorMessageModel:VendorModel;
+ }
+
+ export class participant {
+ name:string;
+ id:string;
+
+ constructor(instance:Models.ComponentsInstances.ComponentInstance){
+ this.name = instance.name;
+ this.id = instance.uniqueId;
+ }
+ }
+
+
+ export class NetworkCallFlowViewModel {
+
+ static '$inject' = [
+ '$scope',
+ 'uuid4'
+ ];
+
+ constructor(private $scope:INetworkCallFlowViewModelScope,
+ private uuid4:any) {
+
+ this.initScope();
+ this.$scope.updateSelectedMenuItem();
+ }
+
+ private getVFParticipantsFromInstances(instances:Array<Models.ComponentsInstances.ComponentInstance>):Array<participant> {
+ let participants = [];
+ _.forEach(instances,(instance)=> {
+ if(Utils.Constants.ResourceType.VF == instance.originType){
+ participants.push(new participant(instance));
+ }
+ });
+ return participants;
+ }
+
+
+ private initScope():void {
+ this.$scope.vendorMessageModel = new VendorModel(
+ this.$scope.component.artifacts.filteredByType(Utils.Constants.ArtifactType.THIRD_PARTY_RESERVED_TYPES.NETWORK_CALL_FLOW),
+ this.$scope.component.uniqueId,
+ this.$scope.isViewMode(),
+ this.$scope.user.userId,
+ this.uuid4.generate(),
+ Utils.Constants.ArtifactType.THIRD_PARTY_RESERVED_TYPES.NETWORK_CALL_FLOW,
+ this.getVFParticipantsFromInstances(this.$scope.component.componentInstances)
+ );
+
+ this.$scope.thirdParty = true;
+ this.$scope.setValidState(true);
+ }
+
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/network-call-flow/network-call-flow-view.html b/catalog-ui/app/scripts/view-models/workspace/tabs/network-call-flow/network-call-flow-view.html
new file mode 100644
index 0000000000..6ce3e8e2b7
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/network-call-flow/network-call-flow-view.html
@@ -0,0 +1,3 @@
+<div class="workspace-network-call-flow">
+ <punch-out name="'sequence-diagram'" data="vendorMessageModel" user="user" on-event="onVendorEvent"></punch-out>
+</div>
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/product-hierarchy/product-hierarchy-view-model.ts b/catalog-ui/app/scripts/view-models/workspace/tabs/product-hierarchy/product-hierarchy-view-model.ts
new file mode 100644
index 0000000000..faf77a5215
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/product-hierarchy/product-hierarchy-view-model.ts
@@ -0,0 +1,134 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/**
+ * Created by obarda on 4/7/2016.
+ */
+/// <reference path="../../../../references"/>
+module Sdc.ViewModels {
+ 'use strict';
+
+ export interface IProductHierarchyScope extends IWorkspaceViewModelScope {
+
+ categoriesOptions: Array<Models.IMainCategory>;
+ product:Models.Components.Product;
+ isLoading:boolean;
+ showDropDown:boolean;
+
+ onInputTextClicked():void;
+ onGroupSelected(category: Models.IMainCategory, subcategory: Models.ISubCategory, group: Models.IGroup):void;
+ clickOutside():void;
+ deleteGroup(uniqueId:string):void;
+ }
+
+ export class ProductHierarchyViewModel {
+
+ static '$inject' = [
+ '$scope',
+ 'Sdc.Services.CacheService',
+ 'ComponentFactory',
+ '$state'
+ ];
+
+ constructor(private $scope:IProductHierarchyScope,
+ private cacheService:Sdc.Services.CacheService,
+ private ComponentFactory: Sdc.Utils.ComponentFactory,
+ private $state:ng.ui.IStateService) {
+
+
+ this.$scope.product = <Models.Components.Product>this.$scope.getComponent();
+ this.$scope.setValidState(true);
+ this.initScope();
+ this.$scope.updateSelectedMenuItem();
+ }
+
+ private initCategories = () => {
+ this.$scope.categoriesOptions = angular.copy(this.cacheService.get('productCategories'));
+ let selectedGroup:Array<Models.IGroup> = [];
+ _.forEach(this.$scope.product.categories, (category: Models.IMainCategory) => {
+ _.forEach(category.subcategories, (subcategory:Models.ISubCategory) => {
+ selectedGroup = selectedGroup.concat(subcategory.groupings);
+ });
+ });
+ _.forEach(this.$scope.categoriesOptions, (category: Models.IMainCategory) => {
+ _.forEach(category.subcategories, (subcategory:Models.ISubCategory) => {
+ _.forEach(subcategory.groupings, (group:Models.ISubCategory) => {
+ let componentGroup:Models.IGroup = _.find(selectedGroup, (componentGroupObj) => {
+ return componentGroupObj.uniqueId == group.uniqueId;
+ });
+ if(componentGroup){
+ group.isDisabled = true;
+ }
+ });
+ });
+ });
+ };
+
+ private setFormValidation = ():void => {
+ //if(!this.$scope.product.categories || this.$scope.product.categories.length === 0){
+ // this.$scope.setValidState(false);
+ //}
+ //else{
+ this.$scope.setValidState(true);
+ // }
+
+ };
+
+ private initScope = ():void => {
+ this.$scope.isLoading= false;
+ this.$scope.showDropDown =false;
+ this.initCategories();
+ this.setFormValidation();
+
+ this.$scope.onGroupSelected = (category: Models.IMainCategory, subcategory: Models.ISubCategory, group: Models.IGroup):void => {
+ this.$scope.product.addGroup(category, subcategory, group);
+ this.$state.current.data.unsavedChanges = !this.$scope.isViewMode();
+ group.isDisabled = true;
+ this.$scope.showDropDown = false;
+ this.setFormValidation();
+ };
+
+ this.$scope.onInputTextClicked = ():void => {//just edit the component in place, no pop up nor server update ?
+ this.$scope.showDropDown = !this.$scope.showDropDown;
+ };
+
+ this.$scope.clickOutside = (): any => {
+ this.$scope.showDropDown = false;
+ };
+
+ this.$scope.deleteGroup = (uniqueId:string) : void => {
+ //delete group from component
+ this.$scope.product.deleteGroup(uniqueId);
+ this.$state.current.data.unsavedChanges = !this.$scope.isViewMode();
+ this.setFormValidation();
+ //enabled group
+ _.forEach(this.$scope.categoriesOptions, (category: Models.IMainCategory) => {
+ _.forEach(category.subcategories, (subcategory:Models.ISubCategory) => {
+ let groupObj:Models.IGroup = _.find (subcategory.groupings, (group) => {
+ return group.uniqueId === uniqueId;
+ });
+ if(groupObj){
+ groupObj.isDisabled = false;
+ }
+ });
+ });
+ }
+ };
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/product-hierarchy/product-hierarchy-view.html b/catalog-ui/app/scripts/view-models/workspace/tabs/product-hierarchy/product-hierarchy-view.html
new file mode 100644
index 0000000000..2335ad7c74
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/product-hierarchy/product-hierarchy-view.html
@@ -0,0 +1,40 @@
+<div class="workspace-hierarchy">
+ <div class="dropdown-container" clicked-outside="{onClickedOutside: 'clickOutside()', clickedOutsideEnable: 'true'}" >
+ <input placeholder="Add Group" data-ng-click="onInputTextClicked()" class="dropdown-input-text" data-ng-model="search.filterTerms" data-ng-disabled="isViewMode()" data-ng-class="{'view-mode': isViewMode()}" data-ng-model-options="{debounce: 200}"/>
+ <div data-ng-class="{'show': showDropDown}" class="dropdown-content" >
+ <perfect-scrollbar scroll-y-margin-offset="0" include-padding="false" class="scrollbar-container">
+ <div ng-repeat="category in categoriesOptions track by $index">
+ <div ng-repeat="subcategory in category.subcategories track by $index">
+ <div class="dropdown-option" ng-show="!category.filteredGroup || category.filteredGroup.length > 0">
+ <div class="category-container">
+ <div class="category">{{category.name}}</div>
+ <div class="subcategory">{{subcategory.name}}</div>
+ </div>
+ <div class="groupings-container">
+ <div ng-init="group.filterTerms = group.name + ' ' + category.name + ' ' + subcategory.name"
+ ng-repeat="group in (category.filteredGroup = (subcategory.groupings | filter:search )) track by $index">
+ <div class="group" data-ng-disabled="group.isDisabled" data-ng-class="{'disabled-group': group.isDisabled}" ng-click="onGroupSelected(category, subcategory, group)">
+ <span >{{group.name}}</span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </perfect-scrollbar>
+ </div>
+ </div>
+ <div class="hierarchy-groups-container no-border-top" data-ng-class="{'view-mode': isViewMode()}">
+ <perfect-scrollbar scroll-y-margin-offset="0" include-padding="true" class="scrollbar-container">
+ <div ng-if="!product.categories.length || product.categories.length === 0" class="no-groups-text" translate="NEW_PRODUCT_NO_CATEGORIES_TO_DISPLAY"></div>
+ <div ng-repeat="category in product.categories track by $index">
+ <div ng-repeat="subcategory in category.subcategories track by $index">
+ <div class="group-tag" ng-repeat="group in subcategory.groupings track by $index"
+ data-ng-init="tooltip = '<b>' + category.name + '</b><br />' + subcategory.name">
+ <sdc-tag sdc-disable="isViewMode()" data-on-delete="deleteGroup(uniqueId)" data-tag-data="{tag: group.name, tooltip: tooltip, id: group.uniqueId }"></sdc-tag>
+ </div>
+ </div>
+ </div>
+ </perfect-scrollbar>
+ </div>
+</div>
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/product-hierarchy/product-hierarchy.less b/catalog-ui/app/scripts/view-models/workspace/tabs/product-hierarchy/product-hierarchy.less
new file mode 100644
index 0000000000..c992558ed2
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/product-hierarchy/product-hierarchy.less
@@ -0,0 +1,130 @@
+.workspace-hierarchy {
+ display: inline-block;
+ width: 93%;
+
+ .scrollbar-container{
+ max-height:400px;
+ .perfect-scrollbar;
+ }
+
+ .dropdown-container {
+ position: relative;
+ display: inline-block;
+ width: 100%;
+
+ &:after{
+ top: 47%;
+ right: 1%;
+ border: solid transparent;
+ content: " ";
+ height: 0;
+ width: 0;
+ position: absolute;
+ pointer-events: none;
+ border-color: rgba(0, 0, 0, 0);
+ border-top-color: black;
+ border-width: 4px;
+ margin-left: -4px;
+ }
+
+ .dropdown-input-text {
+ width: 100%;
+ padding: 4px 10px;
+ }
+
+ .dropdown-content {
+ .perfect-scrollbar;
+ border: 1px solid #d8d8d8;
+ display: none;
+ position: absolute;
+ overflow: hidden;
+ width: 100%;
+ .bg_c;
+ max-height: 400px;
+ z-index: 999999;
+
+ .dropdown-option {
+ border-bottom: 1px solid #d8d8d8;
+ display: inline-block;
+ width: 100%;
+ }
+
+ .category-container{
+ width: 250px;
+ float: left;
+ padding-left: 5px;
+
+ .category {
+ .bold;
+ padding: 3px 3px 2px 3px;
+ &:after{
+ .sprite;
+ .arrow-left;
+ content: '';
+ margin-left: 5px;
+ transform: rotate(180deg);
+ }
+ }
+ .subcategory {
+ padding-left: 3px;
+ }
+ }
+
+ .groupings-container{
+ display: inline-block;
+ width: 424px;
+ border-left: 1px solid #d8d8d8;
+ min-height: 55px;
+ .group{
+ padding: 3px 3px 3px 10px;
+ &:hover{
+ .hand;
+ .bg_n;
+ }
+ &.disabled-group {
+ opacity: 0.5;
+ &:hover{
+ cursor: auto;
+ .bg_c;
+ }
+ }
+ }
+ }
+
+ .seperator {
+ height: 1px;
+ width: 100%;
+ .bg_j;
+ margin: 5px 0px;
+ }
+ }
+ .show {
+ display: block;
+ }
+ }
+
+ .hierarchy-groups-container{
+ .b_9;
+ width: 100%;
+ border: 1px solid #d8d8d8;
+ height: 425px;
+ padding: 15px;
+ text-align: center;
+
+ .scrollbar-container {
+ z-index: 0;
+ }
+
+ .no-group-text{
+ text-align: center;
+ margin-top:25px;
+ a {
+ cursor: pointer;
+ }
+ }
+ .group-tag{
+ display: inline-block;
+ float: left;
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/properties/properties-view-model.ts b/catalog-ui/app/scripts/view-models/workspace/tabs/properties/properties-view-model.ts
new file mode 100644
index 0000000000..9b824bfca9
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/properties/properties-view-model.ts
@@ -0,0 +1,114 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../../../references"/>
+module Sdc.ViewModels {
+ 'use strict';
+
+ interface IPropertiesViewModelScope extends IWorkspaceViewModelScope {
+ tableHeadersList: Array<any>;
+ reverse: boolean;
+ sortBy:string;
+ filteredProperties:any;
+
+ addOrUpdateProperty(property?:Models.PropertyModel): void;
+ delete(property:Models.PropertyModel): void;
+ sort(sortBy:string): void;
+ }
+
+ export class PropertiesViewModel {
+
+ static '$inject' = [
+ '$scope',
+ '$filter',
+ '$modal',
+ '$templateCache',
+ 'ModalsHandler'
+ ];
+
+
+ constructor(private $scope:IPropertiesViewModelScope,
+ private $filter:ng.IFilterService,
+ private $modal:ng.ui.bootstrap.IModalService,
+ private $templateCache:ng.ITemplateCacheService,
+ private ModalsHandler:Utils.ModalsHandler) {
+ this.initScope();
+ this.$scope.updateSelectedMenuItem();
+ }
+
+
+ private openEditPropertyModal = (property:Models.PropertyModel):void => {
+ let viewModelsHtmlBasePath:string = '/app/scripts/view-models/';
+
+ let modalOptions:ng.ui.bootstrap.IModalSettings = {
+ template: this.$templateCache.get(viewModelsHtmlBasePath + 'forms/property-form/property-form-view.html'),
+ controller: 'Sdc.ViewModels.PropertyFormViewModel',
+ size: 'sdc-l',
+ backdrop: 'static',
+ keyboard: false,
+ resolve: {
+ property: ():Models.PropertyModel => {
+ return property;
+ },
+ component: ():Models.Components.Component => {
+ return <Models.Components.Component> this.$scope.component;
+ },
+ filteredProperties: ():Array<Models.PropertyModel> => {
+ return this.$scope.filteredProperties.properties;
+ }
+ }
+ };
+ this.$modal.open(modalOptions);
+ };
+
+ private initScope = ():void => {
+
+ //let self = this;
+ this.$scope.filteredProperties={properties:[]};
+ this.$scope.sortBy = 'name';
+ this.$scope.reverse = false;
+ this.$scope.setValidState(true);
+ this.$scope.tableHeadersList = [
+ {title: 'Name', property: 'name'},
+ {title: 'Type', property: 'type'},
+ {title: 'Schema', property: 'schema.property.type'},
+ {title: 'Description', property: 'description'},
+ ];
+ this.$scope.sort = (sortBy:string):void => {
+ this.$scope.reverse = (this.$scope.sortBy === sortBy) ? !this.$scope.reverse : false;
+ this.$scope.sortBy = sortBy;
+ };
+
+
+ this.$scope.addOrUpdateProperty = (property?:Models.PropertyModel):void => {
+ this.openEditPropertyModal(property ? property : new Models.PropertyModel());
+ };
+
+ this.$scope.delete = (property:Models.PropertyModel):void => {
+
+ let onOk = ():void => {
+ this.$scope.component.deleteProperty(property.uniqueId);
+ };
+ let title:string = this.$filter('translate')("PROPERTY_VIEW_DELETE_MODAL_TITLE");
+ let message:string = this.$filter('translate')("PROPERTY_VIEW_DELETE_MODAL_TEXT", "{'name': '" + property.name + "'}");
+ this.ModalsHandler.openConfirmationModal(title, message, false).then(onOk);
+ };
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/properties/properties-view.html b/catalog-ui/app/scripts/view-models/workspace/tabs/properties/properties-view.html
new file mode 100644
index 0000000000..e9a4c3879d
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/properties/properties-view.html
@@ -0,0 +1,62 @@
+<div class="workspace-properties">
+ <div id="left-top-bar">
+ <span id="properties-count">Total Properties: {{component.properties.length}}</span>
+ <input id="search-by-name" type="search" placeholder="Search" data-ng-model-options="{debounce: 200}" data-ng-model="filterTerms"/>
+ <span class="sprite magnification-glass search-button"></span>
+ </div>
+ <div class="add-btn" data-tests-id="addGrey" ng-if="!isViewMode()"
+ data-ng-class="{'disabled': isDisableMode()}" data-ng-click="addOrUpdateProperty()">Add Property</div>
+ <div class="table-container-flex">
+ <div class="table" data-ng-class="{'view-mode': isViewMode()}">
+ <div class="head flex-container">
+ <div class="table-header head-row hand flex-item" data-ng-repeat="header in tableHeadersList track by $index" data-ng-click="sort(header.property)">{{header.title}}
+ <span data-ng-if="sortBy === header.property" class="table-header-sort-arrow" data-ng-class="{'down': reverse, 'up':!reverse}"> </span>
+ </div>
+ <div class="table-no-text-header head-row flex-item" ng-if="!isViewMode()"><span class="delete-col-header"></span></div>
+ <!--div class="table-no-text-header head-row flex-item"></div-->
+ </div>
+
+ <div class="body">
+ <perfect-scrollbar scroll-y-margin-offset="0" include-padding="true" class="scrollbar-container">
+ <div data-ng-if="component.properties.length === 0" class="no-row-text" data-ng-class="{'disabled': isDisableMode()}">
+ There are no properties to display <br>
+ <span ng-if="!isViewMode()"> click <a data-ng-click="addOrUpdateProperty()">here</a> to add one </span>
+
+ </div>
+ <div data-ng-repeat="property in filteredProperties.properties=(component.properties | orderBy:sortBy:reverse | filter: {filterTerm:filterTerms}) track by $index"
+ data-tests-id="propertyRow" data-ng-class="{'selected': property.selected}"
+ class="flex-container data-row">
+
+ <div class="table-col-general flex-item text">
+ <a data-tests-id="{{property.name}}"
+ tooltips tooltip-content="{{property.name}}"
+ data-ng-click="addOrUpdateProperty(property); $event.stopPropagation();"
+ data-ng-class="{'disabled': isViewMode()}">{{property.name}}</a>
+
+ </div>
+
+ <div class="table-col-general flex-item text"
+ data-tests-id="{{property.type}}"
+ tooltips tooltip-content="{{property.type}}"
+ data-ng-bind="property.type">
+ </div>
+ <div class="table-col-general flex-item text"
+ data-tests-id="{{property.schema.property.type}}"
+ tooltips tooltip-content="{{property.schema.property.type}}"
+ data-ng-bind="property.schema.property.type">
+ </div>
+ <div class="table-col-general flex-item text">
+ <span tooltips tooltip-content="{{property.description}}" data-tests-id="{{property.description}}" data-ng-bind="property.description"></span>
+ </div>
+ <div class="table-btn-col flex-item" ng-if="!isViewMode()">
+ <button class="table-delete-btn" data-tests-id="delete_{{property.name}}" data-ng-if="property.parentUniqueId==component.uniqueId"
+ data-ng-click="delete(property); $event.stopPropagation();" data-ng-class="{'disabled': isViewMode()}"> </button>
+ </div>
+ </div>
+ </perfect-scrollbar>
+ </div>
+
+ </div>
+ </div>
+
+</div>
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/properties/properties.less b/catalog-ui/app/scripts/view-models/workspace/tabs/properties/properties.less
new file mode 100644
index 0000000000..3e8d6c3fbd
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/properties/properties.less
@@ -0,0 +1,115 @@
+.workspace-properties {
+
+ width: 93%;
+ display: inline-block;
+
+ #left-top-bar{
+ float: left;
+ width: 155px;
+ ::-webkit-input-placeholder {
+ font-style: italic;
+ }
+ :-moz-placeholder {
+ font-style: italic;
+ }
+ ::-moz-placeholder {
+ font-style: italic;
+ }
+ :-ms-input-placeholder {
+ font-style: italic;
+ }
+
+ #properties-count{
+ font-weight: bold;
+ float: left;
+ }
+
+ #search-by-name{
+ -webkit-border-radius: 2px;
+ -moz-border-radius: 2px;
+ border-radius: 2px;
+ width: 245px;
+ height: 32px;
+ line-height: 32px;
+ border: 1px solid @main_color_o;
+ text-indent: 10px;
+ }
+
+ .search-button{
+ .hand;
+ cursor: pointer;
+ float: right;
+ position: relative;
+ top: -22px;
+ right: -80px;
+ }
+ }
+
+ .add-btn {
+ margin: 36px 0 0 0;
+ }
+
+ .delete-col-header{
+ .sprite;
+ .sprite.e-sdc-small-icon-delete;
+ }
+
+ .w-sdc-classic-btn {
+ float: right;
+ margin-bottom: 10px;
+ }
+
+ .table{
+ height:490px;
+ margin-bottom: 0;
+ }
+
+ .data-row{
+ .table-delete-btn{
+ display: none !important;
+ }
+ &:hover{
+ .table-delete-btn{
+ display: inline-block !important;
+ }
+ }
+ }
+
+ .table-container-flex {
+ margin-top: 27px;
+
+ .text{
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: inline-block;
+ white-space: nowrap;
+ }
+
+ .flex-item:nth-child(1) {
+ flex-grow: 15;
+ a{
+ .hand
+ }
+ }
+
+ .flex-item:nth-child(2) {
+ flex-grow: 6;
+ }
+
+ .flex-item:nth-child(3) {
+ flex-grow: 6;
+ }
+
+ .flex-item:nth-child(4) {
+ flex-grow: 20;
+
+ }
+ .flex-item:nth-child(5) {
+ flex-grow: 3;
+ padding-top: 10px;
+ }
+
+
+ }
+
+}
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities-view-model.ts b/catalog-ui/app/scripts/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities-view-model.ts
new file mode 100644
index 0000000000..97a117e8b7
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities-view-model.ts
@@ -0,0 +1,165 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/**
+ * Created by rcohen on 9/22/2016.
+ */
+/// <reference path="../../../../references"/>
+module Sdc.ViewModels {
+ 'use strict';
+ import tree = d3.layout.tree;
+
+ export class SortTableDefined {
+ reverse:boolean;
+ sortByField:string;
+ }
+
+ interface IReqAndCapabilitiesViewModelScope extends IWorkspaceViewModelScope {
+ requirementsTableHeadersList: Array<any>;
+ capabilitiesTableHeadersList: Array<any>;
+ capabilityPropertiesTableHeadersList: Array<any>;
+ requirementsSortTableDefined: SortTableDefined;
+ capabilitiesSortTableDefined: SortTableDefined;
+ propertiesSortTableDefined: SortTableDefined;
+ requirements:Array<Models.Requirement>;
+ capabilities:Array<Models.Capability>;
+ mode:string;
+ filteredProperties:Array<Array<Models.PropertyModel>>;
+ searchText:string;
+
+ sort(sortBy:string, sortByTableDefined:SortTableDefined):void;
+ updateProperty(property:Models.PropertyModel, indexInFilteredProperties:number):void;
+ allCapabilitiesSelected(selected:boolean):void;
+ }
+
+ export class ReqAndCapabilitiesViewModel {
+
+ static '$inject' = [
+ '$scope',
+ '$filter',
+ '$modal',
+ '$templateCache',
+ 'ModalsHandler'
+ ];
+
+
+ constructor(private $scope:IReqAndCapabilitiesViewModelScope,
+ private $filter:ng.IFilterService,
+ private $modal:ng.ui.bootstrap.IModalService,
+ private $templateCache:ng.ITemplateCacheService) {
+ this.initScope();
+ this.$scope.updateSelectedMenuItem();
+ }
+
+
+ private openEditPropertyModal = (property:Models.PropertyModel, indexInFilteredProperties:number):void => {
+ let viewModelsHtmlBasePath:string = '/app/scripts/view-models/';
+ //...because there is not be api
+ _.forEach(this.$scope.filteredProperties[indexInFilteredProperties],(prop:Models.PropertyModel)=>{
+ prop.readonly = true;
+ });
+ let modalOptions:ng.ui.bootstrap.IModalSettings = {
+ template: this.$templateCache.get(viewModelsHtmlBasePath + 'forms/property-form/property-form-view.html'),
+ controller: 'Sdc.ViewModels.PropertyFormViewModel',
+ size: 'sdc-l',
+ backdrop: 'static',
+ keyboard: false,
+ resolve: {
+ property: ():Models.PropertyModel => {
+ return property;
+ },
+ component: ():Models.Components.Component => {
+ return <Models.Components.Component> this.$scope.component;
+ },
+ filteredProperties: ():Array<Models.PropertyModel> => {
+ return this.$scope.filteredProperties[indexInFilteredProperties];
+ }
+ }
+ };
+ this.$modal.open(modalOptions);
+ };
+
+ private initScope = ():void => {
+
+ this.$scope.requirementsSortTableDefined = {
+ reverse: false,
+ sortByField: 'name'
+ };
+ this.$scope.capabilitiesSortTableDefined = {
+ reverse: false,
+ sortByField: 'name'
+ };
+ this.$scope.propertiesSortTableDefined = {
+ reverse: false,
+ sortByField: 'name'
+ };
+
+ this.$scope.setValidState(true);
+ this.$scope.requirementsTableHeadersList = [
+ {title: 'Name', property: 'name'},
+ {title: 'Capability', property: 'capability'},
+ {title: 'Node', property: 'node'},
+ {title: 'Relationship', property: 'relationship'},
+ {title: 'Connected To', property: ''},
+ {title: 'Occurrences', property: ''}
+ ];
+ this.$scope.capabilitiesTableHeadersList = [
+ {title: 'Name', property: 'name'},
+ {title: 'Type', property: 'type'},
+ {title: 'Description', property: ''},
+ {title: 'Valid Source', property: ''},
+ {title: 'Occurrences', property: ''}
+ ];
+ this.$scope.capabilityPropertiesTableHeadersList = [
+ {title: 'Name', property: 'name'},
+ {title: 'Type', property: 'type'},
+ {title: 'Schema', property: 'schema.property.type'},
+ {title: 'Description', property: 'description'},
+ ];
+ this.$scope.filteredProperties=[];
+
+ this.$scope.mode='requirements';
+ this.$scope.requirements=[];
+ _.forEach(this.$scope.component.requirements,(req:Array<Models.Requirement>,capName)=>{
+ this.$scope.requirements=this.$scope.requirements.concat(req);
+ });
+
+ this.$scope.capabilities=[];
+ _.forEach(this.$scope.component.capabilities,(cap:Array<Models.Capability>,capName)=>{
+ this.$scope.capabilities=this.$scope.capabilities.concat(cap);
+ });
+
+ this.$scope.sort = (sortBy:string, sortByTableDefined:SortTableDefined):void => {
+ sortByTableDefined.reverse = (sortByTableDefined.sortByField === sortBy) ? !sortByTableDefined.reverse : false;
+ sortByTableDefined.sortByField = sortBy;
+ };
+
+ this.$scope.updateProperty = (property:Models.PropertyModel, indexInFilteredProperties:number):void => {
+ this.openEditPropertyModal(property, indexInFilteredProperties);
+ };
+
+ this.$scope.allCapabilitiesSelected = (selected:boolean):void => {
+ _.forEach(this.$scope.capabilities,(cap:Models.Capability)=>{
+ cap.selected = selected;
+ });
+ };
+ }
+ }
+}
+
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities-view.html b/catalog-ui/app/scripts/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities-view.html
new file mode 100644
index 0000000000..047768689a
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities-view.html
@@ -0,0 +1,144 @@
+<div class="workspace-req-and-cap">
+ <div class="tabs">
+ <button data-tests-id="req-tab" data-ng-click="mode='requirements';filterTerms='';" class="tlv-btn" data-ng-class="{'selected':mode=='requirements'}">Requirements({{requirements.length||'0'}})</button>
+ <button data-tests-id="cap-tab" data-ng-click="mode='capabilities';filterTerms='';" class="tlv-btn" data-ng-class="{'selected':mode=='capabilities'}">Capabilities({{capabilities.length||'0'}})</button>
+ </div>
+ <div class="expand-collapse-buttons" data-ng-if="mode=='capabilities'">
+ <span class="sprite-new expand-all" data-ng-click="allCapabilitiesSelected(true)"></span>
+ <span class="sprite-new collapse-all" data-ng-click="allCapabilitiesSelected(false)"></span>
+ </div>
+ <div class="search">
+ <input id="search-box" data-tests-id="search-box" type="search" placeholder="Search" data-ng-model-options="{debounce: 200}" data-ng-model="filterTerms"/>
+ <div class="search-icon-container">
+ <span class="search-icon sprite-new search-white-icon"></span>
+ </div>
+ </div>
+ <div class="table-container-flex requirements-table" data-ng-if="mode=='requirements'">
+ <div class="table" data-ng-class="{'view-mode': isViewMode()}">
+ <div class="head flex-container">
+ <div class="table-header head-row hand flex-item" data-ng-repeat="header in requirementsTableHeadersList track by $index" data-ng-click="sort(header.property, requirementsSortTableDefined)">{{header.title}}
+ <span data-ng-if="requirementsSortTableDefined.sortByField === header.property" class="table-header-sort-arrow" data-ng-class="{'down': requirementsSortTableDefined.reverse, 'up':!requirementsSortTableDefined.reverse}"> </span>
+ </div>
+ </div>
+
+ <div class="body">
+ <perfect-scrollbar scroll-y-margin-offset="0" include-padding="true" class="scrollbar-container">
+ <div data-ng-if="requirements.length === 0" class="no-row-text">
+ There are no requirements to display
+
+ </div>
+ <div data-ng-repeat="req in requirements | orderBy:requirementsSortTableDefined.sortByField:requirementsSortTableDefined.reverse | filter: {filterTerm:filterTerms} track by $index"
+ class="flex-container data-row" data-tests-id="reqRow">
+
+ <div class="table-col-general flex-item text">
+ <span data-tests-id="{{req.name}}" tooltips tooltip-content="{{req.name}}">{{req.name}}</span>
+ </div>
+ <div class="table-col-general flex-item text">
+ <span data-tests-id="{{req.capability}}" tooltips tooltip-content="{{req.capability}}">{{req.capability.substring("tosca.capabilities.".length)}}</span>
+ </div>
+ <div class="table-col-general flex-item text">
+ <span data-tests-id="{{req.node}}" tooltips tooltip-content="{{req.node}}">{{req.node.substring("tosca.nodes.".length)}}</span>
+ </div>
+ <div class="table-col-general flex-item text">
+ <span data-tests-id="{{req.relationship}}" tooltips tooltip-content="{{req.relationship}}">{{req.relationship.substring("tosca.relationships.".length)}}</span>
+ </div>
+ <div class="table-col-general flex-item text" data-tests-id="{{}}" data-ng-bind=""></div>
+ <div class="table-col-general flex-item text">
+ <span data-tests-id="{{req.minOccurrences}},{{req.maxOccurrences}}" tooltips tooltip-content="{{req.minOccurrences}},{{req.maxOccurrences}}">{{req.minOccurrences}},{{req.maxOccurrences}}</span>
+ </div>
+ </div>
+ </perfect-scrollbar>
+ </div>
+
+ </div>
+ </div>
+ <div class="table-container-flex capabilities-table" data-ng-if="mode=='capabilities'">
+ <div class="table" data-ng-class="{'view-mode': isViewMode()}">
+ <div class="head flex-container">
+ <div class="table-header head-row hand flex-item" data-ng-repeat="header in capabilitiesTableHeadersList track by $index" data-ng-click="sort(header.property, capabilitiesSortTableDefined)">{{header.title}}
+ <span data-ng-if="capabilitiesSortTableDefined.sortByField === header.property" class="table-header-sort-arrow" data-ng-class="{'down': capabilitiesSortTableDefined.reverse, 'up':!capabilitiesSortTableDefined.reverse}"> </span>
+ </div>
+ </div>
+
+ <div class="body">
+ <perfect-scrollbar scroll-y-margin-offset="0" include-padding="true" class="scrollbar-container">
+ <div data-ng-if="capabilities.length === 0" class="no-row-text">
+ There are no capabilities to display
+
+ </div>
+ <div data-ng-repeat-start="capability in capabilities | orderBy:capabilitiesSortTableDefined.sortByField:capabilitiesSortTableDefined.reverse | filter: {filterTerm:filterTerms} track by $index"
+ class="flex-container data-row" data-ng-class="{'selected': capability.selected}"
+ data-ng-click="capability.selected = !capability.selected" data-tests-id="capabilities-table-row">
+
+ <div class="table-col-general flex-item text">
+ <span class="sprite-new arrow-up-small" data-ng-class="{'opened': capability.selected}"></span>
+ <span data-tests-id="{{capability.name}}" tooltips tooltip-content="{{capability.name}}">{{capability.name}}</span>
+ </div>
+ <div class="table-col-general flex-item text">
+ <span data-tests-id="{{capability.type}}" tooltips tooltip-content="{{capability.type}}">{{capability.type.substring("tosca.capabilities.".length)}}</span>
+ </div>
+
+ <div class="table-col-general flex-item text">
+ <span data-tests-id="{{capability.description}}" tooltips tooltip-content="{{capability.description}}">{{capability.description}}</span>
+ </div>
+
+ <div class="table-col-general flex-item text">
+ <span data-tests-id="{{capability.validSourceTypes.join(',')}}" tooltips tooltip-content="{{capability.validSourceTypes.join(',')}}">{{capability.validSourceTypes.join(',')}}</span>
+ </div>
+
+ <div class="table-col-general flex-item text">
+ <span data-tests-id="{{capability.minOccurrences}},{{capability.maxOccurrences}}" tooltips tooltip-content="{{capability.minOccurrences}},{{capability.maxOccurrences}}">{{capability.minOccurrences}},{{capability.maxOccurrences}}</span>
+ </div>
+ </div>
+ <div data-ng-repeat-end="" data-ng-if="capability.selected" class="item-opened">
+ <p class="properties-title">Properties</p>
+ <div class="table-container-flex properties-table">
+ <div class="table" data-ng-class="{'view-mode': isViewMode()}">
+ <div class="head flex-container">
+ <div class="table-header head-row hand flex-item" data-ng-repeat="header in capabilityPropertiesTableHeadersList track by $index" data-ng-click="sort(header.property, propertiesSortTableDefined)">{{header.title}}
+ <span data-ng-if="propertiesSortTableDefined.sortByField === header.property" class="table-header-sort-arrow" data-ng-class="{'down': propertiesSortTableDefined.reverse, 'up':!propertiesSortTableDefined.reverse}"> </span>
+ </div>
+ </div>
+
+ <div class="body">
+ <div data-ng-if="capability.properties.length === 0" class="no-row-text">
+ There are no properties to display
+ </div>
+ <div data-ng-repeat="property in filteredProperties[$parent.$index]=(capability.properties | orderBy:propertiesSortTableDefined.sortByField:propertiesSortTableDefined.reverse) track by $index"
+ data-tests-id="propertyRow"
+ class="flex-container data-row">
+
+ <div class="table-col-general flex-item text">
+ <a data-tests-id="{{property.name}}"
+ tooltips tooltip-content="{{property.name}}"
+ data-ng-click="updateProperty(property, $parent.$index); $event.stopPropagation();"
+ data-ng-class="{'disabled': isViewMode()}">{{property.name}}</a>
+
+ </div>
+
+ <div class="table-col-general flex-item text"
+ data-tests-id="{{property.type}}"
+ tooltips tooltip-content="{{property.type}}"
+ data-ng-bind="property.type">
+ </div>
+ <div class="table-col-general flex-item text"
+ data-tests-id="{{property.schema.property.type}}"
+ tooltips tooltip-content="{{property.schema.property.type}}"
+ data-ng-bind="property.schema.property.type">
+ </div>
+ <div class="table-col-general flex-item text">
+ <span tooltips tooltip-content="{{property.description}}" data-tests-id="{{property.description}}" data-ng-bind="property.description"></span>
+ </div>
+ </div>
+ </div>
+
+ </div>
+ </div>
+ </div>
+ </perfect-scrollbar>
+ </div>
+
+ </div>
+ </div>
+</div>
+
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities.less b/catalog-ui/app/scripts/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities.less
new file mode 100644
index 0000000000..9b52fad411
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities.less
@@ -0,0 +1,196 @@
+.workspace-req-and-cap {
+
+ width: 93%;
+ display: inline-block;
+
+ .tabs{
+ float: left;
+ position: relative;
+ top: 6px;
+ button{
+ float: left;
+ width: 233px;
+ height: 38px;
+ background-color: @tlv_color_t;
+ border: 1px solid @main_color_o;
+ color: black;
+ &:nth-child(1){
+ border-radius: 10px 0 0 0;
+ }
+ &:nth-child(2){
+ border-radius: 0 10px 0 0;
+ }
+ &.selected{
+ background-color: @main_color_a;
+ border: 1px solid @main_color_a;
+ color: white;
+ }
+ }
+ }
+ .search{
+ margin-bottom: 12px;
+ float: right;
+ ::-webkit-input-placeholder {
+ font-style: italic;
+ }
+ :-moz-placeholder {
+ font-style: italic;
+ }
+ ::-moz-placeholder {
+ font-style: italic;
+ }
+ :-ms-input-placeholder {
+ font-style: italic;
+ }
+ #search-box{
+ -webkit-border-radius: 2px 0 0 2px;
+ -moz-border-radius: 2px 0 0 2px;
+ border-radius: 2px 0 0 2px;
+ width: 213px;
+ height: 32px;
+ line-height: 32px;
+ border: 1px solid @main_color_o;
+ text-indent: 10px;
+ float: left;
+ }
+ .search-icon-container{
+ background-color: @main_color_a;
+ height: 32px;
+ width: 32px;
+ border-radius: 0 2px 2px 0;
+ float: left;
+ .search-icon{
+ position: relative;
+ top: 9px;
+ }
+ }
+ }
+ .expand-collapse-buttons{
+ float: right;
+ width: 44px;
+ margin-left: 11px;
+ margin-top: 10px;
+ span{
+ vertical-align: bottom;
+ .hand;
+ }
+ }
+
+
+
+ .table{
+ height:490px;
+ margin-bottom: 0;
+ }
+
+ .arrow-up-small{
+ &.opened{
+ .arrow-up-small-hover;
+ }
+ }
+
+ .item-opened{
+ background-color: @tlv_color_t;
+ }
+
+ .properties-title{
+ margin:0;
+ font-weight: bold;
+ }
+
+ .table-container-flex {
+ margin-top: 10px;
+
+ .text{
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: inline-block;
+ white-space: nowrap;
+ }
+
+ &.requirements-table{
+ border-top: 4px solid @main_color_a;
+ .flex-item:nth-child(1) {
+ flex-grow: 20;
+ }
+
+ .flex-item:nth-child(2) {
+ flex-grow: 20;
+ }
+
+ .flex-item:nth-child(3) {
+ flex-grow: 20;
+ }
+
+ .flex-item:nth-child(4) {
+ flex-grow: 20;
+ }
+
+ .flex-item:nth-child(5) {
+ flex-grow: 20;
+ }
+
+ .flex-item:nth-child(6) {
+ flex-grow: 20;
+ }
+ }
+
+ &.capabilities-table{
+ border-top: 4px solid @main_color_a;
+ .selected{
+ .flex-item:nth-child(1) {
+ border-left: 4px solid @main_color_a;
+ padding-right: 11px;
+ }
+ }
+ .flex-item:nth-child(1) {
+ flex-grow: 10;
+ }
+
+ .flex-item:nth-child(2) {
+ flex-grow: 10;
+ }
+
+ .flex-item:nth-child(3) {
+ flex-grow: 10;
+ }
+
+ .flex-item:nth-child(4) {
+ flex-grow: 10;
+ }
+
+ .flex-item:nth-child(5) {
+ flex-grow: 10;
+ }
+
+ }
+
+ &.properties-table{
+ .table{
+ height: auto;
+ }
+
+ .flex-item:nth-child(1) {
+ flex-grow: 15;
+ a{
+ .hand
+ }
+ }
+
+ .flex-item:nth-child(2) {
+ flex-grow: 6;
+ }
+
+ .flex-item:nth-child(3) {
+ flex-grow: 6;
+ }
+
+ .flex-item:nth-child(4) {
+ flex-grow: 20;
+
+ }
+ }
+
+ }
+
+}
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/tosca-artifacts/tosca-artifacts-view-model.ts b/catalog-ui/app/scripts/view-models/workspace/tabs/tosca-artifacts/tosca-artifacts-view-model.ts
new file mode 100644
index 0000000000..1e6bc04924
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/tosca-artifacts/tosca-artifacts-view-model.ts
@@ -0,0 +1,87 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../../../references"/>
+module Sdc.ViewModels {
+ 'use strict';
+ import ArtifactModel = Sdc.Models.ArtifactModel;
+
+ export interface IToscaArtifactsScope extends IWorkspaceViewModelScope {
+ artifacts: Array<Models.ArtifactModel>;
+ tableHeadersList: Array<any>;
+ artifactType: string;
+ downloadFile:Models.IFileDownload;
+ isLoading:boolean;
+ sortBy:string;
+ reverse:boolean;
+
+ getTitle(): string;
+ download(artifact:Models.ArtifactModel): void;
+ sort(sortBy:string): void;
+ showNoArtifactMessage():boolean;
+ }
+
+ export class ToscaArtifactsViewModel {
+
+ static '$inject' = [
+ '$scope',
+ '$filter'
+ ];
+
+ constructor(private $scope:IToscaArtifactsScope,
+ private $filter:ng.IFilterService) {
+ this.initScope();
+ this.$scope.updateSelectedMenuItem();
+ }
+
+ private initScope = ():void => {
+ let self = this;
+ this.$scope.isLoading = false;
+ this.$scope.sortBy = 'artifactDisplayName';
+ this.$scope.reverse = false;
+ this.$scope.setValidState(true);
+ this.$scope.artifactType = 'normal';
+ this.$scope.getTitle = ():string => {
+ return this.$filter("resourceName")(this.$scope.component.name) + ' Artifacts';
+
+ };
+
+ this.$scope.tableHeadersList = [
+ {title: 'Name', property: 'artifactDisplayName'},
+ {title: 'Type', property: 'artifactType'}
+ ];
+
+ this.$scope.artifacts = <ArtifactModel[]>_.values(this.$scope.component.toscaArtifacts);
+ this.$scope.sort = (sortBy:string):void => {
+ this.$scope.reverse = (this.$scope.sortBy === sortBy) ? !this.$scope.reverse : false;
+ this.$scope.sortBy = sortBy;
+ };
+
+
+
+ this.$scope.showNoArtifactMessage = ():boolean => {
+ if (this.$scope.artifacts.length === 0) {
+ return true;
+ }
+ return false;
+ };
+
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/tosca-artifacts/tosca-artifacts-view.html b/catalog-ui/app/scripts/view-models/workspace/tabs/tosca-artifacts/tosca-artifacts-view.html
new file mode 100644
index 0000000000..947b37db93
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/tosca-artifacts/tosca-artifacts-view.html
@@ -0,0 +1,45 @@
+<div class="workspace-tosca-artifact">
+ <div class="table-container-flex">
+ <div class="table" data-ng-class="{'view-mode': isViewMode()}">
+ <div class="head flex-container">
+ <div class="table-header head-row hand flex-item" ng-repeat="header in tableHeadersList track by $index" data-ng-click="sort(header.property)">{{header.title}}
+ <span data-ng-show="sortBy === header.property" class="table-header-sort-arrow" data-ng-class="{'down': reverse, 'up':!reverse}"> </span>
+ </div>
+ <div class="table-no-text-header head-row flex-item"></div>
+ </div>
+ <div class="body">
+ <perfect-scrollbar suppress-scroll-x="true" scroll-y-margin-offset="0" include-padding="true" class="scrollbar-container">
+ <div data-ng-if="showNoArtifactMessage()" class="no-row-text" data-ng-class="{'disabled': isDisableMode()}">
+ There are no TOSCA artifacts to display
+ </div>
+ <div data-ng-repeat-start="artifact in artifacts| orderBy:sortBy:reverse track by $index"
+ class="flex-container data-row"
+ data-ng-class="{'selected': artifact.selected}">
+
+ <div class="table-col-general flex-item" data-ng-click="artifact.selected = !artifact.selected">
+ <span class="sprite table-arrow" data-ng-class="{'opened': artifact.selected}" data-tests-id="{{artifact.artifactDisplayName}}"></span>
+ {{artifact.artifactDisplayName}}
+ </div>
+
+ <div class="table-col-general flex-item" data-ng-click="artifact.selected = !artifact.selected" data-tests-id="{{artifact.artifactType}}">
+ {{artifact.artifactType}}
+ </div>
+
+ <div class="table-btn-col flex-item download-icon-container">
+ <button class="table-download-btn tosca" download-artifact data-tests-id="download_{{artifact.artifactDisplayName}}"
+ data-ng-if="artifact.artifactName" component="component" artifact="artifact" show-loader="true"></button>
+ </div>
+ </div>
+ <div data-ng-repeat-end="" data-ng-if="artifact.selected" class="item-opened">
+ <div><span class="details-title">Label:</span> {{artifact.artifactLabel}}</div>
+ <div><span class="details-title">UUID:</span> {{artifact.uniqueId}}</div>
+ <div><span class="details-title">Description:</span> {{artifact.description}}</div>
+
+
+ </div>
+
+ </perfect-scrollbar>
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/tosca-artifacts/tosca-artifacts.less b/catalog-ui/app/scripts/view-models/workspace/tabs/tosca-artifacts/tosca-artifacts.less
new file mode 100644
index 0000000000..f792bb8c53
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/tosca-artifacts/tosca-artifacts.less
@@ -0,0 +1,74 @@
+.workspace-tosca-artifact {
+ width: 93%;
+ display: inline-block;
+ .w-sdc-classic-btn {
+ float: right;
+ margin-bottom: 10px;
+ }
+
+ .details-title{
+ font-weight: bold;
+ margin-right: 5px;
+ }
+
+ .table{
+ height: 490px;
+ margin-bottom: 0;
+ }
+
+
+ .table-container-flex {
+ margin-top: 27px;
+
+ .item-opened{
+ word-wrap: break-word;
+ }
+
+
+ .flex-item:nth-child(1) {
+ flex-grow: 15;
+ .hand;
+ span.table-arrow {
+ margin-right: 7px;
+ }
+ }
+
+ .flex-item:nth-child(2) {
+ flex-grow: 6;
+ }
+
+ .flex-item:nth-child(3) {
+ flex-grow: 1;
+ }
+
+
+
+
+ .table-download-btn{
+ &.tosca{
+ margin-left: 0;
+ margin-top: 8px;
+ }
+ }
+
+ .download-icon-container{
+ position: relative;
+
+ .loader{
+ left: 60%;
+ top: 45px;
+ border: none;
+ background-color: transparent;
+ height: 0px;
+ width: 63px;
+ outline: none;
+
+ }
+ }
+
+
+ }
+
+}
+
+
diff --git a/catalog-ui/app/scripts/view-models/workspace/workspace-view-model.ts b/catalog-ui/app/scripts/view-models/workspace/workspace-view-model.ts
new file mode 100644
index 0000000000..a8523f24f5
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/workspace-view-model.ts
@@ -0,0 +1,703 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/**
+ * Created by obarda on 3/30/2016.
+ */
+/// <reference path="../../references"/>
+module Sdc.ViewModels {
+
+ 'use strict';
+ import Resource = Sdc.Models.Components.Resource;
+ import ResourceType = Sdc.Utils.Constants.ResourceType;
+
+ export interface IWorkspaceViewModelScope extends ng.IScope {
+
+ isLoading: boolean;
+ isCreateProgress: boolean;
+ component: Models.Components.Component;
+ originComponent: Models.Components.Component;
+ componentType: string;
+ importFile: any;
+ leftBarTabs: Utils.MenuItemGroup;
+ isNew: boolean;
+ isFromImport: boolean;
+ isValidForm: boolean;
+ mode: Utils.Constants.WorkspaceMode;
+ breadcrumbsModel: Array<Utils.MenuItemGroup>;
+ sdcMenu: Models.IAppMenu;
+ changeLifecycleStateButtons: any;
+ version: string;
+ versionsList: Array<any>;
+ changeVersion: any;
+ isComposition: boolean;
+ isDeployment: boolean;
+ $state: ng.ui.IStateService;
+ user: Models.IUserProperties;
+ thirdParty: boolean;
+ disabledButtons: boolean;
+ menuComponentTitle: string;
+ progressService: Sdc.Services.ProgressService;
+ progressMessage: string;
+ // leftPanelComponents:Array<Models.Components.Component>; //this is in order to load the left panel once, and not wait long time when moving to composition
+
+ showChangeStateButton(): boolean;
+ getComponent(): Sdc.Models.Components.Component;
+ setComponent(component: Sdc.Models.Components.Component): void;
+ onMenuItemPressed(state: string): ng.IPromise<boolean>;
+ save(): ng.IPromise<boolean>;
+ setValidState(isValid: boolean): void;
+ revert(): void;
+ changeLifecycleState(state: string): void;
+ enabledTabs(): void
+ isDesigner(): boolean;
+ isViewMode(): boolean;
+ isEditMode(): boolean;
+ isCreateMode(): boolean;
+ isDisableMode(): boolean;
+ showFullIcons(): boolean;
+ goToBreadcrumbHome(): void;
+ onVersionChanged(selectedId: string): void;
+ getLatestVersion(): void;
+ getStatus(): string;
+ showLifecycleIcon(): boolean;
+ updateSelectedMenuItem(): void;
+ uploadFileChangedInGeneralTab(): void;
+ updateMenuComponentName(ComponentName: string): void;
+ }
+
+ export class WorkspaceViewModel {
+
+ static '$inject' = [
+ '$scope',
+ 'injectComponent',
+ 'ComponentFactory',
+ '$state',
+ 'sdcMenu',
+ '$q',
+ 'MenuHandler',
+ 'Sdc.Services.CacheService',
+ 'ChangeLifecycleStateHandler',
+ 'ModalsHandler',
+ 'LeftPaletteLoaderService',
+ '$filter',
+ 'EventListenerService',
+ 'Sdc.Services.EntityService',
+ 'Notification',
+ '$stateParams',
+ 'Sdc.Services.ProgressService'
+ ];
+
+ constructor(private $scope: IWorkspaceViewModelScope,
+ private injectComponent: Models.Components.Component,
+ private ComponentFactory: Utils.ComponentFactory,
+ private $state: ng.ui.IStateService,
+ private sdcMenu: Models.IAppMenu,
+ private $q: ng.IQService,
+ private MenuHandler: Utils.MenuHandler,
+ private cacheService: Services.CacheService,
+ private ChangeLifecycleStateHandler: Sdc.Utils.ChangeLifecycleStateHandler,
+ private ModalsHandler: Sdc.Utils.ModalsHandler,
+ private LeftPaletteLoaderService: Services.Components.LeftPaletteLoaderService,
+ private $filter: ng.IFilterService,
+ private EventListenerService: Services.EventListenerService,
+ private EntityService: Sdc.Services.EntityService,
+ private Notification: any,
+ private $stateParams: any,
+ private progressService: Sdc.Services.ProgressService) {
+
+ this.initScope();
+ this.initAfterScope();
+ }
+
+ private role: string;
+ private components: Array<Models.Components.Component>;
+
+ private initViewMode = (): Utils.Constants.WorkspaceMode => {
+ let mode = Utils.Constants.WorkspaceMode.VIEW;
+
+ if (!this.$state.params['id']) { //&& !this.$state.params['vspComponent']
+ mode = Utils.Constants.WorkspaceMode.CREATE;
+ } else {
+ if (this.$scope.component.lifecycleState === Utils.Constants.ComponentState.NOT_CERTIFIED_CHECKOUT &&
+ this.$scope.component.lastUpdaterUserId === this.cacheService.get("user").userId) {
+ if (this.$scope.component.isProduct() && this.role == Utils.Constants.Role.PRODUCT_MANAGER) {
+ mode = Utils.Constants.WorkspaceMode.EDIT;
+ }
+ if ((this.$scope.component.isService() || this.$scope.component.isResource()) && this.role == Utils.Constants.Role.DESIGNER) {
+ mode = Utils.Constants.WorkspaceMode.EDIT;
+ }
+ }
+ }
+ return mode;
+ };
+
+ private initChangeLifecycleStateButtons = (): void => {
+ let state = this.$scope.component.isService() && (Utils.Constants.Role.OPS == this.role || Utils.Constants.Role.GOVERNOR == this.role) ? this.$scope.component.distributionStatus : this.$scope.component.lifecycleState;
+ this.$scope.changeLifecycleStateButtons = this.sdcMenu.roles[this.role].changeLifecycleStateButtons[state];
+ };
+
+ private isNeedSave = (): boolean => {
+ if (this.$scope.isEditMode() && //this is a workaround for onboarding - we need to get the artifact in order to avoid saving the vf when moving from their tabs
+ (this.$state.current.name === Utils.Constants.States.WORKSPACE_MANAGEMENT_WORKFLOW || this.$state.current.name === Utils.Constants.States.WORKSPACE_NETWORK_CALL_FLOW)) {
+ return true;
+ }
+ return this.$scope.isEditMode() &&
+ this.$state.current.data && this.$state.current.data.unsavedChanges;
+ };
+
+ private initScope = (): void => {
+
+ this.$scope.component = this.injectComponent;
+ this.$scope.menuComponentTitle = this.$scope.component.name;
+ this.$scope.disabledButtons = false;
+ this.$scope.originComponent = this.ComponentFactory.createComponent(this.$scope.component);
+ this.$scope.componentType = this.$scope.component.componentType;
+ this.$scope.version = this.cacheService.get('version');
+ this.$scope.user = this.cacheService.get("user");
+ this.role = this.$scope.user.role;
+ this.$scope.mode = this.initViewMode();
+ this.$scope.isValidForm = true;
+ this.initChangeLifecycleStateButtons();
+ this.initVersionObject();
+ this.$scope.$state = this.$state;
+ this.$scope.isLoading = false;
+ this.$scope.isComposition = (this.$state.current.name.indexOf(Utils.Constants.States.WORKSPACE_COMPOSITION) > -1);
+ this.$scope.isDeployment = (this.$state.current.name.indexOf(Utils.Constants.States.WORKSPACE_DEPLOYMENT) > -1);
+ this.$scope.progressService = this.progressService;
+
+ this.$scope.getComponent = (): Sdc.Models.Components.Component => {
+ return this.$scope.component;
+ };
+
+ this.$scope.updateMenuComponentName = (ComponentName: string): void => {
+ this.$scope.menuComponentTitle = ComponentName;
+ };
+
+ this.$scope.sdcMenu = this.sdcMenu;
+ // Will be called from each step after save to update the resource.
+ this.$scope.setComponent = (component: Sdc.Models.Components.Component): void => {
+ this.$scope.component = component;
+ };
+
+ this.$scope.uploadFileChangedInGeneralTab = (): void => {
+ // In case user select browse file, and in update mode, need to disable submit for testing and checkin buttons.
+ if (this.$scope.isEditMode() && this.$scope.component.isResource() && (<Resource>this.$scope.component).resourceType == ResourceType.VF) {
+ this.$scope.disabledButtons = true;
+ }
+ };
+
+ this.$scope.onMenuItemPressed = (state: string): ng.IPromise<boolean> => {
+ let deferred = this.$q.defer();
+ if (this.isNeedSave()) {
+ if (this.$scope.isValidForm) {
+ let onSuccess = (): void => {
+ this.$state.go(state, {
+ id: this.$scope.component.uniqueId,
+ type: this.$scope.component.componentType.toLowerCase(),
+ components: this.components
+ });
+ deferred.resolve(true);
+ };
+ this.$scope.save().then(onSuccess);
+ } else {
+ console.log('form is not valid');
+ deferred.reject(false);
+ }
+ } else {
+ this.$state.go(state, {
+ id: this.$scope.component.uniqueId,
+ type: this.$scope.component.componentType.toLowerCase(),
+ components: this.components
+ });
+ deferred.resolve(true);
+ }
+ return deferred.promise;
+ };
+
+ this.$scope.setValidState = (isValid: boolean): void => {
+ this.$scope.isValidForm = isValid;
+ };
+
+ this.$scope.onVersionChanged = (selectedId: string): void => {
+ this.$scope.isLoading = true;
+ if (this.$state.current.data && this.$state.current.data.unsavedChanges) {
+ this.$scope.changeVersion.selectedVersion = _.find(this.$scope.versionsList, {versionId: this.$scope.component.uniqueId});
+ }
+ this.$state.go(this.$state.current.name, {
+ id: selectedId,
+ type: this.$scope.componentType.toLowerCase(),
+ mode: Utils.Constants.WorkspaceMode.VIEW,
+ components: this.$state.params['components']
+ });
+
+ };
+
+ this.$scope.getLatestVersion = (): void => {
+ this.$scope.onVersionChanged(_.first(this.$scope.versionsList).versionId);
+ };
+
+ this.$scope.save = (state?: string): ng.IPromise<boolean> => {
+ this.EventListenerService.notifyObservers(Utils.Constants.EVENTS.ON_WORKSPACE_SAVE_BUTTON_CLICK);
+
+ this.progressService.initCreateComponentProgress(this.$scope.component.uniqueId);
+
+ let deferred = this.$q.defer();
+ let modalInstance: ng.ui.bootstrap.IModalServiceInstance;
+
+ let onFailed = () => {
+ this.EventListenerService.notifyObservers(Utils.Constants.EVENTS.ON_WORKSPACE_SAVE_BUTTON_ERROR);
+ this.progressService.deleteProgressValue(this.$scope.component.uniqueId);
+ modalInstance && modalInstance.close(); // Close the modal in case it is opened.
+ this.$scope.isCreateProgress = false;
+ this.$scope.isLoading = false; // stop the progress.
+
+ this.$scope.setValidState(true); // Set the form valid (if sent form is valid, the error from server).
+ if (!this.$scope.isCreateMode()) {
+ this.$scope.component = this.$scope.originComponent; // Set the component back to the original.
+ this.enableMenuItems(); // Enable the menu items (left tabs), so user can press on them.
+ this.$scope.disabledButtons = false; // Enable "submit for testing" & checking buttons.
+ }
+
+ deferred.reject(false);
+ };
+
+ let onSuccessCreate = (component: Models.Components.Component) => {
+
+ this.EventListenerService.notifyObservers(Utils.Constants.EVENTS.ON_WORKSPACE_SAVE_BUTTON_SUCCESS);
+ this.progressService.deleteProgressValue(this.$scope.component.uniqueId);
+ //update components for breadcrumbs
+ this.components.unshift(component);
+ this.$state.go(Utils.Constants.States.WORKSPACE_GENERAL, {
+ id: component.uniqueId,
+ type: component.componentType.toLowerCase(),
+ components: this.components
+ });
+
+ deferred.resolve(true);
+ };
+
+ let onSuccessUpdate = (component: Models.Components.Component) => {
+ this.$scope.isCreateProgress = false;
+ this.$scope.disabledButtons = false;
+ this.EventListenerService.notifyObservers(Utils.Constants.EVENTS.ON_WORKSPACE_SAVE_BUTTON_SUCCESS);
+ this.progressService.deleteProgressValue(this.$scope.component.uniqueId);
+
+ // Stop the circle loader.
+ this.$scope.isLoading = false;
+
+ component.tags = _.reject(component.tags, (item)=> {
+ return item === component.name
+ });
+
+ // Update the components
+ this.$scope.component = component;
+ this.$scope.originComponent = this.ComponentFactory.createComponent(this.$scope.component);
+
+ //update components for breadcrumbs
+ this.components.unshift(component);
+
+ // Enable left tags
+ this.$scope.enabledTabs();
+
+
+ if (this.$state.current.data) {
+ this.$state.current.data.unsavedChanges = false;
+ }
+
+ deferred.resolve(true);
+ };
+
+ if (this.$scope.isCreateMode()) {
+ this.$scope.progressMessage = "Creating Asset...";
+ // CREATE MODE
+ this.$scope.isCreateProgress = true;
+
+ // Start creating the component
+ this.ComponentFactory.createComponentOnServer(this.$scope.component).then(onSuccessCreate, onFailed);
+
+ // In case we import CSAR. Notify user that import VF will take long time (the create is performed in the background).
+ if (this.$scope.component.isResource() && (<Resource>this.$scope.component).csarUUID) {
+ this.Notification.info({
+ message: this.$filter('translate')("IMPORT_VF_MESSAGE_CREATE_TAKES_LONG_TIME_DESCRIPTION"),
+ title: this.$filter('translate')("IMPORT_VF_MESSAGE_CREATE_TAKES_LONG_TIME_TITLE")
+ });
+ }
+ } else {
+ // UPDATE MODE
+ this.$scope.isCreateProgress = true;
+ this.$scope.progressMessage = "Updating Asset...";
+ this.disableMenuItems();
+
+
+ // Work around to change the csar version
+ if (this.cacheService.get(Utils.Constants.CHANGE_COMPONENT_CSAR_VERSION_FLAG)) {
+ (<Resource>this.$scope.component).csarVersion = this.cacheService.get(Utils.Constants.CHANGE_COMPONENT_CSAR_VERSION_FLAG);
+ this.cacheService.remove(Utils.Constants.CHANGE_COMPONENT_CSAR_VERSION_FLAG);
+ }
+
+ this.$scope.component.updateComponent().then(onSuccessUpdate, onFailed);
+ }
+ return deferred.promise;
+ };
+
+ this.$scope.revert = (): void => {
+ //in state of import file leave the file in place
+ if (this.$scope.component.isResource() && (<Resource>this.$scope.component).importedFile) {
+ let tempFile: Sdc.Directives.FileUploadModel = (<Resource>this.$scope.component).importedFile;
+ this.$scope.component = this.ComponentFactory.createComponent(this.$scope.originComponent);
+ (<Resource>this.$scope.component).importedFile = tempFile;
+ } else {
+ this.$scope.component = this.ComponentFactory.createComponent(this.$scope.originComponent);
+ }
+
+ };
+
+ this.$scope.changeLifecycleState = (state: string): void => {
+ if (this.isNeedSave() && state !== 'deleteVersion') {
+ this.$scope.save().then(() => {
+ changeLifecycleState(state);
+ })
+ } else {
+ changeLifecycleState(state);
+ }
+ };
+
+ let defaultActionAfterChangeLifecycleState = (): void => {
+ if (this.$state.current.data && this.$state.current.data.unsavedChanges) {
+ this.$state.current.data.unsavedChanges = false;
+ }
+ this.$state.go('dashboard');
+ };
+
+ let changeLifecycleState = (state: string) => {
+ if ('monitor' === state) {
+ this.$state.go('workspace.distribution');
+ return;
+ }
+
+ let data = this.$scope.changeLifecycleStateButtons[state];
+ let onSuccess = (component: Models.Components.Component): void => {
+ //Updating the component from server response
+
+ //the server returns only metaData (small component) except checkout (Full component) ,so we update only the statuses of distribution & lifecycle
+ this.$scope.component.lifecycleState = component.lifecycleState;
+ this.$scope.component.distributionStatus = component.distributionStatus;
+
+ switch (data.url) {
+ case 'lifecycleState/CHECKOUT':
+ // only checkOut get the full component from server
+ this.$scope.component = component;
+ // Work around to change the csar version
+ if (this.cacheService.get(Utils.Constants.CHANGE_COMPONENT_CSAR_VERSION_FLAG)) {
+ (<Resource>this.$scope.component).csarVersion = this.cacheService.get(Utils.Constants.CHANGE_COMPONENT_CSAR_VERSION_FLAG);
+ }
+
+ //when checking out a minor version uuid remains
+ let bcComponent: Sdc.Models.Components.Component = _.find(this.components, (item) => {
+ return item.uuid === component.uuid;
+ });
+ if (bcComponent) {
+ this.components[this.components.indexOf(bcComponent)] = component;
+ } else {
+ //when checking out a major(certified) version
+ this.components.unshift(component);
+ }
+
+ this.$state.go(this.$state.current.name, {
+ id: component.uniqueId,
+ type: component.componentType.toLowerCase(),
+ components: this.components
+ });
+ this.Notification.success({
+ message: this.$filter('translate')("CHECKOUT_SUCCESS_MESSAGE_TEXT"),
+ title: this.$filter('translate')("CHECKOUT_SUCCESS_MESSAGE_TITLE")
+ });
+ break;
+ case 'lifecycleState/CHECKIN':
+ defaultActionAfterChangeLifecycleState();
+ this.Notification.success({
+ message: this.$filter('translate')("CHECKIN_SUCCESS_MESSAGE_TEXT"),
+ title: this.$filter('translate')("CHECKIN_SUCCESS_MESSAGE_TITLE")
+ });
+ break;
+ case 'lifecycleState/UNDOCHECKOUT':
+ defaultActionAfterChangeLifecycleState();
+ this.Notification.success({
+ message: this.$filter('translate')("DELETE_SUCCESS_MESSAGE_TEXT"),
+ title: this.$filter('translate')("DELETE_SUCCESS_MESSAGE_TITLE")
+ });
+ break;
+ case 'lifecycleState/certificationRequest':
+ defaultActionAfterChangeLifecycleState();
+ this.Notification.success({
+ message: this.$filter('translate')("SUBMIT_FOR_TESTING_SUCCESS_MESSAGE_TEXT"),
+ title: this.$filter('translate')("SUBMIT_FOR_TESTING_SUCCESS_MESSAGE_TITLE")
+ });
+ break;
+ //Tester Role
+ case 'lifecycleState/failCertification':
+ defaultActionAfterChangeLifecycleState();
+ this.Notification.success({
+ message: this.$filter('translate')("REJECT_SUCCESS_MESSAGE_TEXT"),
+ title: this.$filter('translate')("REJECT_SUCCESS_MESSAGE_TITLE")
+ });
+ break;
+ case 'lifecycleState/certify':
+ defaultActionAfterChangeLifecycleState();
+ this.Notification.success({
+ message: this.$filter('translate')("ACCEPT_TESTING_SUCCESS_MESSAGE_TEXT"),
+ title: this.$filter('translate')("ACCEPT_TESTING_SUCCESS_MESSAGE_TITLE")
+ });
+ break;
+ //DE203504 Bug Fix Start
+ case 'lifecycleState/startCertification':
+ this.initChangeLifecycleStateButtons();
+ this.Notification.success({
+ message: this.$filter('translate')("START_TESTING_SUCCESS_MESSAGE_TEXT"),
+ title: this.$filter('translate')("START_TESTING_SUCCESS_MESSAGE_TITLE")
+ });
+ break;
+ case 'lifecycleState/cancelCertification':
+ this.initChangeLifecycleStateButtons();
+ this.Notification.success({
+ message: this.$filter('translate')("CANCEL_TESTING_SUCCESS_MESSAGE_TEXT"),
+ title: this.$filter('translate')("CANCEL_TESTING_SUCCESS_MESSAGE_TITLE")
+ });
+ break;
+ //Ops Role
+ case 'distribution/PROD/activate':
+ this.initChangeLifecycleStateButtons();
+ this.Notification.success({
+ message: this.$filter('translate')("DISTRIBUTE_SUCCESS_MESSAGE_TEXT"),
+ title: this.$filter('translate')("DISTRIBUTE_SUCCESS_MESSAGE_TITLE")
+ });
+ break;
+ //Governor Role
+ case 'distribution-state/reject':
+ this.initChangeLifecycleStateButtons();
+ this.Notification.success({
+ message: this.$filter('translate')("REJECT_SUCCESS_MESSAGE_TEXT"),
+ title: this.$filter('translate')("REJECT_SUCCESS_MESSAGE_TITLE")
+ });
+ break;
+ case 'distribution-state/approve':
+ this.initChangeLifecycleStateButtons();
+ this.$state.go('catalog');
+ this.Notification.success({
+ message: this.$filter('translate')("APPROVE_SUCCESS_MESSAGE_TEXT"),
+ title: this.$filter('translate')("APPROVE_SUCCESS_MESSAGE_TITLE")
+ });
+ break;
+ //DE203504 Bug Fix End
+
+ default :
+ defaultActionAfterChangeLifecycleState();
+
+ }
+ if (data.url != 'lifecycleState/CHECKOUT') {
+ this.$scope.isLoading = false;
+ }
+ };
+ //this.$scope.isLoading = true;
+ this.ChangeLifecycleStateHandler.changeLifecycleState(this.$scope.component, data, this.$scope, onSuccess);
+ };
+
+ this.$scope.enabledTabs = (): void => {
+ this.$scope.leftBarTabs.menuItems.forEach((item: Utils.MenuItem) => {
+ item.isDisabled = false;
+ });
+ };
+
+ this.$scope.isViewMode = (): boolean => {
+ return this.$scope.mode === Utils.Constants.WorkspaceMode.VIEW;
+ };
+
+ this.$scope.isDesigner = (): boolean => {
+ return this.role == Utils.Constants.Role.DESIGNER;
+ };
+
+ this.$scope.isDisableMode = (): boolean => {
+ return this.$scope.mode === Utils.Constants.WorkspaceMode.VIEW && this.$scope.component.lifecycleState === Utils.Constants.ComponentState.NOT_CERTIFIED_CHECKIN;
+ };
+
+ this.$scope.showFullIcons = (): boolean => {
+ //we show revert and save icons only in general\icon view
+ return this.$state.current.name === Utils.Constants.States.WORKSPACE_GENERAL ||
+ this.$state.current.name === Utils.Constants.States.WORKSPACE_ICONS;
+ };
+
+ this.$scope.isCreateMode = (): boolean => {
+ return this.$scope.mode === Utils.Constants.WorkspaceMode.CREATE;
+ };
+
+ this.$scope.isEditMode = (): boolean => {
+ return this.$scope.mode === Utils.Constants.WorkspaceMode.EDIT;
+ };
+
+ this.$scope.goToBreadcrumbHome = (): void => {
+ let bcHome: Sdc.Utils.MenuItemGroup = this.$scope.breadcrumbsModel[0];
+ this.$state.go(bcHome.menuItems[bcHome.selectedIndex].state);
+ };
+
+ this.$scope.showLifecycleIcon = (): boolean => {
+ return this.role == Utils.Constants.Role.DESIGNER ||
+ this.role == Utils.Constants.Role.PRODUCT_MANAGER;
+ };
+
+ this.$scope.getStatus = (): string => {
+ if (this.$scope.isCreateMode()) {
+ return 'IN DESIGN';
+ }
+
+ return this.$scope.component.getStatus(this.sdcMenu);
+ };
+
+ this.initMenuItems();
+
+ this.$scope.showChangeStateButton = (): boolean => {
+ let result: boolean = true;
+ if (!this.$scope.component.isLatestVersion() && Utils.Constants.Role.OPS != this.role && Utils.Constants.Role.GOVERNOR != this.role) {
+ result = false;
+ }
+ if (this.role === Utils.Constants.Role.PRODUCT_MANAGER && !this.$scope.component.isProduct()) {
+ result = false;
+ }
+ if ((this.role === Utils.Constants.Role.DESIGNER || this.role === Utils.Constants.Role.TESTER)
+ && this.$scope.component.isProduct()) {
+ result = false;
+ }
+ if (Utils.Constants.ComponentState.NOT_CERTIFIED_CHECKOUT === this.$scope.component.lifecycleState && this.$scope.isViewMode()) {
+ result = false;
+ }
+ if (Utils.Constants.ComponentState.CERTIFIED != this.$scope.component.lifecycleState &&
+ (Utils.Constants.Role.OPS == this.role || Utils.Constants.Role.GOVERNOR == this.role)) {
+ result = false;
+ }
+ return result;
+ };
+
+ this.$scope.updateSelectedMenuItem = (): void => {
+ let selectedItem: Sdc.Utils.MenuItem = _.find(this.$scope.leftBarTabs.menuItems, (item: Sdc.Utils.MenuItem) => {
+ return item.state === this.$state.current.name;
+ });
+ this.$scope.leftBarTabs.selectedIndex = selectedItem ? this.$scope.leftBarTabs.menuItems.indexOf(selectedItem) : 0;
+ };
+
+ this.$scope.$watch('$state.current.name', (newVal: string): void => {
+ if (newVal) {
+ this.$scope.isComposition = (newVal.indexOf(Utils.Constants.States.WORKSPACE_COMPOSITION) > -1);
+ this.$scope.isDeployment = (newVal.indexOf(Utils.Constants.States.WORKSPACE_DEPLOYMENT) > -1);
+ }
+ });
+ };
+
+ private initAfterScope = (): void => {
+ // In case user select csar from the onboarding modal, need to disable checkout and submit for testing.
+ if (this.$state.params['disableButtons'] === true) {
+ this.$scope.uploadFileChangedInGeneralTab();
+ }
+ };
+
+ private initVersionObject = (): void => {
+ this.$scope.versionsList = (this.$scope.component.getAllVersionsAsSortedArray()).reverse();
+ this.$scope.changeVersion = {selectedVersion: _.find(this.$scope.versionsList, {versionId: this.$scope.component.uniqueId})};
+ };
+
+ private getNewComponentBreadcrumbItem = (): Utils.MenuItem => {
+ let text = "";
+ if (this.$scope.component.isResource() && (<Resource>this.$scope.component).isCsarComponent()) {
+ text = this.$scope.component.getComponentSubType() + ': ' + this.$scope.component.name;
+ } else {
+ text = 'Create new ' + this.$state.params['type'];
+ }
+ return new Utils.MenuItem(text, null, Utils.Constants.States.WORKSPACE_GENERAL, 'goToState', [this.$state.params]);
+ };
+
+ private updateMenuItemByRole = (menuItems: Array<Utils.MenuItem>, role: string) => {
+ let tempMenuItems: Array<Utils.MenuItem> = new Array<Utils.MenuItem>();
+ menuItems.forEach((item: Utils.MenuItem) => {
+ //remove item if role is disabled
+ if (!(item.disabledRoles && item.disabledRoles.indexOf(role) > -1)) {
+ tempMenuItems.push(item);
+ }
+ });
+ return tempMenuItems;
+ };
+
+ private initBreadcrumbs = () => {
+ this.components = this.cacheService.get('breadcrumbsComponents');
+ let breadcrumbsComponentsLvl = this.MenuHandler.generateBreadcrumbsModelFromComponents(this.components, this.$scope.component);
+
+ if (this.$scope.isCreateMode()) {
+ let createItem = this.getNewComponentBreadcrumbItem();
+ if (!breadcrumbsComponentsLvl.menuItems) {
+ breadcrumbsComponentsLvl.menuItems = [];
+ }
+ breadcrumbsComponentsLvl.menuItems.unshift(createItem);
+ breadcrumbsComponentsLvl.selectedIndex = 0;
+ }
+
+ this.$scope.breadcrumbsModel = [breadcrumbsComponentsLvl, this.$scope.leftBarTabs];
+ };
+
+ private initMenuItems() {
+
+ let inCreateMode = this.$scope.isCreateMode();
+ this.$scope.leftBarTabs = new Utils.MenuItemGroup();
+ this.$scope.leftBarTabs.menuItems = this.updateMenuItemByRole(this.sdcMenu.component_workspace_menu_option[this.$scope.component.getComponentSubType()], this.role);
+
+ this.$scope.leftBarTabs.menuItems.forEach((item: Utils.MenuItem) => {
+ item.params = [item.state];
+ item.callback = this.$scope.onMenuItemPressed;
+ item.isDisabled = (inCreateMode && Utils.Constants.States.WORKSPACE_GENERAL != item.state) ||
+ (Utils.Constants.States.WORKSPACE_DEPLOYMENT === item.state && this.$scope.component.groups.length === 0 && this.$scope.component.isResource());
+ });
+
+ if (this.cacheService.get('breadcrumbsComponents')) {
+ this.initBreadcrumbs();
+ } else {
+ let onSuccess = (components: Array<Models.Components.Component>) => {
+ this.cacheService.set('breadcrumbsComponents', components);
+ this.initBreadcrumbs();
+ };
+ this.EntityService.getCatalog().then(onSuccess); //getAllComponents() doesnt return components from catalog
+ }
+ }
+
+ private disableMenuItems() {
+ this.$scope.leftBarTabs.menuItems.forEach((item: Utils.MenuItem) => {
+ item.params = [item.state];
+ item.callback = this.$scope.onMenuItemPressed;
+ item.isDisabled = (Utils.Constants.States.WORKSPACE_GENERAL != item.state);
+ });
+ }
+
+ private enableMenuItems() {
+ this.$scope.leftBarTabs.menuItems.forEach((item: Utils.MenuItem) => {
+ item.params = [item.state];
+ item.callback = this.$scope.onMenuItemPressed;
+ item.isDisabled = false;
+ });
+ }
+
+ }
+}
+
+
diff --git a/catalog-ui/app/scripts/view-models/workspace/workspace-view.html b/catalog-ui/app/scripts/view-models/workspace/workspace-view.html
new file mode 100644
index 0000000000..118f7474be
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/workspace-view.html
@@ -0,0 +1,86 @@
+<div class="sdc-workspace-container">
+ <loader data-display="isLoading"></loader>
+
+ <!-- HEADER -->
+<!--
+ <ecomp-header menu-data="menuItems" version="{{version}}"></ecomp-header>
+-->
+
+ <div class="w-sdc-main-container">
+
+ <div class="w-sdc-left-sidebar" data-ng-if="!isComposition">
+ <div class="i-sdc-left-sidebar-item">
+ <expand-collapse-menu-box menu-items-group="leftBarTabs" menu-title="{{menuComponentTitle}}" parent-scope="this"> </expand-collapse-menu-box>
+ </div>
+ </div>
+
+ <div include-padding="true" class="w-sdc-main-right-container" data-ng-class="{'composition':isComposition}">
+
+ <!--<div class="w-sdc-main-right-container" data-ng-class="{'composition':isComposition}">-->
+ <loader data-display="isCreateProgress" relative="true"></loader>
+
+ <div class="sdc-workspace-top-bar">
+ <div class="version-container">
+
+
+ <span data-ng-if="!isCreateMode() && !component.isLatestVersion()" class="not-latest"></span>
+ <select class="version-selector" data-ng-if="!isCreateMode()" data-tests-id="versionHeader" data-ng-model="changeVersion.selectedVersion"
+ ng-options="'V'+version.versionNumber for version in versionsList" data-ng-change="onVersionChanged(changeVersion.selectedVersion.versionId)">
+ </select>
+ </div>
+
+ <div class="lifecycle-state">
+ <div data-ng-show="showLifecycleIcon()" class="lifecycle-state-icon" data-ng-class="{'in-design-status-icon': isCreateMode(), '{{sdcMenu.LifeCycleStatuses[component.lifecycleState].icon}}': !isCreateMode()}"></div>
+ <span class="lifecycle-state-text" data-tests-id="formlifecyclestate">{{getStatus()}}</span>
+ </div>
+
+
+ <div class="progress-container" >
+ <top-progress class="general-view-top-progress" progress-value="progressService.getProgressValue(component.uniqueId)" progress-message="progressMessage"></top-progress>
+ </div>
+
+ <div class="sdc-workspace-top-bar-buttons">
+
+ <span ng-if="!isCreateMode() && !component.isLatestVersion() && !showChangeStateButton()">Switch to the&nbsp;<a ng-click="getLatestVersion()">latest version</a></span>
+
+ <button ng-if="isDesigner() && !isCreateMode()"
+ data-ng-class="{'disabled' :!isValidForm || isDisableMode() || isViewMode()}"
+ ng-click="save()"
+ class="tlv-btn blue"
+ data-tests-id="create/save"
+ data-ng-show="showFullIcons()"
+ sdc-smart-tooltip="">Update</button>
+
+ <button ng-repeat="(key,button) in changeLifecycleStateButtons"
+ ng-click="changeLifecycleState(key)"
+ ng-if="showChangeStateButton() && key != 'deleteVersion'"
+ data-ng-disabled="isCreateMode() || button.disabled || disabledButtons || !isValidForm"
+ class="change-lifecycle-state-btn tlv-btn"
+ ng-class="$first ? 'outline green' : 'grey'"
+ data-tests-id="{{button.text | testsId}}">
+ {{button.text}}
+ </button>
+
+ <button ng-if="!isViewMode() && isCreateMode()" data-ng-disabled="!isValidForm || isDisableMode() || isLoading" ng-click="save()" class="tlv-btn outline green" data-tests-id="create/save">Create</button>
+
+ <span data-ng-if="isDesigner() && !isCreateMode() && component.lifecycleState === 'NOT_CERTIFIED_CHECKOUT'" sdc-smart-tooltip=""
+ data-ng-class="{'disabled' : !isValidForm || isDisableMode() || isViewMode()}" ng-click="changeLifecycleState('deleteVersion')"
+ class="sprite-new delete-btn" data-tests-id="delete_version" sdc-smart-tooltip="">Delete</span>
+
+ <span data-ng-if="isDesigner()" data-ng-class="{'disabled' :isDisableMode() || isViewMode()}" ng-click="revert()" class="sprite-new revert-btn" data-tests-id="revert"
+ data-ng-show="showFullIcons()" sdc-smart-tooltip="">Revert</span>
+
+ <span data-ng-if="isComposition && !component.isProduct()" class="sprite-new print-screen-btn" entity="component" print-graph-screen data-tests-id="printScreen"></span>
+ <span class="delimiter"></span>
+ <span class="sprite-new x-btn" data-ng-click="goToBreadcrumbHome()" sdc-smart-tooltip="">Close</span>
+
+ </div>
+ </div>
+
+ <div data-ng-if="component.creationDate && (!isComposition && !isDeployment)" class="sdc-asset-creation-info"><b>Created:</b>&nbsp;{{component.creationDate | date:'MM/dd/yyyy'}},&nbsp;{{component.creatorFullName}}&nbsp;|&nbsp;<b>Modifed:</b>&nbsp;{{component.lastUpdateDate | date:'MM/dd/yyyy'}}&nbsp;|&nbsp;<b>UUID:</b>&nbsp;{{component.uuid}}<b>&nbsp;Invariant UUID:</b>&nbsp;{{component.invariantUUID}}</div>
+
+ <div class="w-sdc-main-container-body-content" data-ng-class="{'third-party':thirdParty}" data-ui-view></div>
+ </div>
+ </div>
+ <top-nav search-bind="search.filterTerm" hide-search="true" menu-model="breadcrumbsModel" version="{{version}}"></top-nav>
+</div>
diff --git a/catalog-ui/app/scripts/view-models/workspace/workspace.less b/catalog-ui/app/scripts/view-models/workspace/workspace.less
new file mode 100644
index 0000000000..d8bff1b634
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/workspace.less
@@ -0,0 +1,144 @@
+.sdc-workspace-container {
+ .bg_p;
+
+ .add-btn {
+ .f-color.a;
+ .f-type._12_m;
+ .hand;
+ float: right;
+ margin-bottom: 15px;
+
+ &:before {
+ .sprite-new;
+ .plus-icon;
+ margin-right: 5px;
+ content: "";
+
+ }
+ &:hover {
+ .f-color.b;
+ &:before {
+ .sprite-new;
+ .plus-icon-hover;
+ }
+ }
+
+ }
+ .w-sdc-left-sidebar {
+ padding: 3px 3px 0px 0px;
+ background-color: @main_color_p;
+ box-shadow: 7px -3px 6px -8px @main_color_n;
+ z-index: 2;
+ }
+
+ .sdc-asset-creation-info {
+ .n_12_r;
+ float: right;
+ margin: 8px 20px 0 0;
+ }
+
+ .w-sdc-main-right-container {
+
+ padding: 0px 0px 0px 0px;
+ background-color: @main_color_p;
+ z-index: 1;
+
+ .sdc-workspace-top-bar {
+ height: @action_nav_height;
+ padding: 12px 10px 0px 50px;
+ border-bottom: 1px solid @main_color_o;
+ display: flex;
+ justify-content: space-between;
+
+ .version-container {
+
+ }
+
+ .progress-container {
+ flex-grow: 4;
+ z-index: 10000000;
+
+ .general-view-top-progress {
+ width: 30%;
+ margin: 0 auto;
+ }
+ }
+
+ .not-latest {
+ position: absolute;
+ left: 24px;
+ top: 20px;
+ .sprite-new;
+ .asdc-warning;
+ }
+
+ .sdc-workspace-top-bar-buttons {
+
+ > button, > span:not(.delimiter) {
+ margin-right: 10px;
+ vertical-align: middle;
+ .hand;
+
+ &.sprite-new {
+ text-indent: 100%;
+ }
+ &.disabled, &:hover.disabled {
+ pointer-events: none;
+ }
+ }
+ .delimiter {
+ height: 32px;
+ width: 1px;
+ background-color: #959595;
+ display: inline-block;
+ vertical-align: middle;
+ margin-right: 20px;
+ }
+
+ }
+
+ .lifecycle-state {
+ padding: 7px 0 0 10px;
+ margin: 2px 0 7px 10px;
+ border-left: 1px solid @main_color_o;
+ line-height: 15px;
+ font-family: @font-omnes-medium;
+ color: @main_color_m;
+
+ .lifecycle-state-icon {
+ .sprite-new;
+ }
+ .lifecycle-state-text {
+
+ font-weight: bold;
+ text-transform: uppercase;
+ vertical-align: top;
+ padding: 3px;
+ }
+ }
+
+ .version-selector {
+ // float:left;
+ background-color: transparent;
+ border: none;
+ margin-top: 6px;
+ }
+ }
+ .w-sdc-main-container-body-content {
+ height:100%;
+
+ text-align: center;
+ align-items: center;
+ padding: 40px 14% 20px 14%;
+ &.third-party {
+ text-align: left;
+ padding: 0;
+ position: absolute;
+ top: @action_nav_height;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ }
+ }
+ }
+}