aboutsummaryrefslogtreecommitdiffstats
path: root/catalog-ui/app/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'catalog-ui/app/scripts')
-rw-r--r--catalog-ui/app/scripts/app.ts936
-rw-r--r--catalog-ui/app/scripts/directives/clicked-outside/clicked-outside-directive.ts131
-rw-r--r--catalog-ui/app/scripts/directives/custom-validation/custom-validation.ts55
-rw-r--r--catalog-ui/app/scripts/directives/download-artifact/download-artifact.ts141
-rw-r--r--catalog-ui/app/scripts/directives/ecomp-header/ecomp-header.html73
-rw-r--r--catalog-ui/app/scripts/directives/ecomp-header/ecomp-header.less296
-rw-r--r--catalog-ui/app/scripts/directives/ecomp-header/ecomp-header.ts235
-rw-r--r--catalog-ui/app/scripts/directives/edit-name-popover/edit-module-name-popover.html31
-rw-r--r--catalog-ui/app/scripts/directives/edit-name-popover/edit-name-popover-directive.ts98
-rw-r--r--catalog-ui/app/scripts/directives/edit-name-popover/edit-name-popover-view.html1
-rw-r--r--catalog-ui/app/scripts/directives/edit-name-popover/edit-name-popover.less71
-rw-r--r--catalog-ui/app/scripts/directives/elements/checkbox/checkbox.html13
-rw-r--r--catalog-ui/app/scripts/directives/elements/checkbox/checkbox.less35
-rw-r--r--catalog-ui/app/scripts/directives/elements/checkbox/checkbox.ts66
-rw-r--r--catalog-ui/app/scripts/directives/elements/radiobutton/radiobutton.html5
-rw-r--r--catalog-ui/app/scripts/directives/elements/radiobutton/radiobutton.less0
-rw-r--r--catalog-ui/app/scripts/directives/elements/radiobutton/radiobutton.ts71
-rw-r--r--catalog-ui/app/scripts/directives/ellipsis/ellipsis-directive.html7
-rw-r--r--catalog-ui/app/scripts/directives/ellipsis/ellipsis-directive.less10
-rw-r--r--catalog-ui/app/scripts/directives/ellipsis/ellipsis-directive.ts80
-rw-r--r--catalog-ui/app/scripts/directives/events/on-last-repeat/on-last-repeat.ts61
-rw-r--r--catalog-ui/app/scripts/directives/file-opener/file-opener.html3
-rw-r--r--catalog-ui/app/scripts/directives/file-opener/file-opener.ts77
-rw-r--r--catalog-ui/app/scripts/directives/file-type/file-type.ts66
-rw-r--r--catalog-ui/app/scripts/directives/file-upload/file-upload.html22
-rw-r--r--catalog-ui/app/scripts/directives/file-upload/file-upload.less75
-rw-r--r--catalog-ui/app/scripts/directives/file-upload/file-upload.ts134
-rw-r--r--catalog-ui/app/scripts/directives/graphs-v2/common/common-graph-utils.ts361
-rw-r--r--catalog-ui/app/scripts/directives/graphs-v2/common/style/component-instances-nodes-style.ts259
-rw-r--r--catalog-ui/app/scripts/directives/graphs-v2/common/style/module-node-style.ts92
-rw-r--r--catalog-ui/app/scripts/directives/graphs-v2/composition-graph/composition-graph.directive.ts555
-rw-r--r--catalog-ui/app/scripts/directives/graphs-v2/composition-graph/composition-graph.html22
-rw-r--r--catalog-ui/app/scripts/directives/graphs-v2/composition-graph/composition-graph.less14
-rw-r--r--catalog-ui/app/scripts/directives/graphs-v2/composition-graph/utils/composition-graph-general-utils.ts243
-rw-r--r--catalog-ui/app/scripts/directives/graphs-v2/composition-graph/utils/composition-graph-links-utils.ts347
-rw-r--r--catalog-ui/app/scripts/directives/graphs-v2/composition-graph/utils/composition-graph-nodes-utils.ts220
-rw-r--r--catalog-ui/app/scripts/directives/graphs-v2/composition-graph/utils/match-capability-requierment-utils.ts265
-rw-r--r--catalog-ui/app/scripts/directives/graphs-v2/deployment-graph/deployment-graph.directive.ts114
-rw-r--r--catalog-ui/app/scripts/directives/graphs-v2/deployment-graph/deployment-graph.html2
-rw-r--r--catalog-ui/app/scripts/directives/graphs-v2/deployment-graph/deployment-graph.less14
-rw-r--r--catalog-ui/app/scripts/directives/graphs-v2/deployment-graph/deployment-utils/deployment-graph-general-utils.ts24
-rw-r--r--catalog-ui/app/scripts/directives/graphs-v2/image-creator/image-creator.service.ts46
-rw-r--r--catalog-ui/app/scripts/directives/graphs-v2/palette/interfaces/i-dragdrop-event.d.ts7
-rw-r--r--catalog-ui/app/scripts/directives/graphs-v2/palette/palette.directive.ts327
-rw-r--r--catalog-ui/app/scripts/directives/graphs-v2/palette/palette.html59
-rw-r--r--catalog-ui/app/scripts/directives/graphs-v2/palette/palette.less92
-rw-r--r--catalog-ui/app/scripts/directives/graphs-v2/relation-menu/relation-menu.html63
-rw-r--r--catalog-ui/app/scripts/directives/graphs-v2/relation-menu/relation-menu.less118
-rw-r--r--catalog-ui/app/scripts/directives/graphs-v2/relation-menu/relation-menu.ts113
-rw-r--r--catalog-ui/app/scripts/directives/info-tooltip/info-tooltip.html10
-rw-r--r--catalog-ui/app/scripts/directives/info-tooltip/info-tooltip.less39
-rw-r--r--catalog-ui/app/scripts/directives/info-tooltip/info-tooltip.ts60
-rw-r--r--catalog-ui/app/scripts/directives/invalid-characters/invalid-characters.ts72
-rw-r--r--catalog-ui/app/scripts/directives/layout/top-nav/top-nav.html54
-rw-r--r--catalog-ui/app/scripts/directives/layout/top-nav/top-nav.less218
-rw-r--r--catalog-ui/app/scripts/directives/layout/top-nav/top-nav.ts155
-rw-r--r--catalog-ui/app/scripts/directives/layout/top-progress/top-progress.html22
-rw-r--r--catalog-ui/app/scripts/directives/layout/top-progress/top-progress.less58
-rw-r--r--catalog-ui/app/scripts/directives/layout/top-progress/top-progress.ts57
-rw-r--r--catalog-ui/app/scripts/directives/loader/loader-directive.html4
-rw-r--r--catalog-ui/app/scripts/directives/loader/loader-directive.less74
-rw-r--r--catalog-ui/app/scripts/directives/loader/loader-directive.ts155
-rw-r--r--catalog-ui/app/scripts/directives/modal/sdc-modal.html18
-rw-r--r--catalog-ui/app/scripts/directives/modal/sdc-modal.less10
-rw-r--r--catalog-ui/app/scripts/directives/modal/sdc-modal.ts103
-rw-r--r--catalog-ui/app/scripts/directives/page-scroller/page-scroller.html22
-rw-r--r--catalog-ui/app/scripts/directives/page-scroller/page-scroller.less98
-rw-r--r--catalog-ui/app/scripts/directives/page-scroller/page-scroller.ts247
-rw-r--r--catalog-ui/app/scripts/directives/perfect-scrollbar/angular-perfect-scrollbar.ts159
-rw-r--r--catalog-ui/app/scripts/directives/print-graph-screen/print-graph-screen.ts211
-rw-r--r--catalog-ui/app/scripts/directives/property-types/data-type-fields-structure/data-type-fields-structure.html82
-rw-r--r--catalog-ui/app/scripts/directives/property-types/data-type-fields-structure/data-type-fields-structure.less90
-rw-r--r--catalog-ui/app/scripts/directives/property-types/data-type-fields-structure/data-type-fields-structure.ts165
-rw-r--r--catalog-ui/app/scripts/directives/property-types/type-list/type-list-directive.html57
-rw-r--r--catalog-ui/app/scripts/directives/property-types/type-list/type-list-directive.less85
-rw-r--r--catalog-ui/app/scripts/directives/property-types/type-list/type-list-directive.ts130
-rw-r--r--catalog-ui/app/scripts/directives/property-types/type-map/type-map-directive.html70
-rw-r--r--catalog-ui/app/scripts/directives/property-types/type-map/type-map-directive.less83
-rw-r--r--catalog-ui/app/scripts/directives/property-types/type-map/type-map-directive.ts157
-rw-r--r--catalog-ui/app/scripts/directives/punch-out/punch-out.ts99
-rw-r--r--catalog-ui/app/scripts/directives/sdc-tabs/sdc-single-tab/sdc-single-tab-directive.ts67
-rw-r--r--catalog-ui/app/scripts/directives/sdc-tabs/sdc-single-tab/sdc-single-tab.less1
-rw-r--r--catalog-ui/app/scripts/directives/sdc-tabs/sdc-tabs-directive-view.html17
-rw-r--r--catalog-ui/app/scripts/directives/sdc-tabs/sdc-tabs-directive.ts69
-rw-r--r--catalog-ui/app/scripts/directives/sdc-tabs/sdc-tabs.less68
-rw-r--r--catalog-ui/app/scripts/directives/structure-tree/structure-tree-directive.html54
-rw-r--r--catalog-ui/app/scripts/directives/structure-tree/structure-tree-directive.less68
-rw-r--r--catalog-ui/app/scripts/directives/structure-tree/structure-tree-directive.ts197
-rw-r--r--catalog-ui/app/scripts/directives/tag/tag-directive.html10
-rw-r--r--catalog-ui/app/scripts/directives/tag/tag-directive.less51
-rw-r--r--catalog-ui/app/scripts/directives/tag/tag-directive.ts71
-rw-r--r--catalog-ui/app/scripts/directives/tutorial/image-template.html7
-rw-r--r--catalog-ui/app/scripts/directives/tutorial/text-template.html4
-rw-r--r--catalog-ui/app/scripts/directives/tutorial/tutorial-directive.html22
-rw-r--r--catalog-ui/app/scripts/directives/tutorial/tutorial-directive.less213
-rw-r--r--catalog-ui/app/scripts/directives/tutorial/tutorial-directive.ts147
-rw-r--r--catalog-ui/app/scripts/directives/user-header-details/user-header-details-directive.html9
-rw-r--r--catalog-ui/app/scripts/directives/user-header-details/user-header-details-directive.less62
-rw-r--r--catalog-ui/app/scripts/directives/user-header-details/user-header-details-directive.ts72
-rw-r--r--catalog-ui/app/scripts/directives/utils/expand-collapse-menu-box/expand-collaps-menu-box.ts66
-rw-r--r--catalog-ui/app/scripts/directives/utils/expand-collapse-menu-box/expand-collapse-menu-box.html15
-rw-r--r--catalog-ui/app/scripts/directives/utils/expand-collapse-menu-box/expand-collapse-menu-box.less55
-rw-r--r--catalog-ui/app/scripts/directives/utils/expand-collapse/expand-collapse.html1
-rw-r--r--catalog-ui/app/scripts/directives/utils/expand-collapse/expand-collapse.less10
-rw-r--r--catalog-ui/app/scripts/directives/utils/expand-collapse/expand-collapse.ts136
-rw-r--r--catalog-ui/app/scripts/directives/utils/page-selector/page-selector.html9
-rw-r--r--catalog-ui/app/scripts/directives/utils/page-selector/page-selector.less51
-rw-r--r--catalog-ui/app/scripts/directives/utils/page-selector/page-selector.ts106
-rw-r--r--catalog-ui/app/scripts/directives/utils/sdc-keyboard-events/sdc-keyboard-events.ts106
-rw-r--r--catalog-ui/app/scripts/directives/utils/sdc-tags/sdc-tags.html27
-rw-r--r--catalog-ui/app/scripts/directives/utils/sdc-tags/sdc-tags.less61
-rw-r--r--catalog-ui/app/scripts/directives/utils/sdc-tags/sdc-tags.ts97
-rw-r--r--catalog-ui/app/scripts/directives/utils/sdc_error_tooltip/sdc_error_tooltip.html6
-rw-r--r--catalog-ui/app/scripts/directives/utils/sdc_error_tooltip/sdc_error_tooltip.ts109
-rw-r--r--catalog-ui/app/scripts/directives/utils/sdc_messages/sdc-message.ts179
-rw-r--r--catalog-ui/app/scripts/directives/utils/sdc_messages/sdc-messages.less10
-rw-r--r--catalog-ui/app/scripts/directives/utils/sdc_messages/sdc-messages.ts245
-rw-r--r--catalog-ui/app/scripts/directives/utils/sdc_messages/sdc_messages.html1
-rw-r--r--catalog-ui/app/scripts/directives/utils/smart-tooltip/smart-tooltip.ts85
-rw-r--r--catalog-ui/app/scripts/directives/utils/wizard_steps/sdc-wizard-steps.html16
-rw-r--r--catalog-ui/app/scripts/directives/utils/wizard_steps/sdc-wizard-steps.less69
-rw-r--r--catalog-ui/app/scripts/directives/utils/wizard_steps/sdc-wizard-steps.ts139
-rw-r--r--catalog-ui/app/scripts/filters/_category-name-filter.ts40
-rw-r--r--catalog-ui/app/scripts/filters/capitalize-filter.ts43
-rw-r--r--catalog-ui/app/scripts/filters/catalog-status-filter.ts41
-rw-r--r--catalog-ui/app/scripts/filters/category-icon-filter.ts54
-rw-r--r--catalog-ui/app/scripts/filters/category-type-filter.ts45
-rw-r--r--catalog-ui/app/scripts/filters/clear-whitespaces-filter.ts39
-rw-r--r--catalog-ui/app/scripts/filters/entity-filter.ts117
-rw-r--r--catalog-ui/app/scripts/filters/graph-resource-name-filter.ts47
-rw-r--r--catalog-ui/app/scripts/filters/product-category-name-filter.ts39
-rw-r--r--catalog-ui/app/scripts/filters/product-subcategory-name-filter.ts39
-rw-r--r--catalog-ui/app/scripts/filters/relation-name-fllter.ts53
-rw-r--r--catalog-ui/app/scripts/filters/resource-name-filter.ts45
-rw-r--r--catalog-ui/app/scripts/filters/resource-type-filter.ts38
-rw-r--r--catalog-ui/app/scripts/filters/string-to-date-filter.ts34
-rw-r--r--catalog-ui/app/scripts/filters/tests-id-filter.ts33
-rw-r--r--catalog-ui/app/scripts/filters/trim-filter.ts39
-rw-r--r--catalog-ui/app/scripts/filters/truncate-filter.ts48
-rw-r--r--catalog-ui/app/scripts/filters/underscoreless-filter.ts33
-rw-r--r--catalog-ui/app/scripts/models/activity.ts48
-rw-r--r--catalog-ui/app/scripts/models/additional-information.ts44
-rw-r--r--catalog-ui/app/scripts/models/app-config.ts232
-rw-r--r--catalog-ui/app/scripts/models/artifacts.ts115
-rw-r--r--catalog-ui/app/scripts/models/aschema-property.ts63
-rw-r--r--catalog-ui/app/scripts/models/attributes.ts139
-rw-r--r--catalog-ui/app/scripts/models/capability.ts116
-rw-r--r--catalog-ui/app/scripts/models/category.ts67
-rw-r--r--catalog-ui/app/scripts/models/comments.ts33
-rw-r--r--catalog-ui/app/scripts/models/components/component.ts828
-rw-r--r--catalog-ui/app/scripts/models/components/displayComponent.ts98
-rw-r--r--catalog-ui/app/scripts/models/components/product.ts109
-rw-r--r--catalog-ui/app/scripts/models/components/resource.ts185
-rw-r--r--catalog-ui/app/scripts/models/components/service.ts147
-rw-r--r--catalog-ui/app/scripts/models/componentsInstances/componentInstance.ts126
-rw-r--r--catalog-ui/app/scripts/models/componentsInstances/productInstance.ts34
-rw-r--r--catalog-ui/app/scripts/models/componentsInstances/resourceInstance.ts36
-rw-r--r--catalog-ui/app/scripts/models/componentsInstances/serviceInstance.ts35
-rw-r--r--catalog-ui/app/scripts/models/csar-component.ts36
-rw-r--r--catalog-ui/app/scripts/models/data-type-properties.ts65
-rw-r--r--catalog-ui/app/scripts/models/data-types-map.ts39
-rw-r--r--catalog-ui/app/scripts/models/data-types.ts55
-rw-r--r--catalog-ui/app/scripts/models/distribution.ts66
-rw-r--r--catalog-ui/app/scripts/models/file-download.ts28
-rw-r--r--catalog-ui/app/scripts/models/graph/d2-node.ts31
-rw-r--r--catalog-ui/app/scripts/models/graph/graph-links/common-base-link.ts53
-rw-r--r--catalog-ui/app/scripts/models/graph/graph-links/common-ci-link-base.ts50
-rw-r--r--catalog-ui/app/scripts/models/graph/graph-links/composition-graph-links/composition-ci-link-base.ts46
-rw-r--r--catalog-ui/app/scripts/models/graph/graph-links/composition-graph-links/composition-ci-simple-link.ts31
-rw-r--r--catalog-ui/app/scripts/models/graph/graph-links/composition-graph-links/composition-ci-ucpe-host-link.ts33
-rw-r--r--catalog-ui/app/scripts/models/graph/graph-links/composition-graph-links/composition-ci-ucpe-link.ts37
-rw-r--r--catalog-ui/app/scripts/models/graph/graph-links/composition-graph-links/composition-ci-vl-link.ts34
-rw-r--r--catalog-ui/app/scripts/models/graph/graph-links/composition-graph-links/composition-ci-vl-ucpe-link.ts33
-rw-r--r--catalog-ui/app/scripts/models/graph/graph-links/links-factory.ts80
-rw-r--r--catalog-ui/app/scripts/models/graph/graph-links/module-graph-links/module-ci-link-base.ts38
-rw-r--r--catalog-ui/app/scripts/models/graph/graph-links/module-graph-links/module-ci-vl-link.ts37
-rw-r--r--catalog-ui/app/scripts/models/graph/graphTooltip.ts38
-rw-r--r--catalog-ui/app/scripts/models/graph/link-menu.ts38
-rw-r--r--catalog-ui/app/scripts/models/graph/match-relation.ts109
-rw-r--r--catalog-ui/app/scripts/models/graph/nodes/base-common-node.ts73
-rw-r--r--catalog-ui/app/scripts/models/graph/nodes/common-ci-node-base.ts46
-rw-r--r--catalog-ui/app/scripts/models/graph/nodes/composition-graph-nodes/composition-ci-node-base.ts72
-rw-r--r--catalog-ui/app/scripts/models/graph/nodes/composition-graph-nodes/composition-ci-node-cp.ts48
-rw-r--r--catalog-ui/app/scripts/models/graph/nodes/composition-graph-nodes/composition-ci-node-service.ts42
-rw-r--r--catalog-ui/app/scripts/models/graph/nodes/composition-graph-nodes/composition-ci-node-ucpe-cp.ts39
-rw-r--r--catalog-ui/app/scripts/models/graph/nodes/composition-graph-nodes/composition-ci-node-ucpe.ts50
-rw-r--r--catalog-ui/app/scripts/models/graph/nodes/composition-graph-nodes/composition-ci-node-vf.ts41
-rw-r--r--catalog-ui/app/scripts/models/graph/nodes/composition-graph-nodes/composition-ci-node-vfc.ts33
-rw-r--r--catalog-ui/app/scripts/models/graph/nodes/composition-graph-nodes/composition-ci-node-vl.ts54
-rw-r--r--catalog-ui/app/scripts/models/graph/nodes/modules-graph-nodes/module-node-base.ts52
-rw-r--r--catalog-ui/app/scripts/models/graph/nodes/nodes-factory.ts63
-rw-r--r--catalog-ui/app/scripts/models/graph/point.ts26
-rw-r--r--catalog-ui/app/scripts/models/graph/relationMenuObjects.ts138
-rw-r--r--catalog-ui/app/scripts/models/graph/relationship.ts107
-rw-r--r--catalog-ui/app/scripts/models/inputs.ts74
-rw-r--r--catalog-ui/app/scripts/models/instance-inputs-properties-map.ts39
-rw-r--r--catalog-ui/app/scripts/models/instances-inputs-map.ts39
-rw-r--r--catalog-ui/app/scripts/models/left-panel.ts33
-rw-r--r--catalog-ui/app/scripts/models/member.ts39
-rw-r--r--catalog-ui/app/scripts/models/modules/base-module.ts108
-rw-r--r--catalog-ui/app/scripts/models/properties.ts176
-rw-r--r--catalog-ui/app/scripts/models/requirement.ts91
-rw-r--r--catalog-ui/app/scripts/models/schema-attribute.ts37
-rw-r--r--catalog-ui/app/scripts/models/tab.ts47
-rw-r--r--catalog-ui/app/scripts/models/tooltip-data.ts28
-rw-r--r--catalog-ui/app/scripts/models/user.ts117
-rw-r--r--catalog-ui/app/scripts/models/validate.ts29
-rw-r--r--catalog-ui/app/scripts/modules/directive-module.ts106
-rw-r--r--catalog-ui/app/scripts/modules/filters.ts44
-rw-r--r--catalog-ui/app/scripts/modules/service-module.ts64
-rw-r--r--catalog-ui/app/scripts/modules/utils.ts39
-rw-r--r--catalog-ui/app/scripts/modules/view-model-module.ts96
-rw-r--r--catalog-ui/app/scripts/services/activity-log-service.ts48
-rw-r--r--catalog-ui/app/scripts/services/angular-js-bridge-service.ts22
-rw-r--r--catalog-ui/app/scripts/services/available-icons-service.ts105
-rw-r--r--catalog-ui/app/scripts/services/cache-service.ts58
-rw-r--r--catalog-ui/app/scripts/services/category-resource-service.ts83
-rw-r--r--catalog-ui/app/scripts/services/components/component-service.ts698
-rw-r--r--catalog-ui/app/scripts/services/components/product-service.ts61
-rw-r--r--catalog-ui/app/scripts/services/components/resource-service.ts61
-rw-r--r--catalog-ui/app/scripts/services/components/service-service.ts97
-rw-r--r--catalog-ui/app/scripts/services/components/utils/composition-left-palette-service.ts248
-rw-r--r--catalog-ui/app/scripts/services/configuration-ui-service.ts49
-rw-r--r--catalog-ui/app/scripts/services/cookie-service.ts95
-rw-r--r--catalog-ui/app/scripts/services/data-types-service.ts129
-rw-r--r--catalog-ui/app/scripts/services/ecomp-service.ts54
-rw-r--r--catalog-ui/app/scripts/services/entity-service.ts114
-rw-r--r--catalog-ui/app/scripts/services/event-listener-service.ts78
-rw-r--r--catalog-ui/app/scripts/services/header-interceptor.ts93
-rw-r--r--catalog-ui/app/scripts/services/http-error-interceptor.ts118
-rw-r--r--catalog-ui/app/scripts/services/loader-service.ts26
-rw-r--r--catalog-ui/app/scripts/services/onboarding-service.ts103
-rw-r--r--catalog-ui/app/scripts/services/progress-service.ts112
-rw-r--r--catalog-ui/app/scripts/services/relation-icons-service.ts61
-rw-r--r--catalog-ui/app/scripts/services/sdc-version-service.ts48
-rw-r--r--catalog-ui/app/scripts/services/sharing-service.ts41
-rw-r--r--catalog-ui/app/scripts/services/url-tobase64-service.ts52
-rw-r--r--catalog-ui/app/scripts/services/user-resource-service.ts123
-rw-r--r--catalog-ui/app/scripts/utils/artifacts-utils.ts121
-rw-r--r--catalog-ui/app/scripts/utils/change-lifecycle-state-handler.ts151
-rw-r--r--catalog-ui/app/scripts/utils/common-utils.ts81
-rw-r--r--catalog-ui/app/scripts/utils/component-factory.ts181
-rw-r--r--catalog-ui/app/scripts/utils/component-instance-factory.ts85
-rw-r--r--catalog-ui/app/scripts/utils/constants.ts247
-rw-r--r--catalog-ui/app/scripts/utils/dictionary/dictionary.ts257
-rw-r--r--catalog-ui/app/scripts/utils/file-utils.ts73
-rw-r--r--catalog-ui/app/scripts/utils/functions.ts58
-rw-r--r--catalog-ui/app/scripts/utils/menu-handler.ts145
-rw-r--r--catalog-ui/app/scripts/utils/modals-handler.ts275
-rw-r--r--catalog-ui/app/scripts/utils/prototypes.ts155
-rw-r--r--catalog-ui/app/scripts/utils/validation-utils.ts173
-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
444 files changed, 46164 insertions, 0 deletions
diff --git a/catalog-ui/app/scripts/app.ts b/catalog-ui/app/scripts/app.ts
new file mode 100644
index 0000000000..513810595b
--- /dev/null
+++ b/catalog-ui/app/scripts/app.ts
@@ -0,0 +1,936 @@
+/*-
+ * ============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"/>
+/*
+ SD&C Web Portal Wireframes – Designer Home Page and Create New Service Flow
+ */
+//libraries variables to prevent compile errors
+declare let jsPDF:any;
+
+module Sdc {
+ import User = Sdc.Models.User;
+ import UserResourceService = Sdc.Services.UserResourceService;
+
+ 'use strict';
+ import Resource = Sdc.Models.Components.Resource;
+ let moduleName:string = 'sdcApp';
+ let viewModelsModuleName:string = 'Sdc.ViewModels';
+ let directivesModuleName:string = 'Sdc.Directives';
+ let servicesModuleName:string = 'Sdc.Services';
+ let filtersModuleName:string = 'Sdc.Filters';
+ let utilsModuleName: string = 'Sdc.Utils';
+ let dependentModules:Array<string> = [
+ 'ui.router',
+ 'ui.bootstrap',
+ 'ngDragDrop',
+ 'ui-notification',
+ 'ngResource',
+ 'ngSanitize',
+ 'Sdc.Config',
+ 'naif.base64',
+ 'base64',
+ 'uuid4',
+ 'checklist-model',
+ 'angular.filter',
+ 'pascalprecht.translate',
+ '720kb.tooltips',
+ 'restangular',
+ 'angular-clipboard',
+ 'angularResizable',
+ 'infinite-scroll',
+ viewModelsModuleName,
+ directivesModuleName,
+ servicesModuleName,
+ filtersModuleName,
+ utilsModuleName
+ ];
+
+ let appModule:ng.IModule = angular.module(moduleName, dependentModules);
+
+ appModule.config([
+ '$stateProvider',
+ '$translateProvider',
+ '$urlRouterProvider',
+ '$httpProvider',
+ 'tooltipsConfigProvider',
+ 'NotificationProvider',
+ ($stateProvider:any,
+ $translateProvider:any,
+ $urlRouterProvider:ng.ui.IUrlRouterProvider,
+ $httpProvider:ng.IHttpProvider,
+ tooltipsConfigProvider:any,
+ NotificationProvider:any):void => {
+
+ NotificationProvider.setOptions({
+ delay: 10000,
+ startTop: 10,
+ startRight: 10,
+ closeOnClick: true,
+ verticalSpacing: 20,
+ horizontalSpacing: 20,
+ positionX: 'right',
+ positionY: 'top'
+ });
+
+ let viewModelsHtmlBasePath:string = '/app/scripts/view-models/';
+ console.info('appModule.config: ', viewModelsHtmlBasePath);
+
+ $translateProvider.useStaticFilesLoader({
+ prefix: 'languages/',
+ langKey: '',
+ suffix: '.json?d=' + (new Date()).getTime()
+ });
+ $translateProvider.useSanitizeValueStrategy('escaped');
+ $translateProvider.preferredLanguage('en_US_OS'); // For open source changed to en_US_OS
+
+ $httpProvider.interceptors.push('Sdc.Services.HeaderInterceptor');
+ $httpProvider.interceptors.push('Sdc.Services.HttpErrorInterceptor');
+
+ $urlRouterProvider.otherwise('welcome');
+
+ $stateProvider.state(
+ 'dashboard', {
+ url: '/dashboard?show&folder',
+ templateProvider: ['$templateCache', ($templateCache):string => {
+ return $templateCache.get(viewModelsHtmlBasePath + 'dashboard/dashboard-view.html');
+ }],
+ controller: viewModelsModuleName + '.DashboardViewModel',
+
+ }
+ );
+
+ $stateProvider.state(
+ 'welcome', {
+ url: '/welcome',
+ templateProvider: ['$templateCache', ($templateCache):string => {
+ return $templateCache.get(viewModelsHtmlBasePath + 'welcome/welcome-view.html');
+ }],
+ controller: viewModelsModuleName + '.WelcomeViewModel'
+ }
+ );
+
+ $stateProvider.state(
+ 'dashboard.cover', {
+ url: '/cover',
+ templateProvider: ['$templateCache', ($templateCache):string => {
+ return $templateCache.get(viewModelsHtmlBasePath + 'dashboard/cover/dashboard-cover-view.html');
+ }],
+ controller: viewModelsModuleName + '.DashboardCoverViewModel'
+ }
+ );
+
+ $stateProvider.state(
+ 'dashboard.tutorial-end', {
+ url: '/tutorial-end',
+ templateProvider: ['$templateCache', ($templateCache):string => {
+ return $templateCache.get(viewModelsHtmlBasePath + 'tutorial-end/tutorial-end.html');
+ }],
+ controller: viewModelsModuleName + '.TutorialEndViewModel'
+ }
+ );
+
+ $stateProvider.state(
+ 'additionalInformation', {
+ url: '/additionalInformation',
+ templateProvider: ['$templateCache', ($templateCache):string => {
+ return $templateCache.get(viewModelsHtmlBasePath + 'additional-information/additional-information-view.html');
+ }],
+ controller: viewModelsModuleName + '.AdditionalInformationViewModel'
+ }
+ );
+
+ let componentsParam:Array<any> = ['$stateParams', 'Sdc.Services.EntityService','Sdc.Services.CacheService' , ($stateParams:any, EntityService:Sdc.Services.EntityService, cacheService:Services.CacheService) => {
+ if(cacheService.get('breadcrumbsComponents')){
+ return cacheService.get('breadcrumbsComponents');
+ } else {
+ return EntityService.getCatalog(); //getAllComponents() doesnt return components from catalog
+ }
+ }];
+
+
+ $stateProvider.state (
+ 'workspace', {
+ url: '/workspace/:id/:type/',
+ params: {'importedFile':null,'componentCsar':null,'resourceType': null, 'disableButtons': null}, //'vspComponent': null,
+ templateProvider: ['$templateCache', ($templateCache:ng.ITemplateCacheService):string => {
+ return $templateCache.get(viewModelsHtmlBasePath + 'workspace/workspace-view.html');
+ }],
+ controller: viewModelsModuleName + '.WorkspaceViewModel',
+ resolve: {
+ injectComponent: ['$stateParams', 'ComponentFactory' , function ($stateParams, ComponentFactory) {
+ /*
+ if($stateParams.vspComponent){
+ return $stateParams.vspComponent;
+ } else
+ */
+ if($stateParams.id){
+ return ComponentFactory.getComponentFromServer($stateParams.type.toUpperCase(), $stateParams.id);
+ } else if ($stateParams.componentCsar && $stateParams.componentCsar.csarUUID) {
+ return $stateParams.componentCsar;
+ } else {
+ let emptyComponent = ComponentFactory.createEmptyComponent($stateParams.type.toUpperCase());
+ if (emptyComponent.isResource() && $stateParams.resourceType){
+ // Set the resource type
+ (<Resource>emptyComponent).resourceType = $stateParams.resourceType;
+ }
+ if($stateParams.importedFile){
+ (<Models.Components.Resource>emptyComponent).importedFile = $stateParams.importedFile;
+ }
+ return emptyComponent;
+ }
+ }],
+ components: componentsParam
+ }
+ }
+ );
+
+ $stateProvider.state(
+ Utils.Constants.States.WORKSPACE_GENERAL, {
+ url: 'general',
+ parent: 'workspace',
+ controller: viewModelsModuleName + '.GeneralViewModel',
+ templateProvider: ['$templateCache', ($templateCache:ng.ITemplateCacheService):string => {
+ return $templateCache.get(viewModelsHtmlBasePath + 'workspace/tabs/general/general-view.html');
+ }],
+ data: {unsavedChanges:false,bodyClass:'general'}
+ }
+ );
+
+ $stateProvider.state(
+ Utils.Constants.States.WORKSPACE_ICONS, {
+ url: 'icons',
+ parent: 'workspace',
+ controller: viewModelsModuleName + '.IconsViewModel',
+ templateProvider: ['$templateCache', ($templateCache:ng.ITemplateCacheService):string => {
+
+ return $templateCache.get(viewModelsHtmlBasePath + 'workspace/tabs/icons/icons-view.html');
+ }],
+ data: {unsavedChanges:false,bodyClass:'icons'}
+
+ }
+ );
+
+ $stateProvider.state(
+ Utils.Constants.States.WORKSPACE_ACTIVITY_LOG, {
+ url: 'activity_log',
+ parent: 'workspace',
+ controller: viewModelsModuleName + '.ActivityLogViewModel',
+ templateProvider: ['$templateCache', ($templateCache:ng.ITemplateCacheService):string => {
+ return $templateCache.get(viewModelsHtmlBasePath + 'workspace/tabs/activity-log/activity-log.html');
+ }],
+ data: {unsavedChanges:false}
+ }
+ );
+
+ $stateProvider.state(
+ Utils.Constants.States.WORKSPACE_DEPLOYMENT_ARTIFACTS, {
+ url: 'deployment_artifacts',
+ parent: 'workspace',
+ controller: viewModelsModuleName + '.DeploymentArtifactsViewModel',
+ templateProvider: ['$templateCache', ($templateCache:ng.ITemplateCacheService):string => {
+
+ return $templateCache.get(viewModelsHtmlBasePath + 'workspace/tabs/deployment-artifacts/deployment-artifacts-view.html');
+ }],
+ data:{
+ bodyClass:'deployment_artifacts'
+ }
+ }
+ );
+
+ $stateProvider.state(
+ Utils.Constants.States.WORKSPACE_HIERARCHY, {
+ url: 'hierarchy',
+ parent: 'workspace',
+ controller: viewModelsModuleName + '.ProductHierarchyViewModel',
+ templateProvider: ['$templateCache', ($templateCache:ng.ITemplateCacheService):string => {
+
+ return $templateCache.get(viewModelsHtmlBasePath + 'workspace/tabs/product-hierarchy/product-hierarchy-view.html');
+ }]
+
+ }
+ );
+
+ $stateProvider.state(
+ Utils.Constants.States.WORKSPACE_INFORMATION_ARTIFACTS, {
+ url: 'information_artifacts',
+ parent: 'workspace',
+ controller: viewModelsModuleName + '.InformationArtifactsViewModel',
+ templateProvider: ['$templateCache', ($templateCache:ng.ITemplateCacheService):string => {
+
+ return $templateCache.get(viewModelsHtmlBasePath + 'workspace/tabs/information-artifacts/information-artifacts-view.html');
+ }],
+ data:{
+ bodyClass:'information_artifacts'
+ }
+ }
+ );
+
+ $stateProvider.state(
+ Utils.Constants.States.WORKSPACE_TOSCA_ARTIFACTS, {
+ url: 'tosca_artifacts',
+ parent: 'workspace',
+ controller: viewModelsModuleName + '.ToscaArtifactsViewModel',
+ templateProvider: ['$templateCache', ($templateCache:ng.ITemplateCacheService):string => {
+
+ return $templateCache.get(viewModelsHtmlBasePath + 'workspace/tabs/tosca-artifacts/tosca-artifacts-view.html');
+ }],
+ data:{
+ bodyClass:'tosca_artifacts'
+ }
+ }
+ );
+
+ $stateProvider.state(
+ Utils.Constants.States.WORKSPACE_PROPERTIES, {
+ url: 'properties',
+ parent: 'workspace',
+ controller: viewModelsModuleName + '.PropertiesViewModel',
+ templateProvider: ['$templateCache', ($templateCache:ng.ITemplateCacheService):string => {
+ return $templateCache.get(viewModelsHtmlBasePath + 'workspace/tabs/properties/properties-view.html');
+ }],
+ data:{
+ bodyClass:'properties'
+ }
+ }
+ );
+
+ $stateProvider.state(
+ Utils.Constants.States.WORKSPACE_SERVICE_INPUTS, {
+ url: 'service_inputs',
+ parent: 'workspace',
+ controller: viewModelsModuleName + '.ServiceInputsViewModel',
+ templateProvider: ['$templateCache', ($templateCache:ng.ITemplateCacheService):string => {
+ return $templateCache.get(viewModelsHtmlBasePath + 'workspace/tabs/inputs/service-input/service-inputs-view.html');
+ }],
+ data:{
+ bodyClass:'workspace-inputs'
+ }
+ }
+ );
+
+ $stateProvider.state(
+ Utils.Constants.States.WORKSPACE_RESOURCE_INPUTS, {
+ url: 'resource_inputs',
+ parent: 'workspace',
+ controller: viewModelsModuleName + '.ResourceInputsViewModel',
+ templateProvider: ['$templateCache', ($templateCache:ng.ITemplateCacheService):string => {
+ return $templateCache.get(viewModelsHtmlBasePath + 'workspace/tabs/inputs/resource-input/resource-inputs-view.html');
+ }],
+ data:{
+ bodyClass:'workspace-inputs'
+ }
+ }
+ );
+
+ $stateProvider.state(
+ Utils.Constants.States.WORKSPACE_ATTRIBUTES, {
+ url: 'attributes',
+ parent: 'workspace',
+ controller: viewModelsModuleName + '.AttributesViewModel',
+ templateProvider: ['$templateCache', ($templateCache:ng.ITemplateCacheService):string => {
+ return $templateCache.get(viewModelsHtmlBasePath + 'workspace/tabs/attributes/attributes-view.html');
+ }],
+ data:{
+ bodyClass:'attributes'
+ }
+ }
+ );
+
+ $stateProvider.state(
+ Utils.Constants.States.WORKSPACE_REQUIREMENTS_AND_CAPABILITIES, {
+ url: 'req_and_capabilities',
+ parent: 'workspace',
+ controller: viewModelsModuleName + '.ReqAndCapabilitiesViewModel',
+ templateProvider: ['$templateCache', ($templateCache:ng.ITemplateCacheService):string => {
+ return $templateCache.get(viewModelsHtmlBasePath + 'workspace/tabs/req-and-capabilities/req-and-capabilities-view.html');
+ }],
+ data:{
+ bodyClass:'attributes'
+ }
+ }
+ );
+
+
+ $stateProvider.state(
+ Utils.Constants.States.WORKSPACE_MANAGEMENT_WORKFLOW, {
+ parent: 'workspace',
+ url: 'management_workflow',
+ templateProvider: ['$templateCache', ($templateCache):string => {
+ return $templateCache.get(viewModelsHtmlBasePath + 'workspace/tabs/management-workflow/management-workflow-view.html');
+ }],
+ controller: viewModelsModuleName + '.ManagementWorkflowViewModel'
+ }
+ );
+
+ $stateProvider.state(
+ Utils.Constants.States.WORKSPACE_NETWORK_CALL_FLOW, {
+ parent: 'workspace',
+ url: 'network_call_flow',
+ templateProvider: ['$templateCache', ($templateCache):string => {
+ return $templateCache.get(viewModelsHtmlBasePath + 'workspace/tabs/network-call-flow/network-call-flow-view.html');
+ }],
+ controller: viewModelsModuleName + '.NetworkCallFlowViewModel'
+ }
+ );
+
+ $stateProvider.state(
+ Utils.Constants.States.WORKSPACE_DISTRIBUTION, {
+ parent: 'workspace',
+ url: 'distribution',
+ templateProvider: ['$templateCache', ($templateCache):string => {
+ return $templateCache.get(viewModelsHtmlBasePath + 'workspace/tabs/distribution/distribution-view.html');
+ }],
+ controller: viewModelsModuleName + '.DistributionViewModel'
+ }
+ );
+
+ $stateProvider.state(
+ Utils.Constants.States.WORKSPACE_COMPOSITION, {
+ url: 'composition/',
+ parent: 'workspace',
+ controller: viewModelsModuleName + '.CompositionViewModel',
+ templateProvider: ['$templateCache', ($templateCache:ng.ITemplateCacheService):string => {
+
+ return $templateCache.get(viewModelsHtmlBasePath + 'workspace/tabs/composition/composition-view.html');
+ }],
+ data:{
+ bodyClass:'composition'
+ }
+ }
+ );
+
+ $stateProvider.state(
+ Utils.Constants.States.WORKSPACE_DEPLOYMENT, {
+ url: 'deployment/',
+ parent: 'workspace',
+ controller: viewModelsModuleName + '.DeploymentViewModel',
+ templateProvider: ['$templateCache', ($templateCache:ng.ITemplateCacheService):string => {
+
+ return $templateCache.get(viewModelsHtmlBasePath + 'workspace/tabs/deployment/deployment-view.html');
+ }],
+ data:{
+ bodyClass:'composition'
+ }
+ }
+ );
+
+ $stateProvider.state(
+ 'workspace.composition.details', {
+ url: 'details',
+ parent: 'workspace.composition',
+ templateProvider: ['$templateCache', ($templateCache):string => {
+ return $templateCache.get(viewModelsHtmlBasePath + 'workspace/tabs/composition/tabs/details/details-view.html');
+ }],
+ controller: viewModelsModuleName + '.DetailsViewModel'
+ }
+ );
+
+ $stateProvider.state(
+ 'workspace.composition.properties', {
+ url: 'properties',
+ parent: 'workspace.composition',
+ templateProvider: ['$templateCache', ($templateCache):string => {
+ return $templateCache.get(viewModelsHtmlBasePath + 'workspace/tabs/composition/tabs/properties-and-attributes/properties-view.html');
+ }],
+ controller: viewModelsModuleName + '.ResourcePropertiesViewModel'
+ }
+ );
+
+ $stateProvider.state(
+ 'workspace.composition.artifacts', {
+ url: 'artifacts',
+ parent: 'workspace.composition',
+ templateProvider: ['$templateCache', ($templateCache):string => {
+ return $templateCache.get(viewModelsHtmlBasePath + 'workspace/tabs/composition/tabs/artifacts/artifacts-view.html');
+ }],
+ controller: viewModelsModuleName + '.ResourceArtifactsViewModel'
+ }
+ );
+
+ $stateProvider.state(
+ 'workspace.composition.relations', {
+ url: 'relations',
+ parent: 'workspace.composition',
+ templateProvider: ['$templateCache', ($templateCache):string => {
+ return $templateCache.get(viewModelsHtmlBasePath + 'workspace/tabs/composition/tabs/relations/relations-view.html');
+ }],
+ controller: viewModelsModuleName + '.RelationsViewModel'
+ }
+ );
+
+ $stateProvider.state(
+ 'workspace.composition.relationships', {
+ url: 'relationships',
+ parent: 'workspace.composition',
+ templateProvider: ['$templateCache', ($templateCache):string => {
+ return $templateCache.get(viewModelsHtmlBasePath + 'resource-relationships/resource-relationships-view.html');
+ }],
+ controller: viewModelsModuleName + '.ResourceRelationshipsViewModel'
+ }
+ );
+
+ $stateProvider.state(
+ 'workspace.composition.structure', {
+ url: 'structure',
+ parent: 'workspace.composition',
+ templateProvider: ['$templateCache', ($templateCache):string => {
+ return $templateCache.get(viewModelsHtmlBasePath + 'workspace/tabs/composition/tabs/structure/structure-view.html');
+ }],
+ controller: viewModelsModuleName + '.StructureViewModel'
+ }
+ );
+ $stateProvider.state(
+ 'workspace.composition.lifecycle', {
+ url: 'lifecycle',
+ parent: 'workspace.composition',
+ templateProvider: ['$templateCache', ($templateCache):string => {
+ return $templateCache.get(viewModelsHtmlBasePath + 'workspace/tabs/composition/tabs/artifacts/artifacts-view.html');
+ }],
+ controller: viewModelsModuleName + '.ResourceArtifactsViewModel'
+ }
+ );
+
+ $stateProvider.state(
+ 'workspace.composition.api', {
+ url: 'api',
+ parent: 'workspace.composition',
+ templateProvider: ['$templateCache', ($templateCache):string => {
+ return $templateCache.get(viewModelsHtmlBasePath + 'workspace/tabs/composition/tabs/artifacts/artifacts-view.html');
+ }],
+ controller: viewModelsModuleName + '.ResourceArtifactsViewModel'
+ }
+ );
+ $stateProvider.state(
+ 'workspace.composition.deployment', {
+ url: 'deployment',
+ parent: 'workspace.composition',
+ templateProvider: ['$templateCache', ($templateCache):string => {
+ return $templateCache.get(viewModelsHtmlBasePath + 'workspace/tabs/composition/tabs/artifacts/artifacts-view.html');
+ }],
+ controller: viewModelsModuleName + '.ResourceArtifactsViewModel'
+ }
+ );
+
+ $stateProvider.state(
+ 'edit-resource', {
+ url: '/edit-resource/:id',
+ templateProvider: ['$templateCache', ($templateCache):string => {
+ return $templateCache.get(viewModelsHtmlBasePath + 'entity-handler/resource-form/resource-form-view.html');
+ }],
+ controller: viewModelsModuleName + '.ResourceFormViewModel'
+ }
+ );
+
+ $stateProvider.state(
+ 'edit-product', {
+ url: '/edit-product/:id',
+ templateProvider: ['$templateCache', ($templateCache):string => {
+ return $templateCache.get(viewModelsHtmlBasePath + 'entity-handler/product-form/product-form-view.html');
+ }],
+ controller: viewModelsModuleName + '.ProductFormViewModel'
+ }
+ );
+
+ $stateProvider.state(
+ 'adminDashboard', {
+ url: '/adminDashboard',
+ templateProvider: ['$templateCache', ($templateCache):string => {
+ return $templateCache.get(viewModelsHtmlBasePath + 'admin-dashboard/admin-dashboard-view.html');
+ }],
+ controller: viewModelsModuleName + '.AdminDashboardViewModel',
+ permissions: ['ADMIN']
+ }
+ );
+
+ $stateProvider.state(
+ 'onboardVendor', {
+ url: '/onboardVendor',
+ templateProvider: ['$templateCache', ($templateCache):string => {
+ return $templateCache.get(viewModelsHtmlBasePath + 'onboard-vendor/onboard-vendor-view.html');
+ }],
+ controller: viewModelsModuleName + '.OnboardVendorViewModel'//,
+ //resolve: {
+ // auth: ["$q", "Sdc.Services.UserResourceService", function ($q:any, userResourceService:Sdc.Services.IUserResourceClass) {
+ // let userInfo:Sdc.Services.IUserResource = userResourceService.getLoggedinUser();
+ // if (userInfo) {
+ // return $q.when(userInfo);
+ // } else {
+ // return $q.reject({authenticated: false});
+ // }
+ // }]
+ //}
+ }
+ );
+
+ $stateProvider.state(
+ 'catalog', {
+ url: '/catalog',
+ templateProvider: ['$templateCache', ($templateCache):string => {
+ return $templateCache.get(viewModelsHtmlBasePath + 'catalog/catalog-view.html');
+ }],
+ controller: viewModelsModuleName + '.CatalogViewModel',
+ resolve: {
+ auth: ["$q", "Sdc.Services.UserResourceService", function ($q:any, userResourceService:Sdc.Services.IUserResourceClass) {
+ let userInfo:Sdc.Services.IUserResource = userResourceService.getLoggedinUser();
+ if (userInfo) {
+ return $q.when(userInfo);
+ } else {
+ return $q.reject({authenticated: false});
+ }
+ }]
+ }
+ }
+ );
+
+ $stateProvider.state(
+ 'distribution', {
+ url: '/distribution',
+ templateProvider: ['$templateCache', ($templateCache):string => {
+ return $templateCache.get(viewModelsHtmlBasePath + 'distribution/distribution-view.html');
+ }],
+ controller: viewModelsModuleName + '.DistributionViewModel'
+ }
+ );
+
+ $stateProvider.state(
+ 'support', {
+ url: '/support',
+ templateProvider: ['$templateCache', ($templateCache):string => {
+ return $templateCache.get(viewModelsHtmlBasePath + 'support/support-view.html');
+ }],
+ controller: viewModelsModuleName + '.SupportViewModel'
+ }
+ );
+
+ $stateProvider.state(
+ 'error-403', {
+ url: '/error-403',
+ templateProvider: ['$templateCache', ($templateCache):string => {
+ return $templateCache.get(viewModelsHtmlBasePath + 'modals/error-modal/error-403-view.html');
+ }],
+ controller: viewModelsModuleName + '.ErrorViewModel'
+ }
+ );
+
+ tooltipsConfigProvider.options({
+
+ side:'bottom',
+ delay: '600',
+ class: 'tooltip-custom',
+ lazy:0,
+ try:0
+
+ });
+
+ }
+ ])
+ .run(['AngularJSBridge', (AngularJSBridge)=>{
+
+ }]);
+ appModule.value('ValidationPattern', /^[\s\w\&_.:-]{1,1024}$/);
+ appModule.value('PropertyNameValidationPattern', /^[a-zA-Z0-9_:-]{1,50}$/);// DE210977
+ appModule.value('TagValidationPattern', /^[\s\w_.-]{1,50}$/);
+ // appModule.value('VendorValidationPattern', /^[^?\\<>:"/|*]{1,25}$/);
+ appModule.value('VendorValidationPattern', /^[\x20-\x21\x23-\x29\x2B-\x2E\x30-\x39\x3B\x3D\x40-\x5B\x5D-\x7B\x7D-\xFF]{1,25}$/);
+ appModule.value('ContactIdValidationPattern', /^[\s\w-]{1,50}$/);
+ appModule.value('UserIdValidationPattern',/^[\s\w-]{1,50}$/);
+ appModule.value('ProjectCodeValidationPattern', /^[\s\w-]{1,50}$/);
+ appModule.value('LabelValidationPattern', /^[\sa-zA-Z0-9+-]{1,25}$/);
+ appModule.value('UrlValidationPattern', /^(https?|ftp):\/\/(((([A-Za-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([A-Za-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([A-Za-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([A-Za-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([A-Za-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([A-Za-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([A-Za-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([A-Za-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([A-Za-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([A-Za-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([A-Za-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([A-Za-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/);
+ appModule.value('IntegerValidationPattern', /^(([-+]?\d+)|([-+]?0x[0-9a-fA-F]+))$/);
+ appModule.value('IntegerNoLeadingZeroValidationPattern', /^(0|[-+]?[1-9][0-9]*|[-+]?0x[0-9a-fA-F]+|[-+]?0o[0-7]+)$/);
+ appModule.value('FloatValidationPattern', /^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?f?$/);
+ appModule.value('NumberValidationPattern', /^((([-+]?\d+)|([-+]?0x[0-9a-fA-F]+))|([-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?))$/);
+ appModule.value('KeyValidationPattern', /^[\s\w-]{1,50}$/);
+ appModule.value('CommentValidationPattern', /^[\u0000-\u00BF]*$/);
+ appModule.value('BooleanValidationPattern', /^([Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee])$/);
+
+
+ appModule.run([
+ '$http',
+ 'Sdc.Services.CacheService',
+ 'Sdc.Services.CookieService',
+ 'Sdc.Services.ConfigurationUiService',
+ 'Sdc.Services.UserResourceService',
+ 'Sdc.Services.CategoryResourceService',
+ 'Sdc.Services.SdcVersionService',
+ '$state',
+ '$rootScope',
+ '$location',
+ 'sdcConfig',
+ 'sdcMenu',
+ 'ModalsHandler',
+ 'Sdc.Services.EcompHeaderService',
+ 'LeftPaletteLoaderService',
+ ($http:ng.IHttpService,
+ cacheService:Services.CacheService,
+ cookieService:Services.CookieService,
+ ConfigurationUi:Services.ConfigurationUiService,
+ UserResourceClass:Services.IUserResourceClass,
+ categoryResourceService:Sdc.Services.ICategoryResourceClass,
+ sdcVersionService:Services.SdcVersionService,
+ $state:ng.ui.IStateService,
+ $rootScope:ng.IRootScopeService,
+ $location: ng.ILocationService,
+ sdcConfig: Models.IAppConfigurtaion,
+ sdcMenu: Models.IAppMenu,
+ ModalsHandler:Utils.ModalsHandler,
+ ecompHeaderService:Sdc.Services.EcompHeaderService,
+ LeftPaletteLoaderService:Services.Components.LeftPaletteLoaderService
+ ):void => {
+
+ //handle cache data - version
+ let initSdcVersion:Function = ():void => {
+
+ let onFailed = (response) => {
+ console.info('onFailed initSdcVersion', response);
+ cacheService.set('version', 'N/A');
+ };
+
+ let onSuccess = (version:any) => {
+ console.log("Version returned from server: " + version);
+ let tmpVerArray = version.version.split(".");
+ let ver = tmpVerArray[0] + "." + tmpVerArray[1] + "." + tmpVerArray[2];
+ cacheService.set('version', ver);
+ };
+
+ sdcVersionService.getVersion().then(onSuccess, onFailed);
+
+ };
+
+ let initEcompMenu:Function = (user):void => {
+ ecompHeaderService.getMenuItems(user.userId).then((data)=> {
+ $rootScope['menuItems'] = data;
+ });
+ };
+
+ let initConfigurationUi:Function = ():void => {
+ ConfigurationUi
+ .getConfigurationUi()
+ .then((configurationUi:any) => {
+ cacheService.set('UIConfiguration', configurationUi);
+ });
+ };
+
+ let initCategories:Function = ():void => {
+ let onError = ():void => {
+ console.log('Failed to init categories');
+ };
+
+ categoryResourceService.getAllCategories({types: 'services'}, (categories:Array<Models.IMainCategory>):void => {
+ cacheService.set('serviceCategories', categories);
+ }, onError);
+
+ categoryResourceService.getAllCategories({types: 'resources'}, (categories:Array<Models.IMainCategory>):void => {
+ cacheService.set('resourceCategories', categories);
+ }, onError);
+
+ categoryResourceService.getAllCategories({types: 'products'}, (categories:Array<Models.IMainCategory>):void => {
+ cacheService.set('productCategories', categories);
+ }, onError);
+ };
+
+ let initBaseUrl:Function = ():void => {
+ let env:string = sdcConfig.environment;
+ let baseUrl:string = $location.absUrl();
+ console.log("baseUrl="+baseUrl);
+
+ if(baseUrl) {
+ sdcConfig.api.baseUrl = baseUrl;
+
+ if(env==='prod'){
+ //let tempUrl = $location.absUrl().split('/sdc1/');
+ var mainUrl = location.protocol+'//'+location.hostname+(location.port ? ':'+location.port: '');
+ console.log("mainUrl="+mainUrl);
+ sdcConfig.api.root = mainUrl + sdcConfig.api.root;
+ console.log("sdcConfig.api.root="+sdcConfig.api.root);
+ }
+ }
+ };
+
+ let initLeftPalette:Function = ():void => {
+ LeftPaletteLoaderService.loadLeftPanel();
+ };
+
+ //handle http config
+ $http.defaults.withCredentials = true;
+ $http.defaults.headers.common[cookieService.getUserIdSuffix()] = cookieService.getUserId();
+
+ initBaseUrl();
+ initSdcVersion();
+ initConfigurationUi();
+ Utils.Constants.IMAGE_PATH = sdcConfig.imagesPath;
+ initLeftPalette();
+
+ //handle stateChangeStart
+ let internalDeregisterStateChangeStartWatcher:Function = ():void => {
+ if (deregisterStateChangeStartWatcher) {
+ deregisterStateChangeStartWatcher();
+ deregisterStateChangeStartWatcher = null;
+ }
+ };
+
+ let removeLoader:Function = ():void => {
+ $(".sdc-loading-page .main-loader").addClass("animated fadeOut");
+ $(".sdc-loading-page .caption1").addClass("animated fadeOut");
+ $(".sdc-loading-page .caption2").addClass("animated fadeOut");
+ window.setTimeout(():void=>{
+ $(".sdc-loading-page .main-loader").css("display", "none");
+ $(".sdc-loading-page .caption1").css("display", "none");
+ $(".sdc-loading-page .caption2").css("display", "none");
+ $(".sdc-loading-page").addClass("animated fadeOut");
+ },1000);
+ };
+
+ let onNavigateOut:Function = (toState, toParams):void => {
+ let onOk = ():void => {
+ $state.current.data.unsavedChanges = false;
+ $state.go(toState.name, toParams);
+ };
+
+ let data = sdcMenu.alertMessages.exitWithoutSaving;
+ //open notify to user if changes are not saved
+ ModalsHandler.openAlertModal(data.title, data.message).then(onOk);
+ };
+
+ let onStateChangeStart:Function = (event, toState, toParams, fromState, fromParams):void => {
+ console.info((new Date()).getTime());
+ console.info('$stateChangeStart', toState.name);
+ //set body class
+ $rootScope['bodyClass'] = 'default-class';
+ if(toState.data && toState.data.bodyClass){
+ $rootScope['bodyClass'] = toState.data.bodyClass;
+ }
+
+ // Workaround in case we are entering other state then workspace (user move to catalog)
+ // remove the changeComponentCsarVersion, user should open again the VSP list and select one for update.
+ if (toState.name.indexOf('workspace') === -1) {
+ if (cacheService.contains(Utils.Constants.CHANGE_COMPONENT_CSAR_VERSION_FLAG)){
+ cacheService.remove(Utils.Constants.CHANGE_COMPONENT_CSAR_VERSION_FLAG);
+ }
+ }
+
+ //saving last state to params , for breadcrumbs
+ if (['dashboard', 'catalog', 'onboardVendor'].indexOf(fromState.name) > -1) {
+ toParams.previousState = fromState.name;
+ } else {
+ toParams.previousState = fromParams.previousState;
+ }
+
+ if (toState.name !== 'error-403' && !UserResourceClass.getLoggedinUser()) {
+ internalDeregisterStateChangeStartWatcher();
+ event.preventDefault();
+
+ UserResourceClass.authorize().$promise.then((user:Services.IUserResource) => {
+ if(!doesUserHasAccess(toState, user)){
+ $state.go('error-403');
+ console.info('User has no permissions');
+ registerStateChangeStartWatcher();
+ return;
+ }
+ UserResourceClass.setLoggedinUser(user);
+ cacheService.set('user', user);
+ initCategories();
+ // initEcompMenu(user);
+ setTimeout(function () {
+
+ removeLoader();
+
+ // initCategories();
+ if(UserResourceClass.getLoggedinUser().role === 'ADMIN'){
+ // toState.name = "adminDashboard";
+ $state.go("adminDashboard", toParams);
+ registerStateChangeStartWatcher();
+ return;
+ }
+
+ // After user authorized init categories
+ window.setTimeout(():void=>{
+ //if ($state.current.name==='' || $state.current.name==='preloading') {
+ if ($state.current.name === "welcome" && sdcConfig.openSource) {
+ event.preventDefault();
+ $state.go("dashboard");
+ registerStateChangeStartWatcher();
+ }
+ else if ($state.current.name==='') {
+ $state.go(toState.name, toParams);
+ }
+
+ console.log("------$state.current.name=" + $state.current.name);
+ console.info('-----registerStateChangeStartWatcher authorize $stateChangeStart');
+ registerStateChangeStartWatcher();
+
+ },1000);
+
+ }, 0);
+
+ }, () => {
+ $state.go('error-403');
+
+ console.info('registerStateChangeStartWatcher error-403 $stateChangeStart');
+ registerStateChangeStartWatcher();
+ });
+ }
+ else if(UserResourceClass.getLoggedinUser()){
+ internalDeregisterStateChangeStartWatcher();
+ if(!doesUserHasAccess(toState, UserResourceClass.getLoggedinUser())){
+ event.preventDefault();
+ $state.go('error-403');
+ console.info('User has no permissions');
+ }
+ if(toState.name === "welcome") {
+ $state.go("dashboard");
+ }
+ registerStateChangeStartWatcher();
+ //if form is dirty and not save - notify to user
+ if(fromState.data && fromState.data.unsavedChanges && fromParams.id != toParams.id){
+ event.preventDefault();
+ onNavigateOut(toState, toParams);
+ }
+ }
+
+ };
+
+ let doesUserHasAccess:Function = (toState, user):boolean =>{
+
+ let isUserHasAccess = true;
+ if(toState.permissions && toState.permissions.length > 0) {
+ isUserHasAccess = _.includes(toState.permissions, user.role);
+ }
+ return isUserHasAccess;
+ };
+ let deregisterStateChangeStartWatcher:Function;
+
+ let registerStateChangeStartWatcher:Function = ():void => {
+ internalDeregisterStateChangeStartWatcher();
+ console.info('registerStateChangeStartWatcher $stateChangeStart');
+ deregisterStateChangeStartWatcher = $rootScope.$on('$stateChangeStart', (event, toState, toParams, fromState, fromParams):void => {
+ onStateChangeStart(event, toState, toParams, fromState, fromParams);
+ });
+ };
+
+ registerStateChangeStartWatcher();
+
+ }]);
+
+
+
+}
+
diff --git a/catalog-ui/app/scripts/directives/clicked-outside/clicked-outside-directive.ts b/catalog-ui/app/scripts/directives/clicked-outside/clicked-outside-directive.ts
new file mode 100644
index 0000000000..1b0af4ef99
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/clicked-outside/clicked-outside-directive.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=========================================================
+ */
+/// <reference path="../../references"/>
+module Sdc.Directives {
+
+ class ClickedOutsideModel{
+
+ private clickedOutsideContainerSelector: string;
+ private onClickedOutsideGetter: Function;
+ private clickedOutsideEnableGetter: Function;
+
+ constructor(clickedOutsideData: any) {
+ this.clickedOutsideContainerSelector = clickedOutsideData.clickedOutsideContainerSelector;
+ this.onClickedOutsideGetter = clickedOutsideData.onClickedOutsideGetter;
+ this.clickedOutsideEnableGetter = clickedOutsideData.clickedOutsideEnableGetter;
+ }
+
+ public getClickedOutsideContainerSelector = (): string => {
+ return this.clickedOutsideContainerSelector;
+ }
+
+ public getOnClickedOutsideGetter = (): Function => {
+ return this.onClickedOutsideGetter;
+ }
+
+ public getClickedOutsideEnableGetter = (): Function => {
+ return this.clickedOutsideEnableGetter;
+ }
+ }
+
+ export interface IClickedOutsideDirectiveScope extends ng.IScope{}
+
+ export class ClickedOutsideDirective implements ng.IDirective {
+
+ constructor(private $document: JQuery, private $parse: ng.IParseService) {}
+
+ restrict = 'A';
+
+ link = (scope:IClickedOutsideDirectiveScope, element: JQuery, attrs) => {
+
+ let container: HTMLElement;
+ let attrsAfterEval = scope.$eval(attrs.clickedOutside);
+ attrsAfterEval.onClickedOutsideGetter = this.$parse(attrsAfterEval.onClickedOutside);
+ attrsAfterEval.clickedOutsideEnableGetter = this.$parse(attrsAfterEval.clickedOutsideEnable);
+
+ let clickedOutsideModel: ClickedOutsideModel = new ClickedOutsideModel(attrsAfterEval);
+
+
+ let getContainer: Function = ():HTMLElement => {
+ if(!container){
+ let clickedOutsideContainerSelector: string = clickedOutsideModel.getClickedOutsideContainerSelector();
+ if(!angular.isUndefined(clickedOutsideContainerSelector) && clickedOutsideContainerSelector !== ''){
+ container = element.parents(clickedOutsideContainerSelector+':first')[0];
+ if(!container){
+ container = element[0];
+ }
+ }else{
+ container = element[0];
+ }
+ }
+ return container;
+ };
+
+
+ let onClickedOutside = (event: JQueryEventObject) => {
+ let containerDomElement: HTMLElement = getContainer();
+ let targetDomElementJq: JQuery = angular.element(event.target);
+ if(targetDomElementJq.hasClass('tooltip') || targetDomElementJq.parents('.tooltip:first').length){
+ return;
+ }
+ let targetDomElement: HTMLElement = targetDomElementJq[0];
+ if (!containerDomElement.contains(targetDomElement)){
+ scope.$apply(() => {
+ let onClickedOutsideGetter:Function = clickedOutsideModel.getOnClickedOutsideGetter();
+ onClickedOutsideGetter(scope);
+ });
+ }
+ };
+
+ let attachDomEvents: Function = () => {
+ this.$document.on('mousedown', onClickedOutside);
+ };
+
+ let detachDomEvents: Function = () => {
+ this.$document.off('mousedown', onClickedOutside);
+ };
+
+ //
+ scope.$on('$destroy', () => {
+ detachDomEvents();
+ });
+
+
+ scope.$watch(() => {
+ let clickedOutsideEnableGetter: Function = clickedOutsideModel.getClickedOutsideEnableGetter();
+ return clickedOutsideEnableGetter(scope);
+ }, (newValue: boolean) => {
+ if(newValue){
+ attachDomEvents();
+ return;
+ }
+ detachDomEvents();
+ });
+
+
+ }
+
+ public static factory = ($document: JQuery, $parse: ng.IParseService) => {
+ return new ClickedOutsideDirective($document, $parse);
+ }
+ }
+
+ ClickedOutsideDirective.factory.$inject = ['$document', '$parse'];
+}
diff --git a/catalog-ui/app/scripts/directives/custom-validation/custom-validation.ts b/catalog-ui/app/scripts/directives/custom-validation/custom-validation.ts
new file mode 100644
index 0000000000..e2f831ed53
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/custom-validation/custom-validation.ts
@@ -0,0 +1,55 @@
+/*-
+ * ============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.Directives {
+ 'use strict';
+
+
+ export interface ICustomValidationScope extends ng.IScope {
+ validationFunc: Function;
+ }
+
+ export class CustomValidationDirective implements ng.IDirective {
+
+ constructor() {}
+
+ require = 'ngModel';
+ restrict = 'A';
+
+ scope = {
+ validationFunc: '='
+ };
+
+ link = (scope:ICustomValidationScope, elem, attrs, ngModel) => {
+
+ ngModel.$validators.customValidation = (modelValue, viewValue) :boolean => {
+ return scope.validationFunc(viewValue);
+ };
+
+ };
+
+ public static factory = ()=> {
+ return new CustomValidationDirective();
+ };
+
+ }
+
+ CustomValidationDirective.factory.$inject = [];
+}
diff --git a/catalog-ui/app/scripts/directives/download-artifact/download-artifact.ts b/catalog-ui/app/scripts/directives/download-artifact/download-artifact.ts
new file mode 100644
index 0000000000..49bf14618c
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/download-artifact/download-artifact.ts
@@ -0,0 +1,141 @@
+/*-
+ * ============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.Directives {
+ 'use strict';
+
+ export class DOWNLOAD_CSS_CLASSES {
+ static DOWNLOAD_ICON = "table-download-btn tosca";
+ static LOADER_ICON = "tlv-loader small loader";
+ }
+
+ export interface IDownloadArtifactScope extends ng.IScope {
+ $window:any;
+ artifact: Models.ArtifactModel;
+ component: Models.Components.Component;
+ instance:boolean;
+ download: Function;
+ showLoader:boolean;
+ updateDownloadIcon:Function;
+ }
+
+ export class DownloadArtifactDirective implements ng.IDirective {
+
+ constructor(private $window:any,private cacheService:Services.CacheService, private EventListenerService:Services.EventListenerService, private fileUtils:Sdc.Utils.FileUtils) {}
+
+ scope = {
+ artifact: '=',
+ component: '=',
+ instance:'=',
+ showLoader:'='
+ };
+ restrict = 'EA';
+
+ link = (scope:IDownloadArtifactScope, element:any) => {
+ scope.$window = this.$window;
+
+ element.on("click", function() {
+ scope.download(scope.artifact);
+ });
+
+
+ let initDownloadLoader = ()=>{
+ //if the artifact is in a middle of download progress register form callBack & change icon from download to loader
+ if(scope.showLoader && this.cacheService.get(scope.artifact.uniqueId)){
+ this.EventListenerService.registerObserverCallback(Utils.Constants.EVENTS.DOWNLOAD_ARTIFACT_FINISH_EVENT + scope.artifact.uniqueId, scope.updateDownloadIcon);
+ window.setTimeout(():void => {
+ if(this.cacheService.get(scope.artifact.uniqueId)){
+ element[0].className = DOWNLOAD_CSS_CLASSES.LOADER_ICON;
+ }
+ },1000);
+
+ }
+ };
+
+ let setDownloadedFileLoader = ()=> {
+ if(scope.showLoader){
+ //set in cache service thet the artifact is in download progress
+ this.cacheService.set(scope.artifact.uniqueId,true);
+ initDownloadLoader();
+ }
+ };
+
+ let removeDownloadedFileLoader = ()=> {
+ if (scope.showLoader) {
+ this.cacheService.set(scope.artifact.uniqueId, false);
+ this.EventListenerService.notifyObservers(Utils.Constants.EVENTS.DOWNLOAD_ARTIFACT_FINISH_EVENT + scope.artifact.uniqueId);
+ }
+ };
+
+
+ //replace the loader to download icon
+ scope.updateDownloadIcon = () =>{
+ element[0].className = DOWNLOAD_CSS_CLASSES.DOWNLOAD_ICON;
+ };
+
+
+ initDownloadLoader();
+
+ scope.download = (artifact:Models.ArtifactModel):void => {
+
+ let onFaild = (response):void => {
+ console.info('onFaild', response);
+ removeDownloadedFileLoader();
+ };
+
+ let onSuccess = (data:Models.IFileDownload):void => {
+ downloadFile(data);
+ removeDownloadedFileLoader();
+ };
+
+ setDownloadedFileLoader();
+
+ if(scope.instance){
+ scope.component.downloadInstanceArtifact(artifact.uniqueId).then(onSuccess, onFaild);
+ }else {
+ scope.component.downloadArtifact(artifact.uniqueId).then(onSuccess, onFaild);
+ }
+ };
+
+ let downloadFile = (file:Models.IFileDownload):void => {
+ if (file){
+ let blob = this.fileUtils.base64toBlob(file.base64Contents,'');
+ let fileName = file.artifactName;
+ this.fileUtils.downloadFile(blob, fileName);
+ }
+ };
+
+ element.on('$destroy', ()=>{
+ //remove listener of download event
+ if(scope.artifact && scope.artifact.uniqueId){
+ this.EventListenerService.unRegisterObserver(Utils.Constants.EVENTS.DOWNLOAD_ARTIFACT_FINISH_EVENT + scope.artifact.uniqueId);
+ }
+ });
+
+ };
+
+ public static factory = ($window:any,cacheService:Sdc.Services.CacheService,EventListenerService:Services.EventListenerService, fileUtils:Sdc.Utils.FileUtils)=> {
+ return new DownloadArtifactDirective($window,cacheService,EventListenerService, fileUtils);
+ };
+
+ }
+
+ DownloadArtifactDirective.factory.$inject = ['$window', 'Sdc.Services.CacheService', 'EventListenerService', 'FileUtils'];
+}
diff --git a/catalog-ui/app/scripts/directives/ecomp-header/ecomp-header.html b/catalog-ui/app/scripts/directives/ecomp-header/ecomp-header.html
new file mode 100644
index 0000000000..e86f9df8b0
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/ecomp-header/ecomp-header.html
@@ -0,0 +1,73 @@
+<div class="sdc-ecomp-header-wrapper">
+
+ <div class="sdc-ecomp-header">
+
+ <div class="sdc-ecomp-logo-wrapper">
+ <a class="sdc-ecomp-header-title" data-ng-if="clickableLogo==='true'" data-ui-sref="dashboard"><span class="sdc-ecomp-logo"></span>ASDC</a>
+ <a class="sdc-ecomp-header-title" data-ng-if="clickableLogo==='false'"><span class="sdc-ecomp-logo"></span>ASDC</a>
+ <div class="sdc-ecomp-header-version"> v.{{version}}</div>
+ </div>
+
+ <div class="sdc-ecomp-menu">
+
+ <!-- Top level menu -->
+ <ul class="sdc-ecomp-menu-top-level">
+ <li class="sdc-ecomp-menu-top-level-item"
+ ng-repeat="item in megaMenuDataObject"
+ data-ng-if="item.children && item.children.length>0"
+ >
+ <span class="selected" data-ng-if="item.menuId === selectedTopMenu.menuId"></span>
+ <a href data-ng-click="firstMenuLevelClick(item.menuId)">{{item.text}}</a>
+
+ <!-- Second level menu -->
+ <div class="sdc-ecomp-menu-second-level" data-ng-if="item.menuId === selectedTopMenu.menuId" data-ng-mouseleave="subMenuLeaveAction(item.menuId)">
+ <ul>
+ <li class="sdc-ecomp-menu-second-level-item"
+ ng-repeat="subItem in selectedTopMenu.children | orderBy : 'column'"
+ aria-label="{{subItem.text}}"
+ data-ng-class="{'sdc-ecomp-menu-item-hover': menuItemHover===true}"
+ ng-mouseover="subMenuEnterAction(subItem.menuId)"
+ ng-mouseenter="menuItemHover=true"
+ ng-mouseleave="menuItemHover=false">
+
+ <!--<i ng-if="subItem.text=='Favorites'" id="favorite-star" class="icon-star favorites-icon-active"></i>-->
+
+ <a href title="{{subItem.text}}" data-ng-click="memuItemClick(subItem)">{{subItem.text}}</a>
+
+ <!-- Third and Four menu panel -->
+ <ul class="sdc-ecomp-menu-third-level" data-ng-if="subItem.menuId === selectedSubMenu.menuId && (selectedSubMenu.children && selectedSubMenu.children.length>0)">
+ <li class="sdc-ecomp-menu-third-level-item"
+ ng-repeat="thirdItem in selectedSubMenu.children | orderBy : 'column'"
+ aria-label="{{thirdItem.text}}">
+ <a class="sdc-ecomp-menu-third-level-title" href title="{{thirdItem.text}}" data-ng-click="memuItemClick(thirdItem)">{{thirdItem.text}}</a>
+ <span class="sdc-ecomp-menu-four-level-seperator"></span>
+ <ul class="sdc-ecomp-menu-four-level">
+ <li class="sdc-ecomp-menu-four-level-item" data-ng-repeat="fourItem in thirdItem.children | orderBy : 'column'">
+ <a href title="{{fourItem.text}}" data-ng-click="memuItemClick(fourItem)">{{fourItem.text}}</a>
+ </li>
+ </ul>
+ </li>
+ </ul>
+
+ </li>
+ </ul>
+ </div>
+
+ </li>
+
+ </ul>
+
+ </div>
+ <div class="sdc-ecomp-user-wrapper">
+ <span class="sdc-ecomp-user-icon"></span>
+ <div class="sdc-ecomp-user-details">
+ <div class="sdc-ecomp-user-details-name-role">
+ <div sdc-smart-tooltip class="sdc-ecomp-user-details-name" data-ng-bind="user.getName()"></div>
+ <div class="sdc-ecomp-user-details-role" data-ng-bind="user.getRoleToView()"></div>
+ </div>
+ <div class="sdc-ecomp-user-details-last-login" data-ng-show="user.getLastLogin()!==''">Last Login: {{user.getLastLogin() | date: 'MMM dd &nbsp; hh:mm a' : 'UTC'}}&nbsp;UTC</div>
+ </div>
+ </div>
+ </div>
+
+</div>
diff --git a/catalog-ui/app/scripts/directives/ecomp-header/ecomp-header.less b/catalog-ui/app/scripts/directives/ecomp-header/ecomp-header.less
new file mode 100644
index 0000000000..a6f7e9b5a2
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/ecomp-header/ecomp-header.less
@@ -0,0 +1,296 @@
+@first-level-color: #067ab4;
+@second-level-color: #f8f8f8;
+
+@first-level-height: 60px;
+@second-level-height: 60px;
+@third-four-level-height: 370px;
+
+@max-item-width: 250px;
+
+.sdc-ecomp-header-wrapper {
+
+ position: absolute;
+ top: 0;
+ right: 0;
+ left: 0;
+ z-index: 999;
+
+ ul {
+ margin: 0;
+ padding: 0;
+ }
+
+ .sdc-ecomp-header {
+
+ background-color: @first-level-color;
+ height: @first-level-height;
+ line-height: @first-level-height;
+ vertical-align: middle;
+ display: flex;
+ flex-direction: row;
+ padding: 0 20px;
+
+ .sdc-ecomp-menu {
+ flex-grow: 98;
+ }
+
+ }
+
+ /* LEVEL 1 */
+ .sdc-ecomp-menu-top-level {
+ list-style: none;
+
+ .sdc-ecomp-menu-top-level-item:first-child {
+ margin-left: 0;
+ }
+
+ .sdc-ecomp-menu-top-level-item {
+ display: inline-block;
+ margin: 0 20px;
+ position: relative;
+ a {
+ .p_14_m;
+ text-decoration: none;
+ }
+
+ span {
+ &.selected {
+ position: absolute;
+ bottom: 0;
+ width: 0;
+ height: 0;
+ border-left: 15px solid transparent;
+ border-right: 15px solid transparent;
+ border-bottom: 15px solid @second-level-color;
+ }
+ }
+ }
+ }
+
+ /* LEVEL 2 */
+ .sdc-ecomp-menu-second-level {
+ background-color: @second-level-color;
+ box-shadow: 0px 1px 0px 0px rgba(0, 0, 0, 0.33);
+ height: @second-level-height;
+ list-style: none;
+ line-height: @second-level-height;
+ vertical-align: middle;
+ display: flex;
+ flex-direction: row;
+ padding: 0 20px;
+ position: fixed;
+ left: 0;
+ right: 0;
+
+ .sdc-ecomp-menu-second-level-item:first-child {
+ margin-left: 0;
+ }
+
+ .sdc-ecomp-menu-second-level-item {
+ display: inline-block;
+ height: @second-level-height;
+ line-height: @second-level-height;
+
+ &.sdc-ecomp-menu-item-hover {
+ border-bottom: 4px solid #067ab4;
+ }
+
+ a {
+ .m_14_r;
+ text-decoration: none;
+ text-align:center;
+ padding: 0 20px;
+ display: block;
+
+ &:hover {
+ .s_14_r;
+ font-weight:bold;
+ }
+
+ &:active {
+ font-weight: bold;
+ }
+
+ /* fix jump when hovering text */
+ &:after {
+ display:block;
+ content:attr(title);
+ font-weight:bold;
+ height:1px;
+ color:transparent;
+ overflow:hidden;
+ visibility:hidden;
+ }
+ }
+
+ }
+
+ }
+
+ /* LEVEL 3 */
+ ul.sdc-ecomp-menu-third-level {
+ background-color: #ffffff;
+ box-shadow: 0px 0px 0px 1px rgba(0, 0, 0, 0.1);
+ min-height: @third-four-level-height;
+ list-style: none;
+
+ display: flex;
+ flex-direction: row;
+ /*flex-wrap: wrap;*/
+ position: fixed;
+ top: @first-level-height + @second-level-height;
+ left: 0;
+ right: 0;
+ padding: 0 40px;
+
+ li.sdc-ecomp-menu-third-level-item {
+ height: 40px;
+ line-height: 40px;
+ position: relative;
+ /*width: @max-item-width;*/
+ max-width: @max-item-width;
+
+ .sdc-ecomp-menu-third-level-title {
+ .m_14_m;
+ text-decoration: none;
+ text-align: left;
+ display: block;
+ padding: 0 20px;
+ line-height: 20px;
+ margin-top: 20px;
+ font-weight: bold;
+ cursor: pointer;
+ }
+
+ .sdc-ecomp-menu-four-level-seperator {
+ position: absolute;
+ border-right: solid 1px #e5e5e5;
+ height: @third-four-level-height - 20;
+ top: 10px;
+ }
+ }
+
+ li.sdc-ecomp-menu-third-level-item:first-child {
+ .sdc-ecomp-menu-four-level-seperator {
+ border: none;
+ }
+ }
+ }
+
+ /* LEVEL 4 */
+ ul.sdc-ecomp-menu-four-level {
+ display: flex;
+ flex-direction: column;
+ margin-top: 10px;
+
+ li.sdc-ecomp-menu-four-level-item {
+ display: block;
+ /*width: @max-item-width;*/
+ max-width: @max-item-width;
+ line-height: 20px;
+
+ a {
+ .m_14_r;
+ text-decoration: none;
+ text-align: left;
+ display: block;
+
+ &:hover {
+ .s_14_r;
+ font-weight: bold;
+ }
+ }
+ }
+ }
+
+}
+
+.sdc-ecomp-logo-wrapper {
+ flex-grow: 1;
+ .p_18_m;
+ white-space: nowrap;
+ min-width: 210px;
+ text-align: left;
+ position: relative;
+
+ .sdc-ecomp-logo {
+ background-image: url("images/att_logo_white.png");
+ background-repeat: no-repeat;
+ display: inline-block;
+ vertical-align: middle;
+ width: 55px;
+ height: 55px;
+ }
+
+ .sdc-ecomp-header-version {
+ .c_16;
+ .opacity(0.8);
+ position: absolute;
+ top: 34px;
+ line-height: 20px;
+ left: 56px;
+ }
+
+ a.sdc-ecomp-header-title {
+ .p_24;
+ text-decoration: none;
+ }
+}
+
+
+.sdc-ecomp-user-wrapper {
+
+ flex-grow: 1;
+ .p_14_m;
+ white-space: nowrap;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ height: @first-level-height;
+
+ .sdc-ecomp-user-icon {
+ margin-right: 20px;
+ .tlv-sprite;
+ .tlv-sprite.user;
+ }
+
+ .sdc-ecomp-user-details {
+ display: flex;
+ flex-direction: column;
+ }
+
+ .sdc-ecomp-user-details-name-role {
+ line-height: 20px;
+
+ .sdc-ecomp-user-details-name {
+ max-width: 160px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ vertical-align: bottom;
+
+ .bold;
+ display: inline-block;
+ }
+
+ .sdc-ecomp-user-details-role {
+ .bold;
+ display: inline-block;
+ margin-left: 6px;
+
+ &:before {
+ content: '';
+ margin-right: 8px;
+ border-left: 1px solid @color_m;
+ }
+ }
+ }
+
+ .sdc-ecomp-user-details-last-login {
+ .font-type._3;
+ display: block;
+ line-height: 20px;
+ height: 20px;
+ }
+
+}
diff --git a/catalog-ui/app/scripts/directives/ecomp-header/ecomp-header.ts b/catalog-ui/app/scripts/directives/ecomp-header/ecomp-header.ts
new file mode 100644
index 0000000000..7102c810ba
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/ecomp-header/ecomp-header.ts
@@ -0,0 +1,235 @@
+/*-
+ * ============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.Directives {
+ 'use strict';
+
+ export class MenuItem {
+ menuId:number;
+ column:number;
+ text:string;
+ parentMenuId:number;
+ url:string;
+ children:Array<MenuItem>
+ }
+
+ export interface IEcompHeaderDirectiveScope extends ng.IScope {
+ menuData:Array<MenuItem>;
+ version:string;
+ clickableLogo:string;
+ contactUsUrl:string;
+ getAccessUrl:string;
+ megaMenuDataObjectTemp:Array<any>;
+ megaMenuDataObject:Array<any>;
+
+ selectedTopMenu:MenuItem;
+ selectedSubMenu:MenuItem;
+
+ firstMenuLevelClick:Function;
+ subMenuEnterAction:Function;
+ subMenuLeaveAction:Function;
+
+ memuItemClick:Function;
+ user: Models.IUser;
+ }
+
+ export class EcompHeaderDirective implements ng.IDirective {
+
+ constructor(private $templateCache:ng.ITemplateCacheService,
+ private $http:ng.IHttpService,
+ private sdcConfig:Models.IAppConfigurtaion,
+ private UserResourceClass:Services.IUserResourceClass) {
+
+ }
+
+ scope = {
+ menuData: '=',
+ version: '@',
+ clickableLogo: '@?'
+ };
+
+ public replace = true;
+ public restrict = 'E';
+ public controller = EcompHeaderController;
+
+ template = ():string => {
+ return this.$templateCache.get('/app/scripts/directives/ecomp-header/ecomp-header.html');
+ };
+
+ link = ($scope:IEcompHeaderDirectiveScope, $elem:JQuery, attr:any) => {
+
+ if (!$scope.clickableLogo){
+ $scope.clickableLogo="true";
+ }
+
+ let findMenuItemById = (menuId):MenuItem => {
+ let selectedMenuItem:MenuItem = _.find($scope.menuData, (item:MenuItem)=>{
+ if (item.menuId === menuId){
+ return item;
+ }
+ });
+ return selectedMenuItem;
+ };
+
+ let initUser = ():void => {
+ let defaultUserId:string;
+ let user:Services.IUserResource = this.UserResourceClass.getLoggedinUser();
+ if (!user) {
+ defaultUserId = this.$http.defaults.headers.common[this.sdcConfig.cookie.userIdSuffix];
+ user = this.UserResourceClass.get({id: defaultUserId}, ():void => {
+ $scope.user = new Models.User(user);
+ });
+ } else {
+ $scope.user = new Models.User(user);
+ }
+ };
+
+ $scope.firstMenuLevelClick = (menuId:number):void => {
+ let selectedMenuItem:MenuItem = _.find($scope.megaMenuDataObjectTemp, (item:MenuItem)=>{
+ if (item.menuId === menuId){
+ return item;
+ }
+ });
+ if (selectedMenuItem) {
+ $scope.selectedTopMenu = selectedMenuItem;
+ //console.log("Selected menu item: " + selectedMenuItem.text);
+ }
+ };
+
+ $scope.subMenuEnterAction = (menuId:number):void => {
+ $scope.selectedSubMenu = findMenuItemById(menuId);
+ };
+
+ $scope.subMenuLeaveAction = (menuId:number):void => {
+ $scope.selectedTopMenu = undefined;
+ };
+
+ $scope.memuItemClick = (menuItem:MenuItem):void => {
+ if (menuItem.url){
+ window.location.href=menuItem.url;
+ } else {
+ console.log("Menu item: " + menuItem.text + " does not have defined URL!");
+ }
+ };
+
+ initUser();
+
+ };
+
+ public static factory = ($templateCache:ng.ITemplateCacheService,
+ $http:ng.IHttpService,
+ sdcConfig:Models.IAppConfigurtaion,
+ UserResourceClass:Services.IUserResourceClass)=> {
+ return new EcompHeaderDirective($templateCache, $http, sdcConfig, UserResourceClass);
+ };
+
+ }
+
+ export class EcompHeaderController {
+
+ messages:any;
+ getAttachId:Function;
+ render:any;
+ reRender:Function;
+ register:Function;
+ deregister:Function;
+ head:any;
+
+ static '$inject' = [
+ '$element',
+ '$scope',
+ '$attrs',
+ '$animate'
+ ];
+
+ constructor(private $element:JQuery,
+ private $scope:IEcompHeaderDirectiveScope,
+ private $attrs:ng.IAttributes,
+ private $animate:any) {
+
+ this.$scope = $scope;
+
+ this.$scope.$watch('menuData', (newVal, oldVal) => {
+ if (newVal){
+ this.init();
+ }
+ });
+
+ }
+
+ init = ():void => {
+
+ this.$scope.contactUsUrl = "https://wiki.web.att.com/display/EcompPortal/ECOMP+Portal+Home";
+ this.$scope.getAccessUrl = "http://ecomp-tlv-dev2.uccentral.att.com:8080/ecompportal/get_access";
+
+ let unflatten = ( array, parent?, tree? ) => {
+ tree = typeof tree !== 'undefined' ? tree : [];
+ parent = typeof parent !== 'undefined' ? parent : { menuId: null };
+ let children = _.filter( array, function(child){ return child["parentMenuId"] == parent.menuId; });
+ if( !_.isEmpty( children ) ){
+ if( parent.menuId === null ){
+ tree = children;
+ }else{
+ parent['children'] = children
+ }
+ _.each( children, function( child ){ unflatten( array, child ) } );
+ }
+ return tree;
+ };
+
+ let menuStructureConvert = (menuItems) => {
+ console.log(menuItems);
+ this.$scope.megaMenuDataObjectTemp = [
+ {
+ menuId: 1001,
+ text: "ECOMP",
+ children: menuItems
+ },
+ {
+ menuId: 1002,
+ text: "Help",
+ children: [
+ {
+ text:"Contact Us",
+ url: this.$scope.contactUsUrl
+ }]
+ }
+ ];
+
+ /*{
+ text:"Get Access",
+ url: this.$scope.getAccessUrl
+ }*/
+ return this.$scope.megaMenuDataObjectTemp;
+ };
+
+ let a = unflatten(this.$scope.menuData);
+ this.$scope.megaMenuDataObject = menuStructureConvert(a);
+ //console.log(this.$scope.megaMenuDataObject);
+ };
+ }
+
+ EcompHeaderDirective.factory.$inject = ['$templateCache', '$http', 'sdcConfig', 'Sdc.Services.UserResourceService'];
+
+}
+
+
+
+
diff --git a/catalog-ui/app/scripts/directives/edit-name-popover/edit-module-name-popover.html b/catalog-ui/app/scripts/directives/edit-name-popover/edit-module-name-popover.html
new file mode 100644
index 0000000000..d90c52d9a6
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/edit-name-popover/edit-module-name-popover.html
@@ -0,0 +1,31 @@
+<div>
+ <form name="popoverForm" class="w-sdc-form" data-tests-id="popover-form">
+ <span class="tlv-sprite tlv-x-btn close-popover-btn" data-tests-id="popover-x-button" ng-click="closePopover()"></span>
+ <div class="form-group">
+ <span class="popover-label" data-tests-id="popover-vfinstance-name" tooltips tooltip-content="{{module.vfInstanceName}}">{{module.vfInstanceName}}</span>
+ <div class="popover-input">
+ <div class="i-sdc-form-item" data-ng-class="{'error': validateField(popoverForm.heatName)}">
+ <input type="text"
+ data-ng-model="module.heatName"
+ data-ng-maxlength="50"
+ maxlength="50"
+ name="heatName"
+ placeholder="Enter Name"
+ autocomplete="off"
+ data-ng-init="onInit()"
+ data-ng-pattern="heatNameValidationPattern"
+ data-tests-id="popover-heat-name"
+ class="form-control"
+ />
+ </div>
+ <div class="input-error" data-ng-show="validateField(popoverForm.heatName)">
+ <span ng-show="popoverForm.heatName.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '50' }"></span>
+ <span ng-show="popoverForm.heatName.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span>
+ </div>
+ </div>
+ <span class="popover-label" data-tests-id="popover-module-name" tooltips tooltip-content="{{module.moduleName}}">{{module.moduleName}}</span>
+ <button class="tlv-btn grey popover-btn" data-tests-id="popover-close-button" data-ng-click="closePopover()">Cancel</button>
+ <button class="tlv-btn blue popover-btn" data-tests-id="popover-save-button" data-ng-class="{'disabled': (validateField(popoverForm.heatName) || popoverForm.heatName.$viewValue === originalName)}" data-ng-click="updateHeatName()">Save</button>
+ </div>
+ </form>
+</div>
diff --git a/catalog-ui/app/scripts/directives/edit-name-popover/edit-name-popover-directive.ts b/catalog-ui/app/scripts/directives/edit-name-popover/edit-name-popover-directive.ts
new file mode 100644
index 0000000000..a033df054b
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/edit-name-popover/edit-name-popover-directive.ts
@@ -0,0 +1,98 @@
+/*-
+ * ============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.Directives {
+ 'use strict';
+
+ export interface IEditNamePopoverDirectiveScope extends ng.IScope {
+ isOpen: boolean;
+ templateUrl: string;
+ module: any;
+ direction: string;
+ header: string;
+ heatNameValidationPattern:RegExp;
+ originalName:string;
+ onSave:any;
+
+ closePopover(isCancel:boolean):void;
+ validateField(field:any, originalName:string):boolean;
+ updateHeatName(heatName:string):void;
+ onInit():void;
+ }
+
+ export class EditNamePopoverDirective implements ng.IDirective {
+
+ constructor(private $templateCache:ng.ITemplateCacheService, private ValidationPattern:RegExp) {
+ }
+
+ scope = {
+ direction: "@?",
+ module: "=",
+ header: "@?",
+ onSave: "&"
+ };
+
+ link = (scope:IEditNamePopoverDirectiveScope) => {
+ if(!scope.direction) {
+ scope.direction = 'top';
+ }
+
+ scope.originalName = '';
+ scope.templateUrl = "/app/scripts/directives/edit-name-popover/edit-module-name-popover.html";
+ scope.isOpen = false;
+
+ scope.closePopover = (isCancel:boolean = true) => {
+ scope.isOpen = !scope.isOpen;
+
+ if(isCancel) {
+ scope.module.heatName = scope.originalName;
+ }
+ };
+
+ scope.onInit = () => {
+ scope.originalName = scope.module.heatName;
+ };
+
+ scope.validateField = (field:any):boolean => {
+ return !!(field && field.$dirty && field.$invalid);
+ };
+
+ scope.heatNameValidationPattern = this.ValidationPattern;
+
+ scope.updateHeatName = () => {
+ scope.closePopover(false);
+ scope.onSave();
+ }
+
+ };
+
+ replace = true;
+ restrict = 'E';
+ template = ():string => {
+ return this.$templateCache.get('/app/scripts/directives/edit-name-popover/edit-name-popover-view.html');
+ };
+
+ public static factory = ($templateCache:ng.ITemplateCacheService, ValidationPattern:RegExp)=> {
+ return new EditNamePopoverDirective($templateCache, ValidationPattern);
+ }
+ }
+
+ EditNamePopoverDirective.factory.$inject = ['$templateCache', 'ValidationPattern'];
+}
diff --git a/catalog-ui/app/scripts/directives/edit-name-popover/edit-name-popover-view.html b/catalog-ui/app/scripts/directives/edit-name-popover/edit-name-popover-view.html
new file mode 100644
index 0000000000..17beead6b3
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/edit-name-popover/edit-name-popover-view.html
@@ -0,0 +1 @@
+<div uib-popover-template="templateUrl" popover-title="{{header}}" popover-placement="{{direction}}" popover-is-open="isOpen" popover-append-to-body="true"></div>
diff --git a/catalog-ui/app/scripts/directives/edit-name-popover/edit-name-popover.less b/catalog-ui/app/scripts/directives/edit-name-popover/edit-name-popover.less
new file mode 100644
index 0000000000..3d76a352ce
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/edit-name-popover/edit-name-popover.less
@@ -0,0 +1,71 @@
+.popover {
+ max-width: none;
+ width: 310px;
+ left: initial !important;
+ right: 10px;
+ z-index: 100;
+ border-radius: 0;
+
+ .top > .arrow {
+ bottom: -8px !important;
+ }
+
+ .popover-title {
+ background-color: transparent;
+ border-bottom: 2px solid #40b7e4;
+ margin-left: 20px;
+ margin-right: 20px;
+ padding: 8px 14px 8px 0px;
+ font-family: @font-omnes-medium;
+ font-weight: bold;
+ }
+
+ .arrow {
+ left: 95% !important;
+ border-width: 7px;
+ bottom: -8px !important;
+ }
+
+ .popover-content {
+ width: inherit;
+ padding: 9px 20px;
+ }
+
+ .form-group {
+ margin-top: 9px;
+ }
+
+ .popover-btn {
+ float:right;
+ margin-left: 10px;
+ margin-bottom: 20px;
+ }
+
+ .close-popover-btn {
+ position: absolute;
+ top: 11px;
+ right: 25px;
+ }
+
+ .close-popover-btn:hover {
+ cursor: pointer;
+ }
+
+ .popover-input {
+ height: 47px;
+ margin-bottom:5px;
+ }
+
+ .popover-label {
+ text-overflow: ellipsis;
+ display: block;
+ white-space: nowrap;
+ margin-bottom: 10px;
+ font-family: @font-omnes-medium;
+ color: @main_color_l;
+ }
+
+ .w-sdc-form .i-sdc-form-item {
+ margin-bottom: 0px;
+ }
+}
diff --git a/catalog-ui/app/scripts/directives/elements/checkbox/checkbox.html b/catalog-ui/app/scripts/directives/elements/checkbox/checkbox.html
new file mode 100644
index 0000000000..daf2a89ac2
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/elements/checkbox/checkbox.html
@@ -0,0 +1,13 @@
+<label class="tlv-checkbox" ng-class="{'disabled' : disabled}">
+ <input id="{{elemId}}"
+ name="{{elemId}}"
+ class="tlv-checkbox-i"
+ type="checkbox"
+ checked=""
+ ng-disabled="disabled"
+ checklist-model="sdcChecklistModel"
+ checklist-value="sdcChecklistValue"
+ />
+
+ <span sdc-smart-tooltip class="tlv-checkbox-label" data-tests-id="{{elemId}}">{{text}}</span>
+</label>
diff --git a/catalog-ui/app/scripts/directives/elements/checkbox/checkbox.less b/catalog-ui/app/scripts/directives/elements/checkbox/checkbox.less
new file mode 100644
index 0000000000..0747a680a9
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/elements/checkbox/checkbox.less
@@ -0,0 +1,35 @@
+label.tlv-checkbox {
+ font-weight: normal;
+}
+
+/*
+input[type="checkbox"] {
+ display:none;
+}
+
+input[type="checkbox"] + label span {
+ margin-right: 6px;
+ vertical-align: text-bottom;
+ .sprite-new;
+ .checkbox_unchecked;
+ cursor:pointer;
+}
+
+input[type="checkbox"]:checked + label span {
+ vertical-align: text-bottom;
+ .sprite-new;
+ .checkbox_checked;
+}
+
+input[type="checkbox"]:focus + label span {
+ vertical-align: text-bottom;
+ .sprite-new;
+ .checkbox_focus;
+}
+
+input[type="checkbox"][disabled] + label{
+ vertical-align: text-bottom;
+ .sprite-new;
+ .checkbox_disabled;
+}
+*/
diff --git a/catalog-ui/app/scripts/directives/elements/checkbox/checkbox.ts b/catalog-ui/app/scripts/directives/elements/checkbox/checkbox.ts
new file mode 100644
index 0000000000..c45a9d92e1
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/elements/checkbox/checkbox.ts
@@ -0,0 +1,66 @@
+/*-
+ * ============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.Directives {
+ 'use strict';
+
+ export interface ICheckboxElementScope extends ng.IScope {
+ elemId: string;
+ text: string;
+ sdcChecklistModel: any;
+ sdcChecklistValue: string;
+ disabled:boolean;
+ }
+
+ export class CheckboxElementDirective implements ng.IDirective {
+
+ constructor(private $templateCache:ng.ITemplateCacheService,
+ private $filter:ng.IFilterService) {
+ }
+
+ public replace = true;
+ public restrict = 'E';
+ public transclude = false;
+
+ scope = {
+ elemId: '@',
+ text: '@',
+ disabled: '=',
+ sdcChecklistModel: '=',
+ sdcChecklistValue: '='
+ };
+
+ template = ():string => {
+ return this.$templateCache.get('/app/scripts/directives/elements/checkbox/checkbox.html');
+ };
+
+ public link = (scope:ICheckboxElementScope, $elem:ng.IAugmentedJQuery, $attrs:angular.IAttributes) => {
+ //$elem.removeAttr("id")
+ //console.log(scope.sdcChecklistValue);
+ };
+
+ public static factory = ($templateCache:ng.ITemplateCacheService, $filter:ng.IFilterService)=> {
+ return new CheckboxElementDirective($templateCache, $filter);
+ };
+
+ }
+
+ CheckboxElementDirective.factory.$inject = ['$templateCache', '$filter'];
+}
diff --git a/catalog-ui/app/scripts/directives/elements/radiobutton/radiobutton.html b/catalog-ui/app/scripts/directives/elements/radiobutton/radiobutton.html
new file mode 100644
index 0000000000..b31fae5d73
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/elements/radiobutton/radiobutton.html
@@ -0,0 +1,5 @@
+<div class="tlv-radio">
+ <input name="{{elemName}}" id="{{elemId}}" class="tlv-radio-i" type="radio" ng-model="sdcModel" ng-click="onValueChange()"
+ ng-disabled="disabled" value="{{value}}" />
+ <label for="{{elemId}}" sdc-smart-tooltip class="tlv-radio-label">{{text}}</label>
+</div>
diff --git a/catalog-ui/app/scripts/directives/elements/radiobutton/radiobutton.less b/catalog-ui/app/scripts/directives/elements/radiobutton/radiobutton.less
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/elements/radiobutton/radiobutton.less
diff --git a/catalog-ui/app/scripts/directives/elements/radiobutton/radiobutton.ts b/catalog-ui/app/scripts/directives/elements/radiobutton/radiobutton.ts
new file mode 100644
index 0000000000..9fe58d8f8b
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/elements/radiobutton/radiobutton.ts
@@ -0,0 +1,71 @@
+/*-
+ * ============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.Directives {
+ import INgModelController = angular.INgModelController;
+ 'use strict';
+
+ export interface IRadiobuttonElementScope extends ng.IScope {
+ elemId: string;
+ elemName: string;
+ text: string;
+ sdcModel: any;
+ value: any;
+ disabled: boolean;
+ onValueChange:Function;
+ }
+
+ export class RadiobuttonElementDirective implements ng.IDirective {
+
+ constructor(private $templateCache:ng.ITemplateCacheService,
+ private $filter:ng.IFilterService) {
+ }
+
+ public replace = true;
+ public restrict = 'E';
+ public transclude = false;
+
+ scope = {
+ elemId: '@',
+ elemName: '@',
+ text: '@',
+ sdcModel: '=',
+ value: '@',
+ disabled: '=',
+ onValueChange: '&'
+ };
+
+ template = ():string => {
+ return this.$templateCache.get('/app/scripts/directives/elements/radiobutton/radiobutton.html');
+ };
+
+ public link = (scope:IRadiobuttonElementScope, $elem:ng.IAugmentedJQuery, $attrs:angular.IAttributes) => {
+ //$elem.removeAttr("id")
+ //console.log(scope.sdcChecklistValue);
+ };
+
+ public static factory = ($templateCache:ng.ITemplateCacheService, $filter:ng.IFilterService)=> {
+ return new RadiobuttonElementDirective($templateCache, $filter);
+ };
+
+ }
+
+ RadiobuttonElementDirective.factory.$inject = ['$templateCache', '$filter'];
+}
diff --git a/catalog-ui/app/scripts/directives/ellipsis/ellipsis-directive.html b/catalog-ui/app/scripts/directives/ellipsis/ellipsis-directive.html
new file mode 100644
index 0000000000..31fa06adda
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/ellipsis/ellipsis-directive.html
@@ -0,0 +1,7 @@
+{{actualText}}
+
+<span class="ellipsis-directive-more-less"
+ data-ng-click="collapsed = !collapsed; toggleText()"
+ data-ng-hide="ellipsis.length <= maxChars">
+ {{collapsed ? "More" : "Less"}}
+</span>
diff --git a/catalog-ui/app/scripts/directives/ellipsis/ellipsis-directive.less b/catalog-ui/app/scripts/directives/ellipsis/ellipsis-directive.less
new file mode 100644
index 0000000000..d8dfdbb73b
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/ellipsis/ellipsis-directive.less
@@ -0,0 +1,10 @@
+.ellipsis-directive-more-less {
+ .a_9;
+ .bold;
+ .hand;
+ float: right;
+ margin-right: 17px;
+ line-height: 23px;
+ text-decoration: underline;
+ text-align: left;
+}
diff --git a/catalog-ui/app/scripts/directives/ellipsis/ellipsis-directive.ts b/catalog-ui/app/scripts/directives/ellipsis/ellipsis-directive.ts
new file mode 100644
index 0000000000..a5ccf248e0
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/ellipsis/ellipsis-directive.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.Directives {
+ 'use strict';
+ export interface IEllipsisScope extends ng.IScope {
+ ellipsis: string;
+ maxChars: number;
+ toggleText(): void;
+ collapsed: boolean;
+ actualText: string;
+
+ }
+
+ export class EllipsisDirective implements ng.IDirective {
+
+ constructor(private $templateCache: ng.ITemplateCacheService) {}
+
+ scope = {
+ ellipsis: '=',
+ moreClass: '@',
+ maxChars: '='
+ };
+
+ replace = false;
+ restrict = 'A';
+ template = (): string => {
+ return this.$templateCache.get('/app/scripts/directives/ellipsis/ellipsis-directive.html');
+ };
+
+ link = (scope:IEllipsisScope, $elem:any) => {
+
+
+ scope.collapsed = true;
+
+ scope.toggleText = (): void => {
+ if(scope.ellipsis && scope.collapsed) {
+ scope.actualText = scope.ellipsis.substr(0, scope.maxChars);
+ scope.actualText += scope.ellipsis.length > scope.maxChars ? '...' : '';
+ }
+ else
+ {
+ scope.actualText = scope.ellipsis;
+ }
+ };
+
+ scope.$watch("ellipsis", function(){
+ scope.collapsed = true;
+ scope.toggleText();
+ });
+
+
+
+ };
+
+ public static factory = ($templateCache: ng.ITemplateCacheService)=> {
+ return new EllipsisDirective($templateCache);
+ };
+
+ }
+
+ EllipsisDirective.factory.$inject = ['$templateCache'];
+}
diff --git a/catalog-ui/app/scripts/directives/events/on-last-repeat/on-last-repeat.ts b/catalog-ui/app/scripts/directives/events/on-last-repeat/on-last-repeat.ts
new file mode 100644
index 0000000000..0fb682d202
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/events/on-last-repeat/on-last-repeat.ts
@@ -0,0 +1,61 @@
+/*-
+ * ============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.Directives {
+ 'use strict';
+
+ /**
+ * Usage:
+ * In data-ng-repeat html: <ol ng-repeat="record in records" on-last-repeat>
+ * In the controller, catch the last repeat:
+ * $scope.$on('onRepeatLast', function(scope, element, attrs){
+ * //work your magic
+ * });
+ */
+ export interface IOnLastRepeatDirectiveScope extends ng.IScope {
+ $last:any;
+ }
+
+ export class OnLastRepeatDirective implements ng.IDirective {
+
+ constructor() {}
+
+ scope = {};
+
+ restrict = 'AE';
+ replace = true;
+
+ link = (scope:IOnLastRepeatDirectiveScope, element:any, attrs:any) => {
+ let s:any = scope.$parent; // repeat scope
+ if (s.$last) {
+ setTimeout(function(){
+ s.$emit('onRepeatLast', element, attrs);
+ }, 1);
+ }
+ };
+
+ public static factory = ()=> {
+ return new OnLastRepeatDirective();
+ };
+
+ }
+
+ OnLastRepeatDirective.factory.$inject = [];
+}
diff --git a/catalog-ui/app/scripts/directives/file-opener/file-opener.html b/catalog-ui/app/scripts/directives/file-opener/file-opener.html
new file mode 100644
index 0000000000..38f82554e9
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/file-opener/file-opener.html
@@ -0,0 +1,3 @@
+<div>
+<input class="i-sdc-dashboard-item-upload-input" type="file" data-tests-id="file-{{testsId}}" data-ng-model="importFile" base-sixty-four-input data-ng-change="onFileSelect()" accept="{{getExtensionsWithDot()}}"/>
+</div>
diff --git a/catalog-ui/app/scripts/directives/file-opener/file-opener.ts b/catalog-ui/app/scripts/directives/file-opener/file-opener.ts
new file mode 100644
index 0000000000..b7e3e1804c
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/file-opener/file-opener.ts
@@ -0,0 +1,77 @@
+/*-
+ * ============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.Directives {
+ 'use strict';
+
+ export interface IFileOpenerScope extends ng.IScope {
+ importFile:any;
+ testsId:any;
+ extensions:string;
+
+ onFileSelect():void;
+ onFileUpload(file:any):void;
+ getExtensionsWithDot():string;
+ }
+
+ export class FileOpenerDirective implements ng.IDirective {
+
+ constructor(private $templateCache:ng.ITemplateCacheService,
+ private $compile:ng.ICompileService) {
+ }
+
+ scope = {
+ onFileUpload: '&',
+ testsId: '@',
+ extensions: '@'
+ };
+
+ restrict = 'AE';
+ replace = true;
+ template = ():string => {
+ return this.$templateCache.get('/app/scripts/directives/file-opener/file-opener.html');
+ };
+
+ link = (scope:IFileOpenerScope, element:any) => {
+
+ scope.onFileSelect = () => {
+ scope.onFileUpload({file: scope.importFile});
+ element.html(this.$templateCache.get('/app/scripts/directives/file-opener/file-opener.html'));
+ this.$compile(element.contents())(scope);
+ };
+
+ scope.getExtensionsWithDot = ():string => {
+ let ret = [];
+ _.each(scope.extensions.split(','), function(item){
+ ret.push("." + item.toString());
+ });
+ return ret.join(",");
+ };
+
+ };
+
+ public static factory = ($templateCache:ng.ITemplateCacheService, $compile:ng.ICompileService)=> {
+ return new FileOpenerDirective($templateCache, $compile);
+ };
+
+ }
+
+ FileOpenerDirective.factory.$inject = ['$templateCache', '$compile'];
+}
diff --git a/catalog-ui/app/scripts/directives/file-type/file-type.ts b/catalog-ui/app/scripts/directives/file-type/file-type.ts
new file mode 100644
index 0000000000..e7dee17960
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/file-type/file-type.ts
@@ -0,0 +1,66 @@
+/*-
+ * ============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.Directives {
+ 'use strict';
+
+ export class FileTypeDirective implements ng.IDirective {
+
+ constructor() {}
+
+ require = 'ngModel';
+
+ link = (scope, elem, attrs, ngModel) => {
+
+ let typesToApprove = "";
+
+ attrs.$observe('fileType', (val:string) => {
+ typesToApprove = val;
+ validate(ngModel.$viewValue);
+ });
+
+ let validate: Function = function (value) {
+ let fileName:string = elem.val(), valid:boolean = true;
+
+ if (fileName && value && typesToApprove) {
+ let extension:string = fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase();
+ valid = typesToApprove.split(',').indexOf(extension) > -1;
+ }
+
+ ngModel.$setValidity('filetype', valid);
+ if(!value) {
+ ngModel.$setPristine();
+ }
+ return value;
+ };
+
+ //For DOM -> model validation
+ ngModel.$parsers.unshift(validate);
+
+ };
+
+ public static factory = ()=> {
+ return new FileTypeDirective();
+ };
+
+ }
+
+ FileTypeDirective.factory.$inject = [];
+}
diff --git a/catalog-ui/app/scripts/directives/file-upload/file-upload.html b/catalog-ui/app/scripts/directives/file-upload/file-upload.html
new file mode 100644
index 0000000000..7cbc8d25f3
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/file-upload/file-upload.html
@@ -0,0 +1,22 @@
+<div class="i-sdc-form-item i-sdc-form-file-upload">
+ <span class="i-sdc-form-file-name" data-tests-id="filename">{{(fileModel && fileModel.filename) || defaultText}}</span>
+ <div class="i-sdc-form-file-upload-x-btn" ng-click="cancel()" data-ng-show="fileModel.filename && fileModel.filename!=='' && elementDisabled!=='true'"></div>
+ <label class="i-sdc-form-file-upload-label">
+ <input
+ type="file"
+ name="{{elementName}}"
+ ng-model="myFileModel"
+ base-sixty-four-input
+ accept="{{getExtensionsWithDot()}}"
+ file-type="{{extensions}}"
+ data-ng-change="onFileChange()"
+ onchange="angular.element(this).scope().setEmptyError(this)"
+ onclick="angular.element(this).scope().onFileClick(this)"
+ data-ng-required="{{elementRequired}}"
+ data-ng-disabled="elementDisabled==='true'"
+ data-tests-id="browseButton"
+ maxsize="10240"
+ />
+ <div class="file-upload-browse-btn" data-ng-class="{'disabled':elementDisabled==='true'}">Browse</div>
+ </label>
+</div>
diff --git a/catalog-ui/app/scripts/directives/file-upload/file-upload.less b/catalog-ui/app/scripts/directives/file-upload/file-upload.less
new file mode 100644
index 0000000000..1c4b010853
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/file-upload/file-upload.less
@@ -0,0 +1,75 @@
+.i-sdc-form-file-upload {
+
+ display: flex;
+ margin-top: 0;
+ width: 100%;
+ .p_1;
+ .bg_c;
+ .border-radius(2px);
+ border: solid 1px @border_color_f;
+ height: 30px;
+
+ input[type="file"] {
+ cursor: inherit;
+ display: block;
+ filter: alpha(opacity=0);
+ width: 100px;
+ height: 30px;
+ opacity: 0;
+ position: absolute;
+ right: 0;
+ text-align: right;
+ top: 0;
+ }
+
+ .i-sdc-form-file-name{
+ flex-grow: 999;
+ text-align: left;
+ padding: 3px 10px;
+ opacity: 0.6;
+ width: 80%;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+
+ }
+
+ .i-sdc-form-file-upload-x-btn{
+ flex-grow: 1;
+ .sprite;
+ .sprite.small-x-btn-black;
+ cursor: pointer;
+ top: 10px;
+ right: 9px;
+ width: 10px;
+ position: relative;
+ }
+ .i-sdc-form-file-upload-label {
+ float: right;
+ width: 100px;
+ height: 100%;
+ .bg_n;
+ .b_9;
+
+ .file-upload-browse-btn {
+ .noselect;
+ padding: 4px 6px;
+ cursor: pointer;
+ z-index: 999;
+ position: absolute;
+ width: 100px;
+ height: 28px;
+ text-align: center;
+
+ &.disabled {
+ cursor: default;
+ }
+ }
+ }
+
+ &.error {
+ border-color: #da1f3d;
+ outline: none;
+ box-sizing: border-box;
+ }
+}
diff --git a/catalog-ui/app/scripts/directives/file-upload/file-upload.ts b/catalog-ui/app/scripts/directives/file-upload/file-upload.ts
new file mode 100644
index 0000000000..16db3e7e21
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/file-upload/file-upload.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 1/27/2016.
+ */
+/// <reference path="../../references"/>
+module Sdc.Directives {
+ 'use strict';
+
+ export class FileUploadModel {
+ filetype: string;
+ filename: string;
+ filesize: number;
+ base64: string;
+ }
+
+ export interface IFileUploadScope extends ng.IScope {
+ fileModel: FileUploadModel;
+ formElement:ng.IFormController;
+ extensions: string;
+ elementDisabled: string;
+ elementName: string;
+ elementRequired: string;
+ myFileModel: any; // From the ng bind to <input type=file
+ defaultText: string;
+ onFileChangedInDirective:Function;
+
+ getExtensionsWithDot():string;
+ onFileChange():void
+ onFileClick(element:any):void;
+ setEmptyError(element):void;
+ validateField(field:any):boolean;
+ cancel():void;
+ }
+
+
+ export class FileUploadDirective implements ng.IDirective {
+
+ constructor(private $templateCache:ng.ITemplateCacheService, private sdcConfig:Models.IAppConfigurtaion) {
+ }
+
+ scope = {
+ fileModel: '=',
+ formElement: '=',
+ extensions: '@',
+ elementDisabled: '@',
+ elementName: '@',
+ elementRequired: '@',
+ onFileChangedInDirective: '=?',
+ defaultText: '=',
+ };
+
+ restrict = 'E';
+ replace = true;
+ template = ():string => {
+ return this.$templateCache.get('/app/scripts/directives/file-upload/file-upload.html');
+ };
+
+ link = (scope:IFileUploadScope, element:any, $attr:any) => {
+
+ // In case the browse has filename, set it valid.
+ // When editing artifact the file is not sent again, so if we have filename I do not want to show error.
+ if (scope.fileModel && scope.fileModel.filename && scope.fileModel.filename!==''){
+ scope.formElement[scope.elementName].$setValidity('required', true);
+ }
+
+ scope.getExtensionsWithDot = ():string => {
+ let ret = [];
+ if(scope.extensions) {
+ _.each(scope.extensions.split(','), function (item) {
+ ret.push("." + item.toString());
+ });
+ }
+ return ret.join(",");
+ };
+
+ scope.onFileChange = ():void => {
+ if (scope.onFileChangedInDirective) {
+ scope.onFileChangedInDirective();
+ }
+ if (scope.myFileModel) {
+ scope.fileModel = scope.myFileModel;
+ scope.formElement[scope.elementName].$setValidity('required', true);
+ }
+ };
+
+ scope.setEmptyError = (element):void => {
+ if(element.files[0].size){
+ scope.formElement[scope.elementName].$setValidity('emptyFile', true);
+ }else{
+ scope.formElement[scope.elementName].$setValidity('emptyFile', false);
+ scope.fileModel = undefined;
+ }
+
+ };
+
+ // Workaround, in case user select a file then cancel (X) then select the file again, the event onChange is not fired.
+ // This is a workaround to fix this issue.
+ scope.onFileClick = (element:any):void => {
+ element.value = null;
+ };
+
+ scope.cancel = ():void => {
+ scope.fileModel.filename = '';
+ scope.formElement[scope.elementName].$pristine;
+ scope.formElement[scope.elementName].$setValidity('required', false);
+ }
+ };
+
+ public static factory = ($templateCache:ng.ITemplateCacheService, sdcConfig:Models.IAppConfigurtaion)=> {
+ return new FileUploadDirective($templateCache, sdcConfig);
+ };
+
+ }
+
+ FileUploadDirective.factory.$inject = ['$templateCache', 'sdcConfig'];
+}
diff --git a/catalog-ui/app/scripts/directives/graphs-v2/common/common-graph-utils.ts b/catalog-ui/app/scripts/directives/graphs-v2/common/common-graph-utils.ts
new file mode 100644
index 0000000000..e01e455e93
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/graphs-v2/common/common-graph-utils.ts
@@ -0,0 +1,361 @@
+/**
+ * Created by obarda on 12/21/2016.
+ */
+/**
+ * Created by obarda on 12/13/2016.
+ */
+/// <reference path="../../../references"/>
+module Sdc.Graph.Utils {
+
+ export class CommonGraphUtils {
+
+ constructor(private NodesFactory:Sdc.Utils.NodesFactory, private LinksFactory:Sdc.Utils.LinksFactory) {
+
+ }
+
+ public safeApply = (scope:ng.IScope, fn:any) => { //todo remove to general utils
+ let phase = scope.$root.$$phase;
+ if (phase == '$apply' || phase == '$digest') {
+ if (fn && (typeof(fn) === 'function')) {
+ fn();
+ }
+ } else {
+ scope.$apply(fn);
+ }
+ };
+
+ /**
+ * Draw node on the graph
+ * @param cy
+ * @param compositionGraphNode
+ * @param position
+ * @returns {CollectionElements}
+ */
+ public addNodeToGraph(cy:Cy.Instance, compositionGraphNode:Models.Graph.CommonNodeBase, position?:Cy.Position):Cy.CollectionElements {
+
+ var node = cy.add(<Cy.ElementDefinition> {
+ group: 'nodes',
+ position: position,
+ data: compositionGraphNode,
+ classes: compositionGraphNode.classes
+ });
+
+ if(!node.data().isUcpe) { //ucpe should not have tooltip
+ this.initNodeTooltip(node);
+ }
+ return node;
+ };
+
+ /**
+ * The function will create a component instance node by the componentInstance position.
+ * If the node is UCPE the function will create all cp lan&wan for the ucpe
+ * @param cy
+ * @param compositionGraphNode
+ * @returns {Cy.CollectionElements}
+ */
+ public addComponentInstanceNodeToGraph(cy:Cy.Instance, compositionGraphNode:Models.Graph.CompositionCiNodeBase):Cy.CollectionElements {
+
+ let nodePosition = {
+ x: +compositionGraphNode.componentInstance.posX,
+ y: +compositionGraphNode.componentInstance.posY
+ };
+
+ let node = this.addNodeToGraph(cy, compositionGraphNode, nodePosition);
+ if (compositionGraphNode.isUcpe) {
+ this.createUcpeCpNodes(cy, node);
+ }
+ return node;
+ };
+
+ /**
+ * This function will create CP_WAN & CP_LAN for the UCPE. this is a special node on the group that will behave like ports on the ucpe
+ * @param cy
+ * @param ucpeGraphNode
+ */
+ private createUcpeCpNodes(cy:Cy.Instance, ucpeGraphNode:Cy.CollectionNodes):void {
+
+ let requirementsArray:Array<any> = ucpeGraphNode.data().componentInstance.requirements["tosca.capabilities.Node"];
+ //show only LAN or WAN requirements
+ requirementsArray = _.reject(requirementsArray, (requirement:any) => {
+ let name:string = requirement.ownerName.toLowerCase();
+ return name.indexOf('lan') === -1 && name.indexOf('wan') === -1;
+ });
+ requirementsArray.sort(function (a, b) {
+ let nameA = a.ownerName.toLowerCase().match(/[^ ]+/)[0];
+ let nameB = b.ownerName.toLowerCase().match(/[^ ]+/)[0];
+ let numA = _.last(a.ownerName.toLowerCase().split(' '));
+ let numB = _.last(b.ownerName.toLowerCase().split(' '));
+
+ if (nameA === nameB) return numA > numB ? 1 : -1;
+ return nameA < nameB ? 1 : -1;
+ });
+ let position = angular.copy(ucpeGraphNode.boundingbox());
+ //add CP nodes to group
+ let topCps:number = 0;
+ for (let i = 0; i < requirementsArray.length; i++) {
+
+ let cpNode = this.NodesFactory.createUcpeCpNode(angular.copy(ucpeGraphNode.data().componentInstance));
+ cpNode.componentInstance.capabilities = requirementsArray[i];
+ cpNode.id = requirementsArray[i].ownerId;
+ cpNode.group = ucpeGraphNode.data().componentInstance.uniqueId;
+ cpNode.name = requirementsArray[i].ownerName; //for tooltip
+ cpNode.displayName = requirementsArray[i].ownerName;
+ cpNode.displayName = cpNode.displayName.length > 5 ? cpNode.displayName.substring(0, 5) + '...' : cpNode.displayName;
+
+
+ if (cpNode.name.toLowerCase().indexOf('lan') > -1) {
+ cpNode.textPosition = "top";
+ cpNode.componentInstance.posX = position.x1 + (i * 90) - (topCps * 90) + 53;
+ cpNode.componentInstance.posY = position.y1 + 400 + 27;
+ } else {
+ cpNode.textPosition = "bottom";
+ cpNode.componentInstance.posX = position.x1 + (topCps * 90) + 53;
+ cpNode.componentInstance.posY = position.y1 + 27;
+ topCps++;
+ }
+ let cyCpNode = this.addComponentInstanceNodeToGraph(cy, cpNode);
+ cyCpNode.lock();
+ }
+ };
+
+ /**
+ *
+ * @param nodes - all nodes in graph in order to find the edge connecting the two nodes
+ * @param fromNodeId
+ * @param toNodeId
+ * @returns {boolean} true/false if the edge is certified (from node and to node are certified)
+ */
+ public isRelationCertified(nodes:Cy.CollectionNodes, fromNodeId:string, toNodeId:string):boolean {
+ let resourceTemp = _.filter(nodes, function (node:Cy.CollectionFirst) {
+ return node.data().id === fromNodeId || node.data().id === toNodeId;
+ });
+ let certified:boolean = true;
+
+ _.forEach(resourceTemp, (item) => {
+ certified = certified && item.data().certified;
+ });
+
+ return certified;
+ }
+
+ /**
+ * Add link to graph - only draw the link
+ * @param cy
+ * @param link
+ */
+ public insertLinkToGraph = (cy:Cy.Instance, link:Models.CompositionCiLinkBase) => {
+
+ if (!this.isRelationCertified(cy.nodes(), link.source, link.target)) {
+ link.classes = 'not-certified-link';
+ }
+ cy.add({
+ group: 'edges',
+ data: link,
+ classes: link.classes
+ });
+
+ };
+
+ /**
+ * go over the relations and draw links on the graph
+ * @param cy
+ * @param instancesRelations
+ */
+ public initGraphLinks(cy:Cy.Instance, instancesRelations:Array<Models.RelationshipModel>) {
+
+ if (instancesRelations) {
+ _.forEach(instancesRelations, (relationshipModel:Models.RelationshipModel) => {
+ _.forEach(relationshipModel.relationships, (relationship:Models.Relationship) => {
+ let linkToCreate = this.LinksFactory.createGraphLink(cy, relationshipModel, relationship);
+ this.insertLinkToGraph(cy, linkToCreate);
+ });
+ });
+ }
+ }
+
+ /**
+ * Determine which nodes are in the UCPE and set child data for them.
+ * @param cy
+ */
+ public initUcpeChildren(cy:Cy.Instance){
+ let ucpe:Cy.CollectionNodes = cy.nodes('[?isUcpe]'); // Get ucpe on graph if exist
+ _.each(cy.edges('.ucpe-host-link'), (link)=>{
+
+ let ucpeChild:Cy.CollectionNodes = (link.source().id() == ucpe.id())? link.target() : link.source();
+ this.initUcpeChildData(ucpeChild, ucpe);
+
+ //vls dont have ucpe-host-link connection, so need to find them and iterate separately
+ let connectedVLs = ucpeChild.connectedEdges().connectedNodes('.vl-node');
+ _.forEach(connectedVLs, (vl)=>{ //all connected vls must be UCPE children because not allowed to connect to a VL outside of the UCPE
+ this.initUcpeChildData(vl, ucpe);
+ });
+ });
+ }
+
+ /**
+ * Set properties for nodes contained by the UCPE
+ * @param childNode- node contained in UCPE
+ * @param ucpe- ucpe container node
+ */
+ public initUcpeChildData(childNode:Cy.CollectionNodes, ucpe:Cy.CollectionNodes){
+
+ if(!childNode.data('isInsideGroup')){
+ this.updateUcpeChildPosition(childNode, ucpe);
+ childNode.data({isInsideGroup: true});
+ }
+
+ }
+
+ /**
+ * Updates UCPE child node offset, which allows child nodes to be dragged in synchronization with ucpe
+ * @param childNode- node contained in UCPE
+ * @param ucpe- ucpe container node
+ */
+ public updateUcpeChildPosition(childNode:Cy.CollectionNodes, ucpe:Cy.CollectionNodes){
+ let childPos:Cy.Position = childNode.relativePosition();
+ let ucpePos:Cy.Position = ucpe.relativePosition();
+ let offset:Cy.Position = {
+ x: childPos.x - ucpePos.x,
+ y: childPos.y - ucpePos.y
+ };
+ childNode.data("ucpeOffset", offset);
+ }
+
+ /**
+ * Removes ucpe-child properties from the node
+ * @param childNode- node being removed from UCPE
+ */
+ public removeUcpeChildData(childNode:Cy.CollectionNodes){
+ childNode.removeData("ucpeOffset");
+ childNode.data({isInsideGroup: false});
+
+ }
+
+
+ public HTMLCoordsToCytoscapeCoords(cytoscapeBoundingBox:Cy.Extent, mousePos:Cy.Position):Cy.Position {
+ return {x: mousePos.x + cytoscapeBoundingBox.x1, y: mousePos.y + cytoscapeBoundingBox.y1}
+ };
+
+
+ public getCytoscapeNodePosition = (cy: Cy.Instance, event:IDragDropEvent):Cy.Position => {
+ let targetOffset = $(event.target).offset();
+ let x = event.pageX - targetOffset.left;
+ let y = event.pageY - targetOffset.top;
+
+ return this.HTMLCoordsToCytoscapeCoords(cy.extent(), {
+ x: x,
+ y: y
+ });
+ };
+
+
+ public getNodePosition(node:Cy.CollectionFirstNode):Cy.Position{
+ let nodePosition = node.relativePoint();
+ if(node.data().isUcpe){ //UCPEs use bounding box and not relative point.
+ nodePosition = {x: node.boundingbox().x1, y: node.boundingbox().y1};
+ }
+
+ return nodePosition;
+ }
+
+ /**
+ * return true/false if first node contains in second - this used in order to verify is node is entirely inside ucpe
+ * @param firstBox
+ * @param secondBox
+ * @returns {boolean}
+ */
+ public isFirstBoxContainsInSecondBox(firstBox:Cy.BoundingBox, secondBox:Cy.BoundingBox) {
+
+ return firstBox.x1 > secondBox.x1 && firstBox.x2 < secondBox.x2 && firstBox.y1 > secondBox.y1 && firstBox.y2 < secondBox.y2;
+
+ };
+
+
+ /**
+ * Check if node node bounds position is inside any ucpe on graph, and return the ucpe
+ * @param {diagram} the diagram.
+ * @param {nodeActualBounds} the actual bound position of the node.
+ * @return the ucpe if found else return null
+ */
+ public isInUcpe = (cy: Cy.Instance, nodeBounds: Cy.BoundingBox): Cy.CollectionElements => {
+
+ let ucpeNodes = cy.nodes('[?isUcpe]').filterFn((ucpeNode) => {
+ return this.isFirstBoxContainsInSecondBox(nodeBounds, ucpeNode.boundingbox());
+ });
+ return ucpeNodes;
+ };
+
+ /**
+ *
+ * @param cy
+ * @param node
+ * @returns {Array}
+ */
+ public getLinkableNodes(cy:Cy.Instance, node:Cy.CollectionFirstNode):Array<Models.Graph.CompositionCiNodeBase>{
+ let compatibleNodes = [];
+ _.each(cy.nodes(), (tempNode)=>{
+ if(this.nodeLocationsCompatible(cy, node, tempNode)){
+ compatibleNodes.push(tempNode.data());
+ }
+ });
+ return compatibleNodes;
+ }
+
+ /**
+ * Checks whether node locations are compatible in reference to UCPEs.
+ * Returns true if both nodes are in UCPE or both nodes out, or one node is UCPEpart.
+ * @param node1
+ * @param node2
+ */
+ public nodeLocationsCompatible(cy:Cy.Instance, node1:Cy.CollectionFirstNode, node2:Cy.CollectionFirstNode){
+
+ let ucpe = cy.nodes('[?isUcpe]');
+ if(!ucpe.length){ return true; }
+ if(node1.data().isUcpePart || node2.data().isUcpePart) { return true; }
+
+ return (this.isFirstBoxContainsInSecondBox(node1.boundingbox(), ucpe.boundingbox()) == this.isFirstBoxContainsInSecondBox(node2.boundingbox(), ucpe.boundingbox()));
+
+ }
+
+ /**
+ * This function will init qtip tooltip on the node
+ * @param node - the node we want the tooltip to apply on
+ */
+ public initNodeTooltip(node:Cy.CollectionNodes) {
+
+ let opts = {
+ content: function () {
+ return this.data('name');
+ },
+ position: {
+ my: 'top center',
+ at: 'bottom center',
+ adjust: {x:0, y:-5}
+ },
+ style: {
+ classes: 'qtip-dark qtip-rounded qtip-custom',
+ tip: {
+ width: 16,
+ height: 8
+ }
+ },
+ show: {
+ event: 'mouseover',
+ delay: 1000
+ },
+ hide: {event: 'mouseout mousedown'},
+ includeLabels: true
+ };
+
+ if (node.data().isUcpePart){ //fix tooltip positioning for UCPE-cps
+ opts.position.adjust = {x:0, y:20};
+ }
+
+ node.qtip(opts);
+ };
+ };
+
+
+
+ CommonGraphUtils.$inject = ['NodesFactory', 'LinksFactory'];
+} \ No newline at end of file
diff --git a/catalog-ui/app/scripts/directives/graphs-v2/common/style/component-instances-nodes-style.ts b/catalog-ui/app/scripts/directives/graphs-v2/common/style/component-instances-nodes-style.ts
new file mode 100644
index 0000000000..2ec0174aa9
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/graphs-v2/common/style/component-instances-nodes-style.ts
@@ -0,0 +1,259 @@
+/**
+ * Created by obarda on 12/18/2016.
+ */
+/**
+ * Created by obarda on 12/13/2016.
+ */
+/// <reference path="../../../../references"/>
+module Sdc.Graph.Utils.ComponentIntanceNodesStyle {
+
+ export function getCompositionGraphStyle():Array<Cy.Stylesheet> {
+ return [
+ {
+ selector: 'core',
+ css: {
+ 'shape': 'rectangle',
+ 'active-bg-size': 0,
+ 'selection-box-color': 'rgb(0, 159, 219)',
+ 'selection-box-opacity': 0.2,
+ 'selection-box-border-color': '#009fdb',
+ 'selection-box-border-width': 1
+
+ }
+ },
+ {
+ selector: 'node',
+ css: {
+ 'font-family': 'omnes-regular,sans-serif',
+ 'font-size': 14,
+ 'events': 'yes',
+ 'text-events': 'yes',
+ 'text-border-width': 15,
+ 'text-border-color': Sdc.Utils.Constants.GraphColors.NODE_UCPE,
+ 'text-margin-y': 5
+ }
+ },
+ {
+ selector: '.vf-node',
+ css: {
+ 'background-color': 'transparent',
+ 'shape': 'rectangle',
+ 'label': 'data(displayName)',
+ 'background-image': 'data(img)',
+ 'width': 65,
+ 'height': 65,
+ 'background-opacity': 0,
+ "background-width": 65,
+ "background-height": 65,
+ 'text-valign': 'bottom',
+ 'text-halign': 'center',
+ 'background-fit': 'cover',
+ 'background-clip': 'node',
+ 'overlay-color': Sdc.Utils.Constants.GraphColors.NODE_BACKGROUND_COLOR,
+ 'overlay-opacity': 0
+ }
+ },
+
+ {
+ selector: '.service-node',
+ css: {
+ 'background-color': 'transparent',
+ 'label': 'data(displayName)',
+ 'events': 'yes',
+ 'text-events': 'yes',
+ 'background-image': 'data(img)',
+ 'width': 64,
+ 'height': 64,
+ "border-width": 0,
+ 'text-valign': 'bottom',
+ 'text-halign': 'center',
+ 'background-opacity': 0,
+ 'overlay-color': Sdc.Utils.Constants.GraphColors.NODE_BACKGROUND_COLOR,
+ 'overlay-opacity': 0
+ }
+ },
+ {
+ selector: '.cp-node',
+ css: {
+ 'background-color': 'rgb(255,255,255)',
+ 'shape': 'rectangle',
+ 'label': 'data(displayName)',
+ 'background-image': 'data(img)',
+ 'background-width': 21,
+ 'background-height': 21,
+ 'width': 21,
+ 'height': 21,
+ 'text-valign': 'bottom',
+ 'text-halign': 'center',
+ 'background-opacity': 0,
+ 'overlay-color': Sdc.Utils.Constants.GraphColors.NODE_BACKGROUND_COLOR,
+ 'overlay-opacity': 0
+ }
+ },
+ {
+ selector: '.vl-node',
+ css: {
+ 'background-color': 'rgb(255,255,255)',
+ 'shape': 'rectangle',
+ 'label': 'data(displayName)',
+ 'background-image': 'data(img)',
+ 'background-width': 21,
+ 'background-height': 21,
+ 'width': 21,
+ 'height': 21,
+ 'text-valign': 'bottom',
+ 'text-halign': 'center',
+ 'background-opacity': 0,
+ 'overlay-color': Sdc.Utils.Constants.GraphColors.NODE_BACKGROUND_COLOR,
+ 'overlay-opacity': 0
+ }
+ },
+ {
+ selector: '.ucpe-cp',
+ css: {
+ 'background-color': Sdc.Utils.Constants.GraphColors.NODE_UCPE_CP,
+ 'background-width': 15,
+ 'background-height': 15,
+ 'width': 15,
+ 'height': 15,
+ 'text-halign': 'center',
+ 'overlay-opacity': 0,
+ 'label': 'data(displayName)',
+ 'text-valign': 'data(textPosition)',
+ 'text-margin-y': (ele:Cy.Collection) => {
+ return (ele.data('textPosition') == 'top')? -5 : 5;
+ },
+ 'font-size': 12
+ }
+ },
+ {
+ selector: '.ucpe-node',
+ css: {
+ 'background-fit': 'cover',
+ 'padding-bottom': 0,
+ 'padding-top': 0
+ }
+ },
+ {
+ selector: '.simple-link',
+ css: {
+ 'width': 1,
+ 'line-color': Sdc.Utils.Constants.GraphColors.BASE_LINK,
+ 'target-arrow-color': '#3b7b9b',
+ 'target-arrow-shape': 'triangle',
+ 'curve-style': 'bezier',
+ 'control-point-step-size': 30
+ }
+ },
+ {
+ selector: '.vl-link',
+ css: {
+ 'width': 3,
+ 'line-color': Sdc.Utils.Constants.GraphColors.VL_LINK,
+ 'curve-style': 'bezier',
+ 'control-point-step-size': 30
+ }
+ },
+ {
+ selector: '.ucpe-host-link',
+ css: {
+ 'width': 0
+ }
+ },
+ {
+ selector: '.not-certified-link',
+ css: {
+ 'width': 1,
+ 'line-color': Sdc.Utils.Constants.GraphColors.NOT_CERTIFIED_LINK,
+ 'curve-style': 'bezier',
+ 'control-point-step-size': 30,
+ 'line-style': 'dashed',
+ 'target-arrow-color': '#3b7b9b',
+ 'target-arrow-shape': 'triangle'
+
+ }
+ },
+
+ {
+ selector: '.not-certified',
+ css: {
+ 'shape': 'rectangle',
+ 'background-image': (ele:Cy.Collection) => {
+ return ele.data().initImage(ele)
+ },
+ "border-width": 0
+ }
+ },
+ {
+ selector: 'node:selected',
+ css: {
+ "border-width": 2,
+ "border-color": Sdc.Utils.Constants.GraphColors.NODE_SELECTED_BORDER_COLOR,
+ 'shape': 'rectangle'
+ }
+ },
+ {
+ selector: 'edge:selected',
+ css: {
+ 'line-color': Sdc.Utils.Constants.GraphColors.ACTIVE_LINK
+
+ }
+ },
+ {
+ selector: 'edge:active',
+ css: {
+ 'overlay-opacity': 0
+ }
+ }
+ ]
+ }
+
+ export function getBasicNodeHanlde() {
+ return {
+ positionX: "center",
+ positionY: "top",
+ offsetX: 15,
+ offsetY: -20,
+ color: "#27a337",
+ type: "default",
+ single: false,
+ nodeTypeNames: ["basic-node"],
+ imageUrl: Sdc.Utils.Constants.IMAGE_PATH + '/styles/images/resource-icons/' + 'canvasPlusIcon.png',
+ lineWidth: 2,
+ lineStyle: 'dashed'
+
+ }
+ }
+
+ export function getBasicSmallNodeHandle() {
+ return {
+ positionX: "center",
+ positionY: "top",
+ offsetX: 3,
+ offsetY: -25,
+ color: "#27a337",
+ type: "default",
+ single: false,
+ nodeTypeNames: ["basic-small-node"],
+ imageUrl: Sdc.Utils.Constants.IMAGE_PATH + '/styles/images/resource-icons/' + 'canvasPlusIcon.png',
+ lineWidth: 2,
+ lineStyle: 'dashed'
+ }
+ }
+
+ export function getUcpeCpNodeHandle() {
+ return {
+ positionX: "center",
+ positionY: "center",
+ offsetX: -8,
+ offsetY: -10,
+ color: "#27a337",
+ type: "default",
+ single: false,
+ nodeTypeNames: ["ucpe-cp-node"],
+ imageUrl: Sdc.Utils.Constants.IMAGE_PATH + '/styles/images/resource-icons/' + 'canvasPlusIcon.png',
+ lineWidth: 2,
+ lineStyle: 'dashed'
+ }
+ }
+} \ No newline at end of file
diff --git a/catalog-ui/app/scripts/directives/graphs-v2/common/style/module-node-style.ts b/catalog-ui/app/scripts/directives/graphs-v2/common/style/module-node-style.ts
new file mode 100644
index 0000000000..62436fbf74
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/graphs-v2/common/style/module-node-style.ts
@@ -0,0 +1,92 @@
+/**
+ * Created by obarda on 1/1/2017.
+ */
+/**
+ * Created by obarda on 12/18/2016.
+ */
+/**
+ * Created by obarda on 12/13/2016.
+ */
+/// <reference path="../../../../references"/>
+module Sdc.Graph.Utils.ModulesNodesStyle {
+
+ export function getModuleGraphStyle():Array<Cy.Stylesheet> {
+
+ return [
+ {
+ selector: '.cy-expand-collapse-collapsed-node',
+ css: {
+ 'background-image': 'data(img)',
+ 'width': 34,
+ 'height': 32,
+ 'background-opacity': 0,
+ 'shape': 'rectangle',
+ 'label': 'data(displayName)',
+ 'events': 'yes',
+ 'text-events': 'yes',
+ 'text-valign': 'bottom',
+ 'text-halign': 'center',
+ 'text-margin-y': 5,
+ 'border-opacity': 0
+ }
+ },
+ {
+ selector: '.module-node',
+ css: {
+ 'background-color': 'transparent',
+ 'background-opacity': 0,
+ "border-width": 2,
+ "border-color": Sdc.Utils.Constants.GraphColors.NODE_SELECTED_BORDER_COLOR,
+ 'border-style': 'dashed',
+ 'label': 'data(displayName)',
+ 'events': 'yes',
+ 'text-events': 'yes',
+ 'text-valign': 'bottom',
+ 'text-halign': 'center',
+ 'text-margin-y': 8
+ }
+ },
+ {
+ selector: 'node:selected',
+ css: {
+ "border-opacity": 0
+ }
+ },
+ {
+ selector: '.simple-link:selected',
+ css: {
+ 'line-color': Sdc.Utils.Constants.GraphColors.BASE_LINK,
+ }
+ },
+ {
+ selector: '.vl-link:selected',
+ css: {
+ 'line-color': Sdc.Utils.Constants.GraphColors.VL_LINK,
+ }
+ },
+ {
+ selector: '.cy-expand-collapse-collapsed-node:selected',
+ css: {
+ "border-color": Sdc.Utils.Constants.GraphColors.NODE_SELECTED_BORDER_COLOR,
+ 'border-opacity': 1,
+ 'border-style': 'solid',
+ 'border-width': 2
+ }
+ },
+ {
+ selector: '.module-node:selected',
+ css: {
+ "border-color": Sdc.Utils.Constants.GraphColors.NODE_SELECTED_BORDER_COLOR,
+ 'border-opacity': 1
+ }
+ },
+ {
+ selector: '.dummy-node',
+ css: {
+ 'width': 20,
+ 'height': 20
+ }
+ },
+ ]
+ }
+} \ No newline at end of file
diff --git a/catalog-ui/app/scripts/directives/graphs-v2/composition-graph/composition-graph.directive.ts b/catalog-ui/app/scripts/directives/graphs-v2/composition-graph/composition-graph.directive.ts
new file mode 100644
index 0000000000..708f1d091a
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/graphs-v2/composition-graph/composition-graph.directive.ts
@@ -0,0 +1,555 @@
+/// <reference path="../../../references"/>
+module Sdc.Directives {
+
+ import ComponentFactory = Sdc.Utils.ComponentFactory;
+ import LoaderService = Sdc.Services.LoaderService;
+ import GRAPH_EVENTS = Sdc.Utils.Constants.GRAPH_EVENTS;
+
+ interface ICompositionGraphScope extends ng.IScope {
+
+ component:Models.Components.Component;
+ isViewOnly:boolean;
+ // Link menu - create link menu
+ relationMenuDirectiveObj:Models.RelationMenuDirectiveObj;
+ isLinkMenuOpen:boolean;
+ createLinkFromMenu:(chosenMatch:Models.MatchBase, vl:Models.Components.Component)=>void;
+
+ //modify link menu - for now only delete menu
+ relationMenuTimeout:ng.IPromise<any>;
+ linkMenuObject:Models.LinkMenu;
+
+ //left palette functions callbacks
+ dropCallback(event:JQueryEventObject, ui:any):void;
+ beforeDropCallback(event:IDragDropEvent):void;
+ verifyDrop(event:JQueryEventObject, ui:any):void;
+
+ //Links menus
+ deleteRelation(link:Cy.CollectionEdges):void;
+ hideRelationMenu();
+ }
+
+ export class CompositionGraph implements ng.IDirective {
+ private _cy:Cy.Instance;
+ private _currentlyCLickedNodePosition:Cy.Position;
+ private $document:JQuery = $(document);
+ private dragElement:JQuery;
+ private dragComponent: Sdc.Models.ComponentsInstances.ComponentInstance;
+
+ constructor(private $q:ng.IQService,
+ private $filter:ng.IFilterService,
+ private $log:ng.ILogService,
+ private $timeout:ng.ITimeoutService,
+ private NodesFactory:Sdc.Utils.NodesFactory,
+ private CompositionGraphLinkUtils:Sdc.Graph.Utils.CompositionGraphLinkUtils,
+ private GeneralGraphUtils:Graph.Utils.CompositionGraphGeneralUtils,
+ private ComponentInstanceFactory:Utils.ComponentInstanceFactory,
+ private NodesGraphUtils:Sdc.Graph.Utils.CompositionGraphNodesUtils,
+ private eventListenerService:Services.EventListenerService,
+ private ComponentFactory:ComponentFactory,
+ private LoaderService:LoaderService,
+ private commonGraphUtils:Graph.Utils.CommonGraphUtils,
+ private matchCapabilitiesRequirementsUtils:Graph.Utils.MatchCapabilitiesRequirementsUtils) {
+
+ }
+
+ restrict = 'E';
+ templateUrl = '/app/scripts/directives/graphs-v2/composition-graph/composition-graph.html';
+ scope = {
+ component: '=',
+ isViewOnly: '='
+ };
+
+ link = (scope:ICompositionGraphScope, el:JQuery) => {
+ this.loadGraph(scope, el);
+
+ scope.$on('$destroy', () => {
+ this._cy.destroy();
+ _.forEach(GRAPH_EVENTS, (event) => {
+ this.eventListenerService.unRegisterObserver(event);
+ });
+ });
+
+ };
+
+ private loadGraph = (scope:ICompositionGraphScope, el:JQuery) => {
+
+
+ let graphEl = el.find('.sdc-composition-graph-wrapper');
+ this.initGraph(graphEl, scope.isViewOnly);
+ this.initGraphNodes(scope.component.componentInstances, scope.isViewOnly);
+ this.commonGraphUtils.initGraphLinks(this._cy, scope.component.componentInstancesRelations);
+ this.commonGraphUtils.initUcpeChildren(this._cy);
+ this.initDropZone(scope);
+ this.registerCytoscapeGraphEvents(scope);
+ this.registerCustomEvents(scope, el);
+ this.initViewMode(scope.isViewOnly);
+
+ };
+
+ private initGraph(graphEl:JQuery, isViewOnly:boolean) {
+
+ this._cy = cytoscape({
+ container: graphEl,
+ style: Sdc.Graph.Utils.ComponentIntanceNodesStyle.getCompositionGraphStyle(),
+ zoomingEnabled: false,
+ selectionType: 'single',
+ boxSelectionEnabled: true,
+ autolock: isViewOnly,
+ autoungrabify: isViewOnly
+ });
+ }
+
+ private initViewMode(isViewOnly:boolean) {
+
+ if (isViewOnly) {
+ //remove event listeners
+ this._cy.off('drag');
+ this._cy.off('handlemouseout');
+ this._cy.off('handlemouseover');
+ this._cy.edges().unselectify();
+ }
+ };
+
+ private registerCustomEvents(scope:ICompositionGraphScope, el:JQuery) {
+
+ this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_PALETTE_COMPONENT_HOVER_IN, (component:Models.DisplayComponent) => {
+ this.$log.info(`composition-graph::registerEventServiceEvents:: palette hover on component: ${component.uniqueId}`);
+
+ let nodesData = this.NodesGraphUtils.getAllNodesData(this._cy.nodes());
+ let nodesLinks = this.GeneralGraphUtils.getAllCompositionCiLinks(this._cy);
+
+ if (this.GeneralGraphUtils.componentRequirementsAndCapabilitiesCaching.containsKey(component.uniqueId)) {
+ let cacheComponent = this.GeneralGraphUtils.componentRequirementsAndCapabilitiesCaching.getValue(component.uniqueId);
+ let filteredNodesData = this.matchCapabilitiesRequirementsUtils.findByMatchingCapabilitiesToRequirements(cacheComponent, nodesData, nodesLinks);
+
+ this.matchCapabilitiesRequirementsUtils.highlightMatchingComponents(filteredNodesData, this._cy);
+ this.matchCapabilitiesRequirementsUtils.fadeNonMachingComponents(filteredNodesData, nodesData, this._cy);
+
+ return;
+ }
+
+ component.component.updateRequirementsCapabilities()
+ .then((res) => {
+ component.component.capabilities = res.capabilities;
+ component.component.requirements = res.requirements;
+
+ let filteredNodesData = this.matchCapabilitiesRequirementsUtils.findByMatchingCapabilitiesToRequirements(component.component, nodesData, nodesLinks);
+ this.matchCapabilitiesRequirementsUtils.fadeNonMachingComponents(filteredNodesData, nodesData, this._cy);
+ this.matchCapabilitiesRequirementsUtils.highlightMatchingComponents(filteredNodesData, this._cy)
+ });
+ });
+
+ this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_PALETTE_COMPONENT_HOVER_OUT, () => {
+ this._cy.emit('hidehandles');
+ this.matchCapabilitiesRequirementsUtils.resetFadedNodes(this._cy);
+ });
+
+ this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_PALETTE_COMPONENT_DRAG_START, (dragElement, dragComponent) => {
+
+ this.dragElement = dragElement;
+ this.dragComponent = this.ComponentInstanceFactory.createComponentInstanceFromComponent(dragComponent);
+ });
+
+ this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_PALETTE_COMPONENT_DRAG_ACTION, (event:IDragDropEvent) => {
+ this._onComponentDrag(event);
+
+ });
+
+ this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_COMPONENT_INSTANCE_NAME_CHANGED, (component:Models.ComponentsInstances.ComponentInstance) => {
+
+ let selectedNode = this._cy.getElementById(component.uniqueId);
+ selectedNode.data().componentInstance.name = component.name;
+ selectedNode.data('displayName', selectedNode.data().getDisplayName());
+
+ });
+
+ this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_DELETE_COMPONENT_INSTANCE, (componentInstance:Models.ComponentsInstances.ComponentInstance) => {
+ let nodeToDelete = this._cy.getElementById(componentInstance.uniqueId);
+ this.NodesGraphUtils.deleteNode(this._cy, scope.component, nodeToDelete);
+ });
+
+ this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_DELETE_MULTIPLE_COMPONENTS, () => {
+
+ this._cy.$('node:selected').each((i:number, node:Cy.CollectionNodes) => {
+ this.NodesGraphUtils.deleteNode(this._cy, scope.component, node);
+ });
+
+ });
+
+ this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_DELETE_EDGE, (releaseLoading:boolean, linksToDelete:Cy.CollectionEdges) => {
+ this.CompositionGraphLinkUtils.deleteLink(this._cy, scope.component, releaseLoading, linksToDelete);
+ });
+
+ this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_INSERT_NODE_TO_UCPE, (node:Cy.CollectionNodes, ucpe:Cy.CollectionNodes, updateExistingNode: boolean) => {
+
+ this.commonGraphUtils.initUcpeChildData(node, ucpe);
+ //check if item is a VL, and if so, skip adding the binding to ucpe
+ if(!(node.data() instanceof Sdc.Models.Graph.CompositionCiNodeVl)){
+ this.CompositionGraphLinkUtils.createVfToUcpeLink(scope.component, this._cy, ucpe.data(), node.data()); //create link from the node to the ucpe
+ }
+
+ if(updateExistingNode){
+ let vlsPendingDeletion:Cy.CollectionNodes = this.NodesGraphUtils.deleteNodeVLsUponMoveToOrFromUCPE(scope.component, node.cy(), node); //delete connected VLs that no longer have 2 links
+ this.CompositionGraphLinkUtils.deleteLinksWhenNodeMovedFromOrToUCPE(scope.component, node.cy(), node, vlsPendingDeletion); //delete all connected links if needed
+ this.GeneralGraphUtils.pushUpdateComponentInstanceActionToQueue(scope.component, true, node.data().componentInstance); //update componentInstance position
+ }
+
+ });
+
+ this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_REMOVE_NODE_FROM_UCPE, (node:Cy.CollectionNodes, ucpe:Cy.CollectionNodes) => {
+ this.commonGraphUtils.removeUcpeChildData(node);
+ let vlsPendingDeletion:Cy.CollectionNodes = this.NodesGraphUtils.deleteNodeVLsUponMoveToOrFromUCPE(scope.component, node.cy(), node);
+ this.CompositionGraphLinkUtils.deleteLinksWhenNodeMovedFromOrToUCPE(scope.component, node.cy(), node, vlsPendingDeletion); //delete all connected links if needed
+ this.GeneralGraphUtils.pushUpdateComponentInstanceActionToQueue(scope.component, true, node.data().componentInstance); //update componentInstance position
+ });
+
+ this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_VERSION_CHANGED, (component:Models.Components.Component) => {
+ scope.component = component;
+ this.loadGraph(scope, el);
+ });
+
+
+ scope.createLinkFromMenu = (chosenMatch:Models.MatchBase, vl:Models.Components.Component):void => {
+ scope.isLinkMenuOpen = false;
+
+ this.CompositionGraphLinkUtils.createLinkFromMenu(this._cy, chosenMatch, vl, scope.component);
+ };
+
+ scope.hideRelationMenu = () => {
+ this.commonGraphUtils.safeApply(scope, () => {
+ scope.linkMenuObject = null;
+ this.$timeout.cancel(scope.relationMenuTimeout);
+ });
+ };
+
+
+ scope.deleteRelation = (link:Cy.CollectionEdges) => {
+ scope.hideRelationMenu();
+
+ //if multiple edges selected, delete the VL itself so edges get deleted automatically
+ if (this._cy.$('edge:selected').length > 1) {
+ this.NodesGraphUtils.deleteNode(this._cy, scope.component, this._cy.$('node:selected'));
+ } else {
+ this.CompositionGraphLinkUtils.deleteLink(this._cy, scope.component, true, link);
+ }
+ };
+ }
+
+
+ private registerCytoscapeGraphEvents(scope:ICompositionGraphScope) {
+
+ this._cy.on('addedgemouseup', (event, data) => {
+ scope.relationMenuDirectiveObj = this.CompositionGraphLinkUtils.onLinkDrawn(this._cy, data.source, data.target);
+ if (scope.relationMenuDirectiveObj != null) {
+ scope.$apply(() => {
+ scope.isLinkMenuOpen = true;
+ });
+ }
+ });
+ this._cy.on('tapstart', 'node', (event:Cy.EventObject) => {
+ this._currentlyCLickedNodePosition = angular.copy(event.cyTarget[0].position()); //update node position on drag
+ if(event.cyTarget.data().isUcpe){
+ this._cy.nodes('.ucpe-cp').unlock();
+ event.cyTarget.style('opacity', 0.5);
+ }
+ });
+
+ this._cy.on('drag', 'node', (event:Cy.EventObject) => {
+
+ if (event.cyTarget.data().isDraggable) {
+ event.cyTarget.style({'overlay-opacity': 0.24});
+ if (this.GeneralGraphUtils.isValidDrop(this._cy, event.cyTarget)) {
+ event.cyTarget.style({'overlay-color': Utils.Constants.GraphColors.NODE_BACKGROUND_COLOR});
+ } else {
+ event.cyTarget.style({'overlay-color': Utils.Constants.GraphColors.NODE_OVERLAPPING_BACKGROUND_COLOR});
+ }
+ }
+
+ if(event.cyTarget.data().isUcpe){
+ let pos = event.cyTarget.position();
+
+ this._cy.nodes('[?isInsideGroup]').positions((i, node)=>{
+ return {
+ x: pos.x + node.data("ucpeOffset").x,
+ y: pos.y + node.data("ucpeOffset").y
+ }
+ });
+ }
+ });
+
+
+ this._cy.on('handlemouseover', (event, payload) => {
+
+ if (payload.node.grabbed()) { //no need to add opacity while we are dragging and hovering othe nodes
+ return;
+ }
+
+ let nodesData = this.NodesGraphUtils.getAllNodesData(this._cy.nodes());
+ let nodesLinks = this.GeneralGraphUtils.getAllCompositionCiLinks(this._cy);
+
+ let linkableNodes = this.commonGraphUtils.getLinkableNodes(this._cy, payload.node);
+ let filteredNodesData = this.matchCapabilitiesRequirementsUtils.findByMatchingCapabilitiesToRequirements(payload.node.data().componentInstance, linkableNodes, nodesLinks);
+ this.matchCapabilitiesRequirementsUtils.highlightMatchingComponents(filteredNodesData, this._cy);
+ this.matchCapabilitiesRequirementsUtils.fadeNonMachingComponents(filteredNodesData, nodesData, this._cy, payload.node.data());
+
+ });
+
+ this._cy.on('handlemouseout', () => {
+ this._cy.emit('hidehandles');
+ this.matchCapabilitiesRequirementsUtils.resetFadedNodes(this._cy);
+ });
+
+
+ this._cy.on('tapend', (event:Cy.EventObject) => {
+
+ if (event.cyTarget === this._cy) { //On Background clicked
+ if (this._cy.$('node:selected').length === 0) { //if the background click but not dragged
+ this.eventListenerService.notifyObservers(Sdc.Utils.Constants.GRAPH_EVENTS.ON_GRAPH_BACKGROUND_CLICKED);
+ }
+ scope.hideRelationMenu();
+ }
+
+ else if (event.cyTarget.isEdge()) { //On Edge clicked
+ if (scope.isViewOnly) return;
+ this.CompositionGraphLinkUtils.handleLinkClick(this._cy, event);
+ this.openModifyLinkMenu(scope, this.CompositionGraphLinkUtils.getModifyLinkMenu(event.cyTarget[0], event), 6000);
+ }
+
+ else { //On Node clicked
+ this._cy.nodes(':grabbed').style({'overlay-opacity': 0});
+
+ let isUcpe:boolean = event.cyTarget.data().isUcpe;
+ let newPosition = event.cyTarget[0].position();
+ //node position changed (drop after drag event) - we need to update position
+ if (this._currentlyCLickedNodePosition.x !== newPosition.x || this._currentlyCLickedNodePosition.y !== newPosition.y) {
+ let nodesMoved:Cy.CollectionNodes = this._cy.$(':grabbed');
+ if(isUcpe){
+ nodesMoved = nodesMoved.add(this._cy.nodes('[?isInsideGroup]:free')); //'child' nodes will not be recognized as "grabbed" elements within cytoscape. manually add them to collection of nodes moved.
+ }
+ this.NodesGraphUtils.onNodesPositionChanged(this._cy, scope.component, nodesMoved);
+ } else {
+ this.$log.debug('composition-graph::onNodeSelectedEvent:: fired');
+ scope.$apply(() => {
+ this.eventListenerService.notifyObservers(Sdc.Utils.Constants.GRAPH_EVENTS.ON_NODE_SELECTED, event.cyTarget.data().componentInstance);
+ });
+ }
+
+ if(isUcpe){
+ this._cy.nodes('.ucpe-cp').lock();
+ event.cyTarget.style('opacity', 1);
+ }
+
+ }
+ });
+
+ this._cy.on('boxselect', 'node', (event:Cy.EventObject) => {
+ this.eventListenerService.notifyObservers(Utils.Constants.GRAPH_EVENTS.ON_NODE_SELECTED, event.cyTarget.data().componentInstance);
+ });
+ }
+
+ private openModifyLinkMenu = (scope:ICompositionGraphScope, linkMenuObject:Models.LinkMenu, timeOutInMilliseconds?:number) => {
+
+ this.commonGraphUtils.safeApply(scope, () => {
+ scope.linkMenuObject = linkMenuObject;
+ });
+
+ scope.relationMenuTimeout = this.$timeout(() => {
+ scope.hideRelationMenu();
+ }, timeOutInMilliseconds ? timeOutInMilliseconds : 6000);
+ };
+
+ private initGraphNodes(componentInstances:Models.ComponentsInstances.ComponentInstance[], isViewOnly:boolean) {
+
+ if (!isViewOnly) { //Init nodes handle extension - enable dynamic links
+ setTimeout(()=> {
+ let handles = new CytoscapeEdgeEditation;
+ handles.init(this._cy, 18);
+ handles.registerHandle(Sdc.Graph.Utils.ComponentIntanceNodesStyle.getBasicNodeHanlde());
+ handles.registerHandle(Sdc.Graph.Utils.ComponentIntanceNodesStyle.getBasicSmallNodeHandle());
+ handles.registerHandle(Sdc.Graph.Utils.ComponentIntanceNodesStyle.getUcpeCpNodeHandle());
+ }, 0);
+ }
+
+ _.each(componentInstances, (instance) => {
+ let compositionGraphNode:Models.Graph.CompositionCiNodeBase = this.NodesFactory.createNode(instance);
+ this.commonGraphUtils.addComponentInstanceNodeToGraph(this._cy, compositionGraphNode);
+ });
+
+
+
+ }
+
+
+ private initDropZone(scope:ICompositionGraphScope) {
+
+ if (scope.isViewOnly) {
+ return;
+ }
+ scope.dropCallback = (event:IDragDropEvent) => {
+ this.$log.debug(`composition-graph::dropCallback:: fired`);
+ this.addNode(event, scope);
+ };
+
+ scope.verifyDrop = (event:JQueryEventObject) => {
+
+ if(this.dragElement.hasClass('red')){
+ return false;
+ }
+ return true;
+ };
+
+ scope.beforeDropCallback = (event:IDragDropEvent): ng.IPromise<void> => {
+ let deferred: ng.IDeferred<void> = this.$q.defer<void>();
+ if(this.dragElement.hasClass('red')){
+ deferred.reject();
+ } else {
+ deferred.resolve();
+ }
+
+ return deferred.promise;
+ }
+ }
+
+ private _getNodeBBox(event:IDragDropEvent, position?:Cy.Position) {
+ let bbox = <Cy.BoundingBox>{};
+ if (!position) {
+ position = this.commonGraphUtils.getCytoscapeNodePosition(this._cy, event);
+ }
+ let cushionWidth:number = 40;
+ let cushionHeight:number = 40;
+
+ bbox.x1 = position.x - cushionWidth / 2;
+ bbox.y1 = position.y - cushionHeight / 2;
+ bbox.x2 = position.x + cushionWidth / 2;
+ bbox.y2 = position.y + cushionHeight / 2;
+ return bbox;
+ }
+
+ private createComponentInstanceOnGraphFromComponent(fullComponent:Models.Components.Component, event:IDragDropEvent, scope:ICompositionGraphScope) {
+
+ let componentInstanceToCreate:Models.ComponentsInstances.ComponentInstance = this.ComponentInstanceFactory.createComponentInstanceFromComponent(fullComponent);
+ let cytoscapePosition:Cy.Position = this.commonGraphUtils.getCytoscapeNodePosition(this._cy, event);
+
+ componentInstanceToCreate.posX = cytoscapePosition.x;
+ componentInstanceToCreate.posY = cytoscapePosition.y;
+
+
+ let onFailedCreatingInstance:(error:any) => void = (error:any) => {
+ this.LoaderService.hideLoader('composition-graph');
+ };
+
+ //on success - update node data
+ let onSuccessCreatingInstance = (createInstance:Models.ComponentsInstances.ComponentInstance):void => {
+
+ this.LoaderService.hideLoader('composition-graph');
+
+ createInstance.name = this.$filter('resourceName')(createInstance.name);
+ createInstance.requirements = new Models.RequirementsGroup(fullComponent.requirements);
+ createInstance.capabilities = new Models.CapabilitiesGroup(fullComponent.capabilities);
+ createInstance.componentVersion = fullComponent.version;
+ createInstance.icon = fullComponent.icon;
+ createInstance.setInstanceRC();
+
+ let newNode:Models.Graph.CompositionCiNodeBase = this.NodesFactory.createNode(createInstance);
+ let cyNode:Cy.CollectionNodes = this.commonGraphUtils.addComponentInstanceNodeToGraph(this._cy, newNode);
+
+ //check if node was dropped into a UCPE
+ let ucpe:Cy.CollectionElements = this.commonGraphUtils.isInUcpe(this._cy, cyNode.boundingbox());
+ if (ucpe.length > 0) {
+ this.eventListenerService.notifyObservers(Utils.Constants.GRAPH_EVENTS.ON_INSERT_NODE_TO_UCPE, cyNode, ucpe, false);
+ }
+
+ };
+
+ // Create the component instance on server
+ this.GeneralGraphUtils.getGraphUtilsServerUpdateQueue().addBlockingUIAction(() => {
+ scope.component.createComponentInstance(componentInstanceToCreate).then(onSuccessCreatingInstance, onFailedCreatingInstance);
+ });
+ }
+
+ private _onComponentDrag(event:IDragDropEvent) {
+
+ if(event.clientX < Sdc.Utils.Constants.GraphUIObjects.DIAGRAM_PALETTE_WIDTH_OFFSET || event.clientY < Sdc.Utils.Constants.GraphUIObjects.DIAGRAM_HEADER_OFFSET){ //hovering over palette. Dont bother computing validity of drop
+ this.dragElement.removeClass('red');
+ return;
+ }
+
+ let offsetPosition = {x: event.clientX - Sdc.Utils.Constants.GraphUIObjects.DIAGRAM_PALETTE_WIDTH_OFFSET, y: event.clientY - Sdc.Utils.Constants.GraphUIObjects.DIAGRAM_HEADER_OFFSET}
+ let bbox = this._getNodeBBox(event, offsetPosition);
+
+ if (this.GeneralGraphUtils.isPaletteDropValid(this._cy, bbox, this.dragComponent)) {
+ this.dragElement.removeClass('red');
+ } else {
+ this.dragElement.addClass('red');
+ }
+ }
+
+ private addNode(event:IDragDropEvent, scope:ICompositionGraphScope) {
+ this.LoaderService.showLoader('composition-graph');
+
+ this.$log.debug('composition-graph::addNode:: fired');
+ let draggedComponent:Models.Components.Component = event.dataTransfer.component;
+
+ if (this.GeneralGraphUtils.componentRequirementsAndCapabilitiesCaching.containsKey(draggedComponent.uniqueId)) {
+ this.$log.debug('composition-graph::addNode:: capabilities found in cache, creating component');
+ let fullComponent = this.GeneralGraphUtils.componentRequirementsAndCapabilitiesCaching.getValue(draggedComponent.uniqueId);
+ this.createComponentInstanceOnGraphFromComponent(fullComponent, event, scope);
+ return;
+ }
+
+ this.$log.debug('composition-graph::addNode:: capabilities not found, requesting from server');
+ this.ComponentFactory.getComponentFromServer(draggedComponent.getComponentSubType(), draggedComponent.uniqueId)
+ .then((fullComponent:Models.Components.Component) => {
+ this.createComponentInstanceOnGraphFromComponent(fullComponent, event, scope);
+ });
+ }
+
+ public static factory = ($q,
+ $filter,
+ $log,
+ $timeout,
+ NodesFactory,
+ LinksGraphUtils,
+ GeneralGraphUtils,
+ ComponentInstanceFactory,
+ NodesGraphUtils,
+ EventListenerService,
+ ComponentFactory,
+ LoaderService,
+ CommonGraphUtils,
+ MatchCapabilitiesRequirementsUtils) => {
+ return new CompositionGraph(
+ $q,
+ $filter,
+ $log,
+ $timeout,
+ NodesFactory,
+ LinksGraphUtils,
+ GeneralGraphUtils,
+ ComponentInstanceFactory,
+ NodesGraphUtils,
+ EventListenerService,
+ ComponentFactory,
+ LoaderService,
+ CommonGraphUtils,
+ MatchCapabilitiesRequirementsUtils);
+ }
+ }
+
+ CompositionGraph.factory.$inject = [
+ '$q',
+ '$filter',
+ '$log',
+ '$timeout',
+ 'NodesFactory',
+ 'CompositionGraphLinkUtils',
+ 'CompositionGraphGeneralUtils',
+ 'ComponentInstanceFactory',
+ 'CompositionGraphNodesUtils',
+ 'EventListenerService',
+ 'ComponentFactory',
+ 'LoaderService',
+ 'CommonGraphUtils',
+ 'MatchCapabilitiesRequirementsUtils'
+ ];
+} \ No newline at end of file
diff --git a/catalog-ui/app/scripts/directives/graphs-v2/composition-graph/composition-graph.html b/catalog-ui/app/scripts/directives/graphs-v2/composition-graph/composition-graph.html
new file mode 100644
index 0000000000..5f2c488341
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/graphs-v2/composition-graph/composition-graph.html
@@ -0,0 +1,22 @@
+<loader display="isLoading" loader-type="composition-graph"></loader>
+<div class="sdc-composition-graph-wrapper" ng-class="{'view-only':isViewOnly}"
+ data-drop="true"
+ data-jqyoui-options="{accept: verifyDrop}"
+ data-jqyoui-droppable="{onDrop:'dropCallback', beforeDrop: 'beforeDropCallback'}">
+</div>
+
+<relation-menu relation-menu-directive-obj="relationMenuDirectiveObj" is-link-menu-open="isLinkMenuOpen"
+ create-relation="createLinkFromMenu" cancel="cancelRelationMenu()"></relation-menu>
+
+
+<div class="w-sdc-canvas-menu"
+ data-ng-show="linkMenuObject" ng-style="{left: linkMenuObject.position.x, top: linkMenuObject.position.y}"
+ id="relationMenu">
+
+ <div class="w-sdc-canvas-menu-content hand" data-ng-click="deleteRelation(linkMenuObject.link)">
+ <div class="w-sdc-canvas-menu-content-delete-button"></div>
+ <!--{{relationComponent.data.relation.relationships[0].relationship.type | relationName }}-->
+ Delete
+ </div>
+
+</div>
diff --git a/catalog-ui/app/scripts/directives/graphs-v2/composition-graph/composition-graph.less b/catalog-ui/app/scripts/directives/graphs-v2/composition-graph/composition-graph.less
new file mode 100644
index 0000000000..7b999967b7
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/graphs-v2/composition-graph/composition-graph.less
@@ -0,0 +1,14 @@
+composition-graph {
+ display: block;
+
+ height:100%;
+ width: 100%;
+ .sdc-composition-graph-wrapper{
+ height:100%;
+ width: 100%;
+ }
+
+ &.view-only{
+ background-color:rgb(248, 248, 248);
+ }
+} \ No newline at end of file
diff --git a/catalog-ui/app/scripts/directives/graphs-v2/composition-graph/utils/composition-graph-general-utils.ts b/catalog-ui/app/scripts/directives/graphs-v2/composition-graph/utils/composition-graph-general-utils.ts
new file mode 100644
index 0000000000..495a243d75
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/graphs-v2/composition-graph/utils/composition-graph-general-utils.ts
@@ -0,0 +1,243 @@
+/// <reference path="../../../../references"/>
+module Sdc.Graph.Utils {
+
+ import Dictionary = Sdc.Utils.Dictionary;
+
+ export class CompositionGraphGeneralUtils {
+
+ public componentRequirementsAndCapabilitiesCaching = new Dictionary<string, Models.Components.Component>();
+ protected static graphUtilsUpdateQueue: Sdc.Utils.Functions.QueueUtils;
+
+ constructor(private $q: ng.IQService,
+ private LoaderService: Services.LoaderService,
+ private commonGraphUtils: Sdc.Graph.Utils.CommonGraphUtils,
+ private matchCapabilitiesRequirementsUtils: Graph.Utils.MatchCapabilitiesRequirementsUtils) {
+ CompositionGraphGeneralUtils.graphUtilsUpdateQueue = new Sdc.Utils.Functions.QueueUtils(this.$q);
+ }
+
+
+ /**
+ * Get the offset for the link creation Menu
+ * @param point
+ * @returns {Cy.Position}
+ */
+ public calcMenuOffset: Function = (point: Cy.Position): Cy.Position => {
+ point.x = point.x + 60;
+ point.y = point.y + 105;
+ return point;
+ };
+
+ /**
+ * return the top left position of the link menu
+ * @param cy
+ * @param targetNodePosition
+ * @returns {Cy.Position}
+ */
+ public getLinkMenuPosition = (cy: Cy.Instance, targetNodePosition: Cy.Position) => {
+ let menuPosition: Cy.Position = this.calcMenuOffset(targetNodePosition); //get the link mid point
+ if (document.body.scrollHeight < menuPosition.y + Sdc.Utils.Constants.GraphUIObjects.LINK_MENU_HEIGHT + $(document.getElementsByClassName('sdc-composition-graph-wrapper')).offset().top) { // if position menu is overflow bottom
+ menuPosition.y = document.body.scrollHeight - Sdc.Utils.Constants.GraphUIObjects.TOP_HEADER_HEIGHT - Sdc.Utils.Constants.GraphUIObjects.LINK_MENU_HEIGHT;
+ }
+ return menuPosition;
+ };
+
+
+ /**
+ * will return true/false if two nodes overlapping
+ *
+ * @param graph node
+ */
+ private isNodesOverlapping(node: Cy.CollectionFirstNode, draggedNode: Cy.CollectionFirstNode): boolean {
+
+ let nodeBoundingBox: Cy.BoundingBox = node.renderedBoundingBox();
+ let secondNodeBoundingBox: Cy.BoundingBox = draggedNode.renderedBoundingBox();
+
+ return this.isBBoxOverlapping(nodeBoundingBox, secondNodeBoundingBox);
+ }
+
+ /**
+ * Checks whether the bounding boxes of two nodes are overlapping on any side
+ * @param nodeOneBBox
+ * @param nodeTwoBBox
+ * @returns {boolean}
+ */
+ private isBBoxOverlapping(nodeOneBBox: Cy.BoundingBox, nodeTwoBBox: Cy.BoundingBox) {
+ return (((nodeOneBBox.x1 < nodeTwoBBox.x1 && nodeOneBBox.x2 > nodeTwoBBox.x1) ||
+ (nodeOneBBox.x1 < nodeTwoBBox.x2 && nodeOneBBox.x2 > nodeTwoBBox.x2) ||
+ (nodeTwoBBox.x1 < nodeOneBBox.x1 && nodeTwoBBox.x2 > nodeOneBBox.x2)) &&
+ ((nodeOneBBox.y1 < nodeTwoBBox.y1 && nodeOneBBox.y2 > nodeTwoBBox.y1) ||
+ (nodeOneBBox.y1 < nodeTwoBBox.y2 && nodeOneBBox.y2 > nodeTwoBBox.y2) ||
+ (nodeTwoBBox.y1 < nodeOneBBox.y1 && nodeTwoBBox.y2 > nodeOneBBox.y2)))
+ }
+
+
+ /**
+ * Checks whether a specific component instance can be hosted on the UCPE instance
+ * @param cy - Cytoscape instance
+ * @param fromUcpeInstance
+ * @param toComponentInstance
+ * @returns {Models.MatchReqToCapability}
+ */
+ public canBeHostedOn(cy: Cy.Instance, fromUcpeInstance: Models.ComponentsInstances.ComponentInstance, toComponentInstance: Models.ComponentsInstances.ComponentInstance): Models.MatchReqToCapability {
+
+ let matches: Array<Models.MatchBase> = this.matchCapabilitiesRequirementsUtils.getMatchedRequirementsCapabilities(fromUcpeInstance, toComponentInstance, this.getAllCompositionCiLinks(cy));
+ let hostedOnMatch: Models.MatchBase = _.find(matches, (match: Models.MatchReqToCapability) => {
+ return match.requirement.capability.toLowerCase() === 'tosca.capabilities.container';
+ });
+
+ return <Models.MatchReqToCapability>hostedOnMatch;
+ };
+
+
+ /**
+ * Checks whether node can be dropped into UCPE
+ * @param cy
+ * @param nodeToInsert
+ * @param ucpeNode
+ * @returns {boolean}
+ */
+ private isValidDropInsideUCPE(cy: Cy.Instance, nodeToInsert: Models.ComponentsInstances.ComponentInstance, ucpeNode: Models.ComponentsInstances.ComponentInstance): boolean {
+
+ let hostedOnMatch: Models.MatchReqToCapability = this.canBeHostedOn(cy, ucpeNode, nodeToInsert);
+ let result: boolean = !angular.isUndefined(hostedOnMatch) || nodeToInsert.isVl(); //group validation
+ return result;
+
+ };
+
+
+ /**
+ * For drops from palette, checks whether the node can be dropped. If node is being held over another node, check if capable of hosting
+ * @param cy
+ * @param pseudoNodeBBox
+ * @param paletteComponentInstance
+ * @returns {boolean}
+ */
+ public isPaletteDropValid(cy: Cy.Instance, pseudoNodeBBox: Cy.BoundingBox, paletteComponentInstance:Sdc.Models.ComponentsInstances.ComponentInstance) {
+
+ let componentIsUCPE:boolean = (paletteComponentInstance.capabilities && paletteComponentInstance.capabilities['tosca.capabilities.Container'] && paletteComponentInstance.name.toLowerCase().indexOf('ucpe') > -1);
+
+ if(componentIsUCPE && cy.nodes('[?isUcpe]').length > 0) { //second UCPE not allowed
+ return false;
+ }
+
+ let illegalOverlappingNodes = _.filter(cy.nodes("[isSdcElement]"), (graphNode: Cy.CollectionFirstNode) => {
+
+ if(this.isBBoxOverlapping(pseudoNodeBBox, graphNode.renderedBoundingBox())){
+ if (!componentIsUCPE && graphNode.data().isUcpe) {
+ return !this.isValidDropInsideUCPE(cy, paletteComponentInstance, graphNode.data().componentInstance); //if this is valid insert into ucpe, we return false - no illegal overlapping nodes
+ }
+ return true;
+ }
+
+ return false;
+ });
+
+ return illegalOverlappingNodes.length === 0;
+ }
+
+ /**
+ * will return true/false if a drop of a single node is valid
+ *
+ * @param graph node
+ */
+ public isValidDrop(cy: Cy.Instance, draggedNode: Cy.CollectionFirstNode): boolean {
+
+ let illegalOverlappingNodes = _.filter(cy.nodes("[isSdcElement]"), (graphNode: Cy.CollectionFirstNode) => { //all sdc nodes, removing child nodes (childe node allways collaps
+
+ if (draggedNode.data().isUcpe && (graphNode.isChild() || graphNode.data().isInsideGroup)) { //ucpe cps always inside ucpe, no overlapping
+ return false;
+ }
+ if(draggedNode.data().isInsideGroup && (!draggedNode.active() || graphNode.data().isUcpe)) {
+ return false;
+ }
+
+ if (!draggedNode.data().isUcpe && !(draggedNode.data() instanceof Sdc.Models.Graph.CompositionCiNodeUcpeCp) && graphNode.data().isUcpe) { //case we are dragging a node into UCPE
+ let isEntirelyInUCPE:boolean = this.commonGraphUtils.isFirstBoxContainsInSecondBox(draggedNode.renderedBoundingBox(), graphNode.renderedBoundingBox());
+ if (isEntirelyInUCPE){
+ if(this.isValidDropInsideUCPE(cy, draggedNode.data().componentInstance, graphNode.data().componentInstance)){ //if this is valid insert into ucpe, we return false - no illegal overlapping nodes
+ return false;
+ }
+ }
+ }
+ return graphNode.data().id !== draggedNode.data().id && this.isNodesOverlapping(draggedNode, graphNode);
+
+ });
+ // return false;
+ return illegalOverlappingNodes.length === 0;
+ };
+
+ /**
+ * will return true/false if the move of the nodes is valid (no node overlapping and verifying if insert into UCPE is valid)
+ *
+ * @param nodesArray - the selected drags nodes
+ */
+ public isGroupValidDrop(cy: Cy.Instance, nodesArray: Cy.CollectionNodes): boolean {
+ var filterDraggedNodes = nodesArray.filter('[?isDraggable]');
+ let isValidDrop = _.every(filterDraggedNodes, (node: Cy.CollectionFirstNode) => {
+ return this.isValidDrop(cy, node);
+
+ });
+ return isValidDrop;
+ };
+
+ /**
+ * get all links in diagram
+ * @param cy
+ * @returns {any[]|boolean[]}
+ */
+ public getAllCompositionCiLinks = (cy: Cy.Instance): Array<Models.CompositionCiLinkBase> => {
+ return _.map(cy.edges("[isSdcElement]"), (edge: Cy.CollectionEdges) => {
+ return edge.data();
+ });
+ };
+
+
+ /**
+ * Get Graph Utils server queue
+ * @returns {Sdc.Utils.Functions.QueueUtils}
+ */
+ public getGraphUtilsServerUpdateQueue(): Sdc.Utils.Functions.QueueUtils {
+ return CompositionGraphGeneralUtils.graphUtilsUpdateQueue;
+ }
+ ;
+
+ /**
+ *
+ * @param blockAction - true/false if this is a block action
+ * @param instances
+ * @param component
+ */
+ public pushMultipleUpdateComponentInstancesRequestToQueue = (blockAction: boolean, instances: Array<Models.ComponentsInstances.ComponentInstance>, component: Models.Components.Component): void => {
+ if (blockAction) {
+ this.getGraphUtilsServerUpdateQueue().addBlockingUIAction(
+ () => component.updateMultipleComponentInstances(instances)
+ );
+ } else {
+ this.getGraphUtilsServerUpdateQueue().addNonBlockingUIAction(
+ () => component.updateMultipleComponentInstances(instances),
+ () => this.LoaderService.hideLoader('composition-graph'));
+ }
+ };
+
+ /**
+ * this function will update component instance data
+ * @param blockAction - true/false if this is a block action
+ * @param updatedInstance
+ */
+ public pushUpdateComponentInstanceActionToQueue = (component: Models.Components.Component, blockAction: boolean, updatedInstance: Models.ComponentsInstances.ComponentInstance): void => {
+
+ if (blockAction) {
+ this.LoaderService.showLoader('composition-graph');
+ this.getGraphUtilsServerUpdateQueue().addBlockingUIAction(
+ () => component.updateComponentInstance(updatedInstance)
+ );
+ } else {
+ this.getGraphUtilsServerUpdateQueue().addNonBlockingUIAction(
+ () => component.updateComponentInstance(updatedInstance),
+ () => this.LoaderService.hideLoader('composition-graph'));
+ }
+ };
+ }
+
+ CompositionGraphGeneralUtils.$inject = ['$q', 'LoaderService', 'CommonGraphUtils', 'MatchCapabilitiesRequirementsUtils'];
+} \ No newline at end of file
diff --git a/catalog-ui/app/scripts/directives/graphs-v2/composition-graph/utils/composition-graph-links-utils.ts b/catalog-ui/app/scripts/directives/graphs-v2/composition-graph/utils/composition-graph-links-utils.ts
new file mode 100644
index 0000000000..602e6b6def
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/graphs-v2/composition-graph/utils/composition-graph-links-utils.ts
@@ -0,0 +1,347 @@
+/**
+ * Created by obarda on 6/28/2016.
+ */
+/// <reference path="../../../../references"/>
+module Sdc.Graph.Utils {
+
+ import ImageCreatorService = Sdc.Utils.ImageCreatorService;
+ import Module = Sdc.Models.Module;
+ export class CompositionGraphLinkUtils {
+
+ private p2pVL:Models.Components.Component;
+ private mp2mpVL:Models.Components.Component;
+
+ constructor(private linksFactory:Sdc.Utils.LinksFactory,
+ private loaderService:Services.LoaderService,
+ private generalGraphUtils:Sdc.Graph.Utils.CompositionGraphGeneralUtils,
+ private leftPaletteLoaderService:Services.Components.LeftPaletteLoaderService,
+ private componentInstanceFactory:Sdc.Utils.ComponentInstanceFactory,
+ private nodesFactory:Sdc.Utils.NodesFactory,
+ private commonGraphUtils: Sdc.Graph.Utils.CommonGraphUtils,
+ private matchCapabilitiesRequirementsUtils: Graph.Utils.MatchCapabilitiesRequirementsUtils) {
+
+ this.initScopeVls();
+
+ }
+
+
+ /**
+ * Delete the link on server and then remove it from graph
+ * @param component
+ * @param releaseLoading - true/false release the loader when finished
+ * @param link - the link to delete
+ */
+ public deleteLink = (cy:Cy.Instance, component:Models.Components.Component, releaseLoading:boolean, link:Cy.CollectionEdges) => {
+
+ this.loaderService.showLoader('composition-graph');
+ let onSuccessDeleteRelation = (response) => {
+ cy.remove(link);
+ };
+
+ if (!releaseLoading) {
+ this.generalGraphUtils.getGraphUtilsServerUpdateQueue().addBlockingUIAction(
+ () => component.deleteRelation(link.data().relation).then(onSuccessDeleteRelation)
+ );
+ } else {
+ this.generalGraphUtils.getGraphUtilsServerUpdateQueue().addBlockingUIActionWithReleaseCallback(
+ () => component.deleteRelation(link.data().relation).then(onSuccessDeleteRelation),
+ () => this.loaderService.hideLoader('composition-graph'));
+ }
+ };
+
+ /**
+ * create the link on server and than draw it on graph
+ * @param link - the link to create
+ * @param cy
+ * @param component
+ */
+ public createLink = (link:Models.CompositionCiLinkBase, cy:Cy.Instance, component:Models.Components.Component):void => {
+
+ this.loaderService.showLoader('composition-graph');
+
+ let onSuccess:(response:Models.RelationshipModel) => void = (relation:Models.RelationshipModel) => {
+ link.setRelation(relation);
+ this.commonGraphUtils.insertLinkToGraph(cy, link);
+ };
+
+ link.updateLinkDirection();
+
+ this.generalGraphUtils.getGraphUtilsServerUpdateQueue().addBlockingUIActionWithReleaseCallback(
+ () => component.createRelation(link.relation).then(onSuccess),
+ () => this.loaderService.hideLoader('composition-graph')
+ );
+ };
+
+
+ public initScopeVls = ():void => {
+
+ let vls = this.leftPaletteLoaderService.getFullDataComponentList(Sdc.Utils.Constants.ResourceType.VL);
+ vls.forEach((item) => {
+ let key = _.find(Object.keys(item.capabilities), (key) => {
+ return _.includes(key.toLowerCase(), 'linkable');
+ });
+ let linkable = item.capabilities[key];
+ if (linkable) {
+ if (linkable[0].maxOccurrences == '2') {
+ this.p2pVL = _.find(vls, (component:Models.Components.Component) => {
+ return component.uniqueId === item.uniqueId;
+ });
+
+ } else {//assuming unbounded occurrences
+ this.mp2mpVL = _.find(vls, (component:Models.Components.Component) => {
+ return component.uniqueId === item.uniqueId;
+ });
+ }
+ }
+ });
+ };
+
+ private setVLlinks = (match:Models.MatchReqToReq, vl:Models.ComponentsInstances.ComponentInstance):Array<Models.RelationshipModel> => {
+
+ let relationship1 = new Models.Relationship();
+ let relationship2 = new Models.Relationship();
+ let newRelationshipModel1 = new Models.RelationshipModel();
+ let newRelationshipModel2 = new Models.RelationshipModel();
+
+ let capability:Models.Capability = vl.capabilities.findValueByKey('linkable')[0];
+ relationship1.setRelationProperties(capability, match.requirement);
+ relationship2.setRelationProperties(capability, match.secondRequirement);
+
+ newRelationshipModel1.setRelationshipModelParams(match.fromNode, vl.uniqueId, [relationship1]);
+ newRelationshipModel2.setRelationshipModelParams(match.toNode, vl.uniqueId, [relationship2]);
+
+ return [newRelationshipModel1, newRelationshipModel2];
+ };
+
+ private createVlinks = (cy:Cy.Instance, component:Models.Components.Component, matchReqToReq:Models.MatchReqToReq, vl:Models.Components.Component):void => {
+
+ let componentInstance:Models.ComponentsInstances.ComponentInstance = this.componentInstanceFactory.createComponentInstanceFromComponent(vl);
+ let fromNodePosition:Cy.Position = cy.getElementById(matchReqToReq.fromNode).relativePosition();
+ let toNodePosition:Cy.Position = cy.getElementById(matchReqToReq.toNode).relativePosition();
+ let location:Cy.Position = {
+ x: 0.5 * (fromNodePosition.x + toNodePosition.x),
+ y: 0.5 * (fromNodePosition.y + toNodePosition.y)
+ }
+
+ componentInstance.posX = location.x;
+ componentInstance.posY = location.y;
+
+ let onFailed:(error:any) => void = (error:any) => {
+ this.loaderService.hideLoader('composition-graph');
+ console.info('onFailed', error);
+ };
+
+ let onSuccess = (response:Models.ComponentsInstances.ComponentInstance):void => {
+
+ console.info('onSuccses', response);
+ response.requirements = new Models.RequirementsGroup(vl.requirements);
+ response.capabilities = new Models.CapabilitiesGroup(vl.capabilities);
+ response.componentVersion = vl.version;
+ response.setInstanceRC();
+
+ let newLinks = this.setVLlinks(matchReqToReq, response);
+ let newNode = this.nodesFactory.createNode(response);
+
+ this.commonGraphUtils.addComponentInstanceNodeToGraph(cy, newNode);
+
+ _.forEach(newLinks, (link) => {
+ let linkObg:Models.CompositionCiLinkBase = this.linksFactory.createGraphLink(cy, link, link.relationships[0]);
+ this.createLink(linkObg, cy, component);
+ });
+ };
+ component.createComponentInstance(componentInstance).then(onSuccess, onFailed);
+ };
+
+ private createSimpleLink = (match:Models.MatchReqToCapability, cy:Cy.Instance, component:Models.Components.Component):void => {
+ let newRelation:Models.RelationshipModel = match.matchToRelationModel();
+ let linkObg:Models.CompositionCiLinkBase = this.linksFactory.createGraphLink(cy,newRelation, newRelation.relationships[0]);
+ this.createLink(linkObg, cy, component);
+ };
+
+ public createLinkFromMenu = (cy:Cy.Instance, chosenMatch:Models.MatchBase, vl:Models.Components.Component, component:Models.Components.Component):void => {
+
+ if (chosenMatch) {
+ if (chosenMatch && chosenMatch instanceof Models.MatchReqToReq) {
+ this.createVlinks(cy, component, chosenMatch, vl); //TODO orit implement
+ }
+ if (chosenMatch && chosenMatch instanceof Models.MatchReqToCapability) {
+ this.createSimpleLink(chosenMatch, cy, component);
+ }
+ }
+ };
+
+
+ /**
+ * Filters the matches for UCPE links so that shown requirements and capabilites are only related to the selected ucpe-cp
+ * @param fromNode
+ * @param toNode
+ * @param matchesArray
+ * @returns {Array<Models.MatchBase>}
+ */
+ public filterUcpeLinks(fromNode: Models.Graph.CompositionCiNodeBase, toNode: Models.Graph.CompositionCiNodeBase, matchesArray: Array<Models.MatchBase>): any {
+
+ let matchLink: Array<Models.MatchBase>;
+
+ if (fromNode.isUcpePart) {
+ matchLink = _.filter(matchesArray, (match: Models.MatchBase) => {
+ return match.isOwner(fromNode.id);
+ });
+ }
+
+ if (toNode.isUcpePart) {
+ matchLink = _.filter(matchesArray, (match: Models.MatchBase) => {
+ return match.isOwner(toNode.id);
+ });
+ }
+ return matchLink ? matchLink : matchesArray;
+ }
+
+
+ /**
+ * open the connect link menu if the link drawn is valid - match requirements & capabilities
+ * @param cy
+ * @param fromNode
+ * @param toNode
+ * @returns {any}
+ */
+ public onLinkDrawn(cy:Cy.Instance, fromNode:Cy.CollectionFirstNode, toNode:Cy.CollectionFirstNode):Models.RelationMenuDirectiveObj {
+
+ if(!this.commonGraphUtils.nodeLocationsCompatible(cy, fromNode, toNode)){ return null; }
+ let linkModel:Array<Models.CompositionCiLinkBase> = this.generalGraphUtils.getAllCompositionCiLinks(cy);
+
+ let possibleRelations:Array<Models.MatchBase> = this.matchCapabilitiesRequirementsUtils.getMatchedRequirementsCapabilities(fromNode.data().componentInstance,
+ toNode.data().componentInstance, linkModel, this.mp2mpVL); //TODO orit - add p2p and mp2mp
+
+ //filter relations found to limit to specific ucpe-cp
+ possibleRelations = this.filterUcpeLinks(fromNode.data(), toNode.data(), possibleRelations);
+
+ //if found possibleRelations between the nodes we create relation menu directive and open the link menu
+ if (possibleRelations.length) {
+ let menuPosition = this.generalGraphUtils.getLinkMenuPosition(cy, toNode.renderedPoint());
+ return new Models.RelationMenuDirectiveObj(fromNode.data(), toNode.data(), this.mp2mpVL, this.p2pVL, menuPosition, possibleRelations);
+ }
+ return null;
+ };
+
+
+ /**
+ * when we drag instance in to UCPE or out of UCPE - get all links we need to delete - one node in ucpe and one node outside of ucpe
+ * @param node - the node we dragged into or out of the ucpe
+ */
+ public deleteLinksWhenNodeMovedFromOrToUCPE(component:Models.Components.Component, cy:Cy.Instance, nodeMoved:Cy.CollectionNodes, vlsPendingDeletion?:Cy.CollectionNodes):void {
+
+
+ let linksToDelete:Cy.CollectionElements = cy.collection();
+ _.forEach(nodeMoved.neighborhood('node'), (neighborNode)=>{
+
+ if(neighborNode.data().isUcpePart){ //existing connections to ucpe or ucpe-cp - we want to delete even though nodeLocationsCompatible will technically return true
+ linksToDelete = linksToDelete.add(nodeMoved.edgesWith(neighborNode)); // This will delete the ucpe-host-link, or the vl-ucpe-link if nodeMoved is vl
+ } else if(!this.commonGraphUtils.nodeLocationsCompatible(cy, nodeMoved, neighborNode)){ //connection to regular node or vl - check if locations are compatible
+ if(!vlsPendingDeletion || !vlsPendingDeletion.intersect(neighborNode).length){ //Check if this is a link to a VL pending deletion, to prevent double deletion of between the node moved and vl
+ linksToDelete = linksToDelete.add(nodeMoved.edgesWith(neighborNode));
+ }
+ }
+ });
+
+
+
+ linksToDelete.each((i, link)=>{
+ this.deleteLink(cy, component, false, link);
+ });
+
+ };
+
+
+ /**
+ * Creates a hostedOn link between a VF and UCPE
+ * @param component
+ * @param cy
+ * @param ucpeNode
+ * @param vfNode
+ */
+ public createVfToUcpeLink = (component: Models.Components.Component, cy:Cy.Instance, ucpeNode:Models.Graph.NodeUcpe, vfNode:Models.Graph.CompositionCiNodeVf):void => {
+ let hostedOnMatch:Models.MatchReqToCapability = this.generalGraphUtils.canBeHostedOn(cy, ucpeNode.componentInstance, vfNode.componentInstance);
+ /* create relation */
+ let newRelation = new Models.RelationshipModel();
+ newRelation.fromNode = ucpeNode.id;
+ newRelation.toNode = vfNode.id;
+
+ let link:Models.CompositionCiLinkBase = this.linksFactory.createUcpeHostLink(newRelation);
+ link.relation = hostedOnMatch.matchToRelationModel();
+ this.createLink(link, cy, component);
+ };
+
+
+ /**
+ * Handles click event on links.
+ * If one edge selected: do nothing.
+ /*Two edges selected - always select all
+ /* Three or more edges: first click - select all, secondary click - select single.
+ * @param cy
+ * @param event
+ */
+ public handleLinkClick(cy:Cy.Instance, event : Cy.EventObject) {
+ if(cy.$('edge:selected').length > 2 && event.cyTarget[0].selected()) {
+ cy.$(':selected').unselect();
+ } else {
+
+ let vl: Cy.CollectionNodes = event.cyTarget[0].target('.vl-node');
+ let connectedEdges:Cy.CollectionEdges = vl.connectedEdges();
+ if (vl.length && connectedEdges.length > 1) {
+
+ setTimeout(() => {
+ vl.select();
+ connectedEdges.select();
+ }, 0);
+ }
+ }
+
+ }
+
+
+ /**
+ * Calculates the position for the menu that modifies an existing link
+ * @param event
+ * @param elementWidth
+ * @param elementHeight
+ * @returns {Sdc.Models.Graph.Point}
+ */
+ public calculateLinkMenuPosition(event, elementWidth, elementHeight): Sdc.Models.Graph.Point {
+ let point: Sdc.Models.Graph.Point = new Sdc.Models.Graph.Point(event.originalEvent.x,event.originalEvent.y);
+ if(event.originalEvent.view.screen.height-elementHeight<point.y){
+ point.y = event.originalEvent.view.screen.height-elementHeight;
+ }
+ if(event.originalEvent.view.screen.width-elementWidth<point.x){
+ point.x = event.originalEvent.view.screen.width-elementWidth;
+ }
+ return point;
+ };
+
+
+ /**
+ * Gets the menu that is displayed when you click an existing link.
+ * @param link
+ * @param event
+ * @returns {Models.LinkMenu}
+ */
+ public getModifyLinkMenu(link:Cy.CollectionFirstEdge, event:Cy.EventObject):Models.LinkMenu{
+ let point:Sdc.Models.Graph.Point = this.calculateLinkMenuPosition(event,Sdc.Utils.Constants.GraphUIObjects.MENU_LINK_VL_WIDTH_OFFSET,Sdc.Utils.Constants.GraphUIObjects.MENU_LINK_VL_HEIGHT_OFFSET);
+ let menu:Models.LinkMenu = new Models.LinkMenu(point, true, link);
+ return menu;
+ };
+
+ }
+
+
+
+ CompositionGraphLinkUtils.$inject = [
+ 'LinksFactory',
+ 'LoaderService',
+ 'CompositionGraphGeneralUtils',
+ 'LeftPaletteLoaderService',
+ 'ComponentInstanceFactory',
+ 'NodesFactory',
+ 'CommonGraphUtils',
+ 'MatchCapabilitiesRequirementsUtils'
+ ];
+} \ No newline at end of file
diff --git a/catalog-ui/app/scripts/directives/graphs-v2/composition-graph/utils/composition-graph-nodes-utils.ts b/catalog-ui/app/scripts/directives/graphs-v2/composition-graph/utils/composition-graph-nodes-utils.ts
new file mode 100644
index 0000000000..95c31d16b1
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/graphs-v2/composition-graph/utils/composition-graph-nodes-utils.ts
@@ -0,0 +1,220 @@
+/**
+ * Created by obarda on 11/9/2016.
+ */
+
+/// <reference path="../../../../references"/>
+module Sdc.Graph.Utils {
+
+ export class CompositionGraphNodesUtils {
+ constructor(private NodesFactory:Sdc.Utils.NodesFactory, private $log:ng.ILogService,
+ private GeneralGraphUtils:Graph.Utils.CompositionGraphGeneralUtils,
+ private commonGraphUtils: Sdc.Graph.Utils.CommonGraphUtils,
+ private eventListenerService: Services.EventListenerService,
+ private loaderService:Services.LoaderService) {
+
+ }
+
+ /**
+ * Returns component instances for all nodes passed in
+ * @param nodes - Cy nodes
+ * @returns {any[]}
+ */
+ public getAllNodesData(nodes:Cy.CollectionNodes) {
+ return _.map(nodes, (node:Cy.CollectionFirstNode)=> {
+ return node.data();
+ })
+ };
+
+ /**
+ * Deletes component instances on server and then removes it from the graph as well
+ * @param cy
+ * @param component
+ * @param nodeToDelete
+ */
+ public deleteNode(cy: Cy.Instance, component:Models.Components.Component, nodeToDelete:Cy.CollectionNodes):void {
+
+ this.loaderService.showLoader('composition-graph');
+ let onSuccess:(response:Models.ComponentsInstances.ComponentInstance) => void = (response:Models.ComponentsInstances.ComponentInstance) => {
+ console.info('onSuccess', response);
+
+ //if node to delete is a UCPE, remove all children (except UCPE-CPs) and remove their "hostedOn" links
+ if (nodeToDelete.data().isUcpe){
+ _.each(cy.nodes('[?isInsideGroup]'), (node)=>{
+ this.eventListenerService.notifyObservers(Sdc.Utils.Constants.GRAPH_EVENTS.ON_REMOVE_NODE_FROM_UCPE, node, nodeToDelete);
+ });
+ }
+
+ //check whether the node is connected to any VLs that only have one other connection. If so, delete that VL as well
+ if(!(nodeToDelete.data() instanceof Sdc.Models.Graph.CompositionCiNodeVl)){
+ let connectedVls:Array<Cy.CollectionFirstNode> = this.getConnectedVlToNode(nodeToDelete);
+ this.handleConnectedVlsToDelete(connectedVls);
+ }
+
+ //update UI
+ cy.remove(nodeToDelete);
+
+ };
+
+ let onFailed:(response:any) => void = (response:any) => {
+ console.info('onFailed', response);
+ };
+
+
+ this.GeneralGraphUtils.getGraphUtilsServerUpdateQueue().addBlockingUIActionWithReleaseCallback(
+ () => component.deleteComponentInstance(nodeToDelete.data().componentInstance.uniqueId).then(onSuccess, onFailed),
+ () => this.loaderService.hideLoader('composition-graph')
+ );
+
+ };
+
+
+ /**
+ * Finds all VLs connected to a single node
+ * @param node
+ * @returns {Array<Cy.CollectionFirstNode>}
+ */
+ public getConnectedVlToNode = (node: Cy.CollectionNodes): Array<Cy.CollectionFirstNode> => {
+ let connectedVls: Array<Cy.CollectionFirstNode> = new Array<Cy.CollectionFirstNode>();
+ _.forEach(node.connectedEdges().connectedNodes(), (node: Cy.CollectionFirstNode) => {
+ if (node.data() instanceof Models.Graph.CompositionCiNodeVl) {
+ connectedVls.push(node);
+ }
+ });
+ return connectedVls;
+ };
+
+
+ /**
+ * Delete all VLs that have only two connected nodes (this function is called when deleting a node)
+ * @param connectedVls
+ */
+ public handleConnectedVlsToDelete = (connectedVls: Array<Cy.CollectionFirstNode>) => {
+ _.forEach(connectedVls, (vlToDelete: Cy.CollectionNodes) => {
+
+ if (vlToDelete.connectedEdges().length === 2) { // if vl connected only to 2 nodes need to delete the vl
+ this.eventListenerService.notifyObservers(Sdc.Utils.Constants.GRAPH_EVENTS.ON_DELETE_COMPONENT_INSTANCE, vlToDelete.data().componentInstance);
+ }
+ });
+ };
+
+
+ /**
+ * This function is called when moving a node in or out of UCPE.
+ * Deletes all connected VLs that have less than 2 valid connections remaining after the move
+ * Returns the collection of vls that are in the process of deletion (async) to prevent duplicate calls while deletion is in progress
+ * @param component
+ * @param cy
+ * @param node - node that was moved in/out of ucpe
+ */
+ public deleteNodeVLsUponMoveToOrFromUCPE = (component:Models.Components.Component, cy:Cy.Instance, node:Cy.CollectionNodes):Cy.CollectionNodes =>{
+ if(node.data() instanceof Models.Graph.CompositionCiNodeVl){ return;}
+
+ let connectedVLsToDelete:Cy.CollectionNodes = cy.collection();
+ _.forEach(node.neighborhood('node'), (connectedNode) => {
+
+ //Find all neighboring nodes that are VLs
+ if(connectedNode.data() instanceof Models.Graph.CompositionCiNodeVl){
+
+ //check VL's neighbors to see if it has 2 or more nodes whose location is compatible with VL (regardless of whether VL is in or out of UCPE)
+ let compatibleNodeCount = 0;
+ let vlNeighborhood = connectedNode.neighborhood('node');
+ _.forEach(vlNeighborhood, (vlNeighborNode)=>{
+ if(this.commonGraphUtils.nodeLocationsCompatible(cy, connectedNode, vlNeighborNode)) {
+ compatibleNodeCount ++;
+ }
+ });
+
+ if(compatibleNodeCount < 2) {
+ connectedVLsToDelete = connectedVLsToDelete.add(connectedNode);
+ }
+ }
+ });
+
+ connectedVLsToDelete.each((i, vlToDelete:Cy.CollectionNodes)=>{
+ this.deleteNode(cy, component, vlToDelete);
+ });
+ return connectedVLsToDelete;
+ };
+
+ /**
+ * This function will update nodes position. if the new position is into or out of ucpe, the node will trigger the ucpe events
+ * @param cy
+ * @param component
+ * @param nodesMoved - the node/multiple nodes now moved by the user
+ */
+ public onNodesPositionChanged = (cy: Cy.Instance, component:Models.Components.Component, nodesMoved: Cy.CollectionNodes): void => {
+
+ if (nodesMoved.length === 0) {
+ return;
+ }
+
+ let isValidMove:boolean = this.GeneralGraphUtils.isGroupValidDrop(cy, nodesMoved);
+ if (isValidMove) {
+
+ this.$log.debug(`composition-graph::ValidDrop:: updating node position`);
+ let instancesToUpdateInNonBlockingAction:Array<Models.ComponentsInstances.ComponentInstance> = new Array<Models.ComponentsInstances.ComponentInstance>();
+
+ _.each(nodesMoved, (node:Cy.CollectionFirstNode)=> { //update all nodes new position
+
+ if(node.data().isUcpePart && !node.data().isUcpe){ return; }//No need to update UCPE-CPs
+
+ //update position
+ let newPosition:Cy.Position = this.commonGraphUtils.getNodePosition(node);
+ node.data().componentInstance.updatePosition(newPosition.x, newPosition.y);
+
+ //check if node moved to or from UCPE
+ let ucpe = this.commonGraphUtils.isInUcpe(node.cy(), node.boundingbox());
+ if(node.data().isInsideGroup || ucpe.length) {
+ this.handleUcpeChildMove(node, ucpe, instancesToUpdateInNonBlockingAction);
+ } else {
+ instancesToUpdateInNonBlockingAction.push(node.data().componentInstance);
+ }
+
+ });
+
+ if (instancesToUpdateInNonBlockingAction.length > 0) {
+ this.GeneralGraphUtils.pushMultipleUpdateComponentInstancesRequestToQueue(false, instancesToUpdateInNonBlockingAction, component);
+ }
+ } else {
+ this.$log.debug(`composition-graph::notValidDrop:: node return to latest position`);
+ //reset nodes position
+ nodesMoved.positions((i, node) => {
+ return {
+ x: +node.data().componentInstance.posX,
+ y: +node.data().componentInstance.posY
+ };
+ })
+ }
+
+ this.GeneralGraphUtils.getGraphUtilsServerUpdateQueue().addBlockingUIActionWithReleaseCallback(() => {
+ }, () => {
+ this.loaderService.hideLoader('composition-graph');
+ });
+
+ };
+
+ /**
+ * Checks whether the node has been added or removed from UCPE and triggers appropriate events
+ * @param node - node moved
+ * @param ucpeContainer - UCPE container that the node has been moved to. When moving a node out of ucpe, param will be empty
+ * @param instancesToUpdateInNonBlockingAction
+ */
+ public handleUcpeChildMove(node:Cy.CollectionFirstNode, ucpeContainer:Cy.CollectionElements, instancesToUpdateInNonBlockingAction:Array<Models.ComponentsInstances.ComponentInstance>){
+
+ if(node.data().isInsideGroup){
+ if(ucpeContainer.length){ //moving node within UCPE. Simply update position
+ this.commonGraphUtils.updateUcpeChildPosition(<Cy.CollectionNodes>node, ucpeContainer);
+ instancesToUpdateInNonBlockingAction.push(node.data().componentInstance);
+ } else { //removing node from UCPE. Notify observers
+ this.eventListenerService.notifyObservers(Sdc.Utils.Constants.GRAPH_EVENTS.ON_REMOVE_NODE_FROM_UCPE, node, ucpeContainer);
+ }
+ } else if(!node.data().isInsideGroup && ucpeContainer.length && !node.data().isUcpePart){ //adding node to UCPE
+ this.eventListenerService.notifyObservers(Sdc.Utils.Constants.GRAPH_EVENTS.ON_INSERT_NODE_TO_UCPE, node, ucpeContainer, true);
+ }
+ }
+
+ }
+
+
+ CompositionGraphNodesUtils.$inject = ['NodesFactory', '$log', 'CompositionGraphGeneralUtils', 'CommonGraphUtils', 'EventListenerService', 'LoaderService'];
+} \ No newline at end of file
diff --git a/catalog-ui/app/scripts/directives/graphs-v2/composition-graph/utils/match-capability-requierment-utils.ts b/catalog-ui/app/scripts/directives/graphs-v2/composition-graph/utils/match-capability-requierment-utils.ts
new file mode 100644
index 0000000000..5a401df317
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/graphs-v2/composition-graph/utils/match-capability-requierment-utils.ts
@@ -0,0 +1,265 @@
+/**
+ * Created by obarda on 1/1/2017.
+ */
+/// <reference path="../../../../references"/>
+module Sdc.Graph.Utils {
+
+ export class MatchCapabilitiesRequirementsUtils {
+
+ constructor() {
+ }
+
+
+
+ public static linkable(requirement1:Models.Requirement, requirement2:Models.Requirement, vlCapability:Models.Capability):boolean {
+ return MatchCapabilitiesRequirementsUtils.isMatch(requirement1, vlCapability) && MatchCapabilitiesRequirementsUtils.isMatch(requirement2, vlCapability);
+ };
+
+
+ /**
+ * Shows + icon in corner of each node passed in
+ * @param filteredNodesData
+ * @param cy
+ */
+ public highlightMatchingComponents(filteredNodesData, cy:Cy.Instance) {
+ _.each(filteredNodesData, (data:any) => {
+ let node = cy.getElementById(data.id);
+ cy.emit('showhandle', [node]);
+ });
+ }
+
+ /**
+ * Adds opacity to each node that cannot be linked to hovered node
+ * @param filteredNodesData
+ * @param nodesData
+ * @param cy
+ * @param hoveredNodeData
+ */
+ public fadeNonMachingComponents(filteredNodesData, nodesData, cy:Cy.Instance, hoveredNodeData?) {
+ let fadeNodes = _.xorWith(nodesData, filteredNodesData, (node1, node2) => {
+ return node1.id === node2.id;
+ });
+ if (hoveredNodeData) {
+ _.remove(fadeNodes, hoveredNodeData);
+ }
+ cy.batch(()=> {
+ _.each(fadeNodes, (node) => {
+ cy.getElementById(node.id).style({'background-image-opacity': 0.4});
+ });
+ })
+ }
+
+ /**
+ * Resets all nodes to regular opacity
+ * @param cy
+ */
+ public resetFadedNodes(cy:Cy.Instance) {
+ cy.batch(()=> {
+ cy.nodes().style({'background-image-opacity': 1});
+ })
+ }
+
+ // -------------------------------------------ALL FUNCTIONS NEED REFACTORING---------------------------------------------------------------//
+
+ private static requirementFulfilled(fromNodeId:string, requirement:any, links:Array<Models.CompositionCiLinkBase>):boolean {
+ return _.some(links, {
+ 'relation': {
+ 'fromNode': fromNodeId,
+ 'relationships': [{
+ 'requirementOwnerId': requirement.ownerId,
+ 'requirement': requirement.name,
+ 'relationship': {
+ 'type': requirement.relationship
+ }
+ }
+ ]
+ }
+ });
+ };
+
+ private static isMatch(requirement:Models.Requirement, capability:Models.Capability):boolean {
+ if (capability.type === requirement.capability) {
+ if (requirement.node) {
+ if (_.includes(capability.capabilitySources, requirement.node)) {
+ return true;
+ }
+ } else {
+ return true;
+ }
+ }
+ return false;
+ };
+
+ private getFromToMatches(requirements1:Models.RequirementsGroup,
+ requirements2:Models.RequirementsGroup,
+ capabilities:Models.CapabilitiesGroup,
+ links:Array<Models.CompositionCiLinkBase>,
+ fromId:string,
+ toId:string,
+ vlCapability?:Models.Capability):Array<Models.MatchBase> {
+ let matches:Array<Models.MatchBase> = new Array<Models.MatchBase>();
+ _.forEach(requirements1, (requirementValue:Array<Models.Requirement>, key) => {
+ _.forEach(requirementValue, (requirement:Models.Requirement) => {
+ if (requirement.name !== "dependency" && !MatchCapabilitiesRequirementsUtils.requirementFulfilled(fromId, requirement, links)) {
+ _.forEach(capabilities, (capabilityValue:Array<Models.Capability>, key) => {
+ _.forEach(capabilityValue, (capability:Models.Capability) => {
+ if (MatchCapabilitiesRequirementsUtils.isMatch(requirement, capability)) {
+ let match:Models.MatchReqToCapability = new Models.MatchReqToCapability(requirement, capability, true, fromId, toId);
+ matches.push(match);
+ }
+ });
+ });
+ if (vlCapability) {
+ _.forEach(requirements2, (requirement2Value:Array<Models.Requirement>, key) => {
+ _.forEach(requirement2Value, (requirement2:Models.Requirement) => {
+ if (!MatchCapabilitiesRequirementsUtils.requirementFulfilled(toId, requirement2, links) && MatchCapabilitiesRequirementsUtils.linkable(requirement, requirement2, vlCapability)) {
+ let match:Models.MatchReqToReq = new Models.MatchReqToReq(requirement, requirement2, true, fromId, toId);
+ matches.push(match);
+ }
+ });
+ });
+ }
+ }
+ });
+ });
+ return matches;
+ }
+
+ private getToFromMatches(requirements:Models.RequirementsGroup, capabilities:Models.CapabilitiesGroup, links:Array<Models.CompositionCiLinkBase>, fromId:string, toId:string):Array<Models.MatchReqToCapability> {
+ let matches:Array<Models.MatchReqToCapability> = [];
+ _.forEach(requirements, (requirementValue:Array<Models.Requirement>, key) => {
+ _.forEach(requirementValue, (requirement:Models.Requirement) => {
+ if (requirement.name !== "dependency" && !MatchCapabilitiesRequirementsUtils.requirementFulfilled(toId, requirement, links)) {
+ _.forEach(capabilities, (capabilityValue:Array<Models.Capability>, key) => {
+ _.forEach(capabilityValue, (capability:Models.Capability) => {
+ if (MatchCapabilitiesRequirementsUtils.isMatch(requirement, capability)) {
+ let match:Models.MatchReqToCapability = new Models.MatchReqToCapability(requirement, capability, false, toId, fromId);
+ matches.push(match);
+ }
+ });
+ });
+ }
+ });
+ });
+ return matches;
+ }
+
+ public getMatchedRequirementsCapabilities(fromComponentInstance:Models.ComponentsInstances.ComponentInstance,
+ toComponentInstance:Models.ComponentsInstances.ComponentInstance,
+ links:Array<Models.CompositionCiLinkBase>,
+ vl?:Models.Components.Component):Array<Models.MatchBase> {//TODO allow for VL array
+ let linkCapability;
+ if (vl) {
+ let linkCapabilities:Array<Models.Capability> = vl.capabilities.findValueByKey('linkable');
+ if (linkCapabilities) {
+ linkCapability = linkCapabilities[0];
+ }
+ }
+ let fromToMatches:Array<Models.MatchBase> = this.getFromToMatches(fromComponentInstance.requirements,
+ toComponentInstance.requirements,
+ toComponentInstance.capabilities,
+ links,
+ fromComponentInstance.uniqueId,
+ toComponentInstance.uniqueId,
+ linkCapability);
+ let toFromMatches:Array<Models.MatchReqToCapability> = this.getToFromMatches(toComponentInstance.requirements,
+ fromComponentInstance.capabilities,
+ links,
+ fromComponentInstance.uniqueId,
+ toComponentInstance.uniqueId);
+
+ return fromToMatches.concat(toFromMatches);
+ }
+
+
+
+
+
+ /**
+ * Step I: Check if capabilities of component match requirements of nodeDataArray
+ * 1. Get component capabilities and loop on each capability
+ * 2. Inside the loop, perform another loop on all nodeDataArray, and fetch the requirements for each one
+ * 3. Loop on the requirements, and verify match (see in code the rules)
+ *
+ * Step II: Check if requirements of component match capabilities of nodeDataArray
+ * 1. Get component requirements and loop on each requirement
+ * 2.
+ *
+ * @param component - this is the hovered resource of the left panel of composition screen
+ * @param nodeDataArray - Array of resource instances that are on the canvas
+ * @param links -getMatchedRequirementsCapabilities
+ * @param vl -
+ * @returns {any[]|T[]}
+ */
+ public findByMatchingCapabilitiesToRequirements(component:Models.Components.Component,
+ nodeDataArray:Array<Models.Graph.CompositionCiNodeBase>,
+ links:Array<Models.CompositionCiLinkBase>,
+ vl?:Models.Components.Component):Array<any> {//TODO allow for VL array
+ let res = [];
+
+ // STEP I
+ {
+ let capabilities:any = component.capabilities;
+ _.forEach(capabilities, (capabilityValue:Array<any>, capabilityKey)=> {
+ _.forEach(capabilityValue, (capability)=> {
+ _.forEach(nodeDataArray, (node:Models.Graph.CompositionCiNodeBase)=> {
+ if (node && node.componentInstance) {
+ let requirements:any = node.componentInstance.requirements;
+ let fromNodeId:string = node.componentInstance.uniqueId;
+ _.forEach(requirements, (requirementValue:Array<any>, requirementKey)=> {
+ _.forEach(requirementValue, (requirement)=> {
+ if (requirement.name !== "dependency" && MatchCapabilitiesRequirementsUtils.isMatch(requirement, capability)
+ && !MatchCapabilitiesRequirementsUtils.requirementFulfilled(fromNodeId, requirement, links)) {
+ res.push(node);
+ }
+ });
+ });
+ }
+ });
+ });
+ });
+ }
+
+ // STEP II
+ {
+ let requirements:any = component.requirements;
+ let fromNodeId:string = component.uniqueId;
+ let linkCapability:Array<Models.Capability> = vl ? vl.capabilities.findValueByKey('linkable') : undefined;
+
+ _.forEach(requirements, (requirementValue:Array<any>, requirementKey)=> {
+ _.forEach(requirementValue, (requirement)=> {
+ if (requirement.name !== "dependency" && !MatchCapabilitiesRequirementsUtils.requirementFulfilled(fromNodeId, requirement, links)) {
+ _.forEach(nodeDataArray, (node:any)=> {
+ if (node && node.componentInstance && node.category !== 'groupCp') {
+ let capabilities:any = node.componentInstance.capabilities;
+ _.forEach(capabilities, (capabilityValue:Array<any>, capabilityKey)=> {
+ _.forEach(capabilityValue, (capability)=> {
+ if (MatchCapabilitiesRequirementsUtils.isMatch(requirement, capability)) {
+ res.push(node);
+ }
+ });
+ });
+ if (linkCapability) {
+ let linkRequirements = node.componentInstance.requirements;
+ _.forEach(linkRequirements, (value:Array<any>, key)=> {
+ _.forEach(value, (linkRequirement)=> {
+ if (!MatchCapabilitiesRequirementsUtils.requirementFulfilled(node.componentInstance.uniqueId, linkRequirement, links)
+ && MatchCapabilitiesRequirementsUtils.linkable(requirement, linkRequirement, linkCapability[0])) {
+ res.push(node);
+ }
+ });
+ });
+ }
+ }
+ });
+ }
+ });
+ });
+ }
+
+ return _.uniq(res);
+ };
+ }
+
+ MatchCapabilitiesRequirementsUtils.$inject = [];
+} \ No newline at end of file
diff --git a/catalog-ui/app/scripts/directives/graphs-v2/deployment-graph/deployment-graph.directive.ts b/catalog-ui/app/scripts/directives/graphs-v2/deployment-graph/deployment-graph.directive.ts
new file mode 100644
index 0000000000..d6d4aef374
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/graphs-v2/deployment-graph/deployment-graph.directive.ts
@@ -0,0 +1,114 @@
+/**
+ * Created by obarda on 12/19/2016.
+ */
+/// <reference path="../../../references"/>
+module Sdc.Directives {
+
+ import Util = jasmine.Util;
+
+ interface IDeploymentGraphScope extends ng.IScope {
+ component:Models.Components.Component;
+ }
+
+ export class DeploymentGraph implements ng.IDirective {
+ private _cy:Cy.Instance;
+
+ constructor(private NodesFactory:Utils.NodesFactory, private commonGraphUtils:Graph.Utils.CommonGraphUtils,
+ private deploymentGraphGeneralUtils:Graph.Utils.DeploymentGraphGeneralUtils, private ComponentInstanceFactory: Sdc.Utils.ComponentInstanceFactory) {
+ }
+
+ restrict = 'E';
+ templateUrl = '/app/scripts/directives/graphs-v2/deployment-graph/deployment-graph.html';
+ scope = {
+ component: '=',
+ isViewOnly: '='
+ };
+
+ link = (scope:IDeploymentGraphScope, el:JQuery) => {
+ if(scope.component.isResource()) {
+ this.loadGraph(scope, el);
+ this.registerGraphEvents();
+ }
+ };
+
+
+ public initGraphNodes = (cy:Cy.Instance, component:Models.Components.Component):void => {
+ if (component.groups) { // Init module nodes
+ _.each(component.groups, (groupModule:Models.Module) => {
+ let moduleNode = this.NodesFactory.createModuleNode(groupModule);
+ this.commonGraphUtils.addNodeToGraph(cy, moduleNode);
+
+ });
+ }
+ _.each(component.componentInstances, (instance:Models.ComponentsInstances.ComponentInstance) => { // Init component instance nodes
+ let componentInstanceNode = this.NodesFactory.createNode(instance);
+ componentInstanceNode.parent = this.deploymentGraphGeneralUtils.findInstanceModule(component.groups, instance.uniqueId);
+ if (componentInstanceNode.parent) { // we are not drawing instances that are not a part of a module
+ this.commonGraphUtils.addComponentInstanceNodeToGraph(cy, componentInstanceNode);
+ }
+ });
+
+ // This is a special functionality to pass the cytoscape default behavior - we can't create Parent module node without children's
+ // so we must add an empty dummy child node
+ _.each(this._cy.nodes('[?isGroup]'), (moduleNode: Cy.CollectionFirstNode) => {
+ if (!moduleNode.isParent()) {
+ let dummyInstance = this.ComponentInstanceFactory.createEmptyComponentInstance();
+ let componentInstanceNode = this.NodesFactory.createNode(dummyInstance);
+ componentInstanceNode.parent = moduleNode.id();
+ let dummyNode = this.commonGraphUtils.addNodeToGraph(cy, componentInstanceNode, moduleNode.position());
+ dummyNode.addClass('dummy-node');
+ }
+ })
+ };
+
+ private registerGraphEvents() {
+
+ this._cy.on('afterExpand', (event) => {
+ event.cyTarget.qtip({});
+ });
+
+ this._cy.on('afterCollapse', (event) => {
+ this.commonGraphUtils.initNodeTooltip(event.cyTarget);
+ });
+ }
+
+ private loadGraph = (scope:IDeploymentGraphScope, el:JQuery) => {
+
+ let graphEl = el.find('.sdc-deployment-graph-wrapper');
+ this._cy = cytoscape({
+ container: graphEl,
+ style: Sdc.Graph.Utils.ComponentIntanceNodesStyle.getCompositionGraphStyle().concat(Sdc.Graph.Utils.ModulesNodesStyle.getModuleGraphStyle()),
+ zoomingEnabled: false,
+ selectionType: 'single',
+
+ });
+
+ //adding expand collapse extension
+ this._cy.expandCollapse({
+ layoutBy: {
+ name: "grid",
+ animate: true,
+ randomize: false,
+ fit: true
+ },
+ fisheye: false,
+ undoable: false,
+ expandCollapseCueSize: 18,
+ expandCueImage: Sdc.Utils.Constants.IMAGE_PATH + '/styles/images/resource-icons/' + 'closeModule.png',
+ collapseCueImage: Sdc.Utils.Constants.IMAGE_PATH + '/styles/images/resource-icons/' + 'openModule.png',
+ expandCollapseCueSensitivity: 2,
+ cueOffset: -20
+ });
+
+ this.initGraphNodes(this._cy, scope.component); //creating instances nodes
+ this.commonGraphUtils.initGraphLinks(this._cy, scope.component.componentInstancesRelations);
+ this._cy.collapseAll();
+ };
+
+ public static factory = (NodesFactory:Utils.NodesFactory, CommonGraphUtils:Graph.Utils.CommonGraphUtils, DeploymentGraphGeneralUtils:Graph.Utils.DeploymentGraphGeneralUtils, ComponentInstanceFactory: Utils.ComponentInstanceFactory) => {
+ return new DeploymentGraph(NodesFactory, CommonGraphUtils, DeploymentGraphGeneralUtils, ComponentInstanceFactory)
+ }
+ }
+
+ DeploymentGraph.factory.$inject = ['NodesFactory', 'CommonGraphUtils', 'DeploymentGraphGeneralUtils', 'ComponentInstanceFactory'];
+} \ No newline at end of file
diff --git a/catalog-ui/app/scripts/directives/graphs-v2/deployment-graph/deployment-graph.html b/catalog-ui/app/scripts/directives/graphs-v2/deployment-graph/deployment-graph.html
new file mode 100644
index 0000000000..55e1c131f4
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/graphs-v2/deployment-graph/deployment-graph.html
@@ -0,0 +1,2 @@
+<div class="sdc-deployment-graph-wrapper" ng-class="{'view-only':isViewOnly}">
+</div> \ No newline at end of file
diff --git a/catalog-ui/app/scripts/directives/graphs-v2/deployment-graph/deployment-graph.less b/catalog-ui/app/scripts/directives/graphs-v2/deployment-graph/deployment-graph.less
new file mode 100644
index 0000000000..ff8fc46380
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/graphs-v2/deployment-graph/deployment-graph.less
@@ -0,0 +1,14 @@
+deployment-graph {
+ display: block;
+ height:100%;
+ width: 100%;
+
+ .sdc-deployment-graph-wrapper {
+ height:100%;
+ width: 100%;
+ }
+
+ .view-only{
+ background-color:rgb(248, 248, 248);
+ }
+} \ No newline at end of file
diff --git a/catalog-ui/app/scripts/directives/graphs-v2/deployment-graph/deployment-utils/deployment-graph-general-utils.ts b/catalog-ui/app/scripts/directives/graphs-v2/deployment-graph/deployment-utils/deployment-graph-general-utils.ts
new file mode 100644
index 0000000000..3ad9da56be
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/graphs-v2/deployment-graph/deployment-utils/deployment-graph-general-utils.ts
@@ -0,0 +1,24 @@
+/**
+ * Created by obarda on 12/21/2016.
+ */
+/// <reference path="../../../../references"/>
+module Sdc.Graph.Utils {
+
+ export class DeploymentGraphGeneralUtils {
+
+ constructor() {
+
+ }
+
+ public findInstanceModule = (groupsArray:Array<Models.Module>, componentInstanceId:string):string => {
+ let parentGroup:Sdc.Models.Module = _.find(groupsArray, (group:Sdc.Models.Module) => {
+ return _.find(group.members, (member) => {
+ return member === componentInstanceId;
+ });
+ });
+ return parentGroup ? parentGroup.uniqueId : "";
+ };
+ }
+
+ DeploymentGraphGeneralUtils.$inject = [];
+} \ No newline at end of file
diff --git a/catalog-ui/app/scripts/directives/graphs-v2/image-creator/image-creator.service.ts b/catalog-ui/app/scripts/directives/graphs-v2/image-creator/image-creator.service.ts
new file mode 100644
index 0000000000..e3b17e163d
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/graphs-v2/image-creator/image-creator.service.ts
@@ -0,0 +1,46 @@
+module Sdc.Utils {
+ export class ImageCreatorService {
+ static '$inject' = ['$q'];
+ private _canvas: HTMLCanvasElement;
+
+ constructor(private $q: ng.IQService) {
+ this._canvas = <HTMLCanvasElement>$('<canvas>')[0];
+ this._canvas.setAttribute('style', 'display:none');
+
+ let body = document.getElementsByTagName('body')[0];
+ body.appendChild(this._canvas);
+ }
+
+ getImageBase64(imageBaseUri: string, imageLayerUri: string): ng.IPromise<string> {
+ let deferred = this.$q.defer();
+ let imageBase = new Image();
+ let imageLayer = new Image();
+ let imagesLoaded = 0;
+ let onImageLoaded = () => {
+ imagesLoaded++;
+
+ if (imagesLoaded < 2) {
+ return;
+ }
+ this._canvas.setAttribute('width', imageBase.width.toString());
+ this._canvas.setAttribute('height', imageBase.height.toString());
+
+ let canvasCtx = this._canvas.getContext('2d');
+ canvasCtx.clearRect(0, 0, this._canvas.width, this._canvas.height);
+
+ canvasCtx.drawImage(imageBase, 0, 0, imageBase.width, imageBase.height);
+ canvasCtx.drawImage(imageLayer, imageBase.width - imageLayer.width, 0, imageLayer.width, imageLayer.height);
+
+ let base64Image = this._canvas.toDataURL();
+ deferred.resolve(base64Image);
+ };
+
+ imageBase.onload = onImageLoaded;
+ imageLayer.onload = onImageLoaded;
+ imageBase.src = imageBaseUri;
+ imageLayer.src = imageLayerUri;
+
+ return deferred.promise;
+ }
+ }
+} \ No newline at end of file
diff --git a/catalog-ui/app/scripts/directives/graphs-v2/palette/interfaces/i-dragdrop-event.d.ts b/catalog-ui/app/scripts/directives/graphs-v2/palette/interfaces/i-dragdrop-event.d.ts
new file mode 100644
index 0000000000..26c042611c
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/graphs-v2/palette/interfaces/i-dragdrop-event.d.ts
@@ -0,0 +1,7 @@
+interface IDragDropEvent extends JQueryEventObject {
+ dataTransfer: any;
+ toElement: {
+ naturalWidth: number;
+ naturalHeight: number;
+ }
+} \ No newline at end of file
diff --git a/catalog-ui/app/scripts/directives/graphs-v2/palette/palette.directive.ts b/catalog-ui/app/scripts/directives/graphs-v2/palette/palette.directive.ts
new file mode 100644
index 0000000000..c00da6d1df
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/graphs-v2/palette/palette.directive.ts
@@ -0,0 +1,327 @@
+/// <reference path="../../../references"/>
+
+module Sdc.Directives {
+ import Dictionary = Sdc.Utils.Dictionary;
+ import GRAPH_EVENTS = Sdc.Utils.Constants.GRAPH_EVENTS;
+ import ImageCreatorService = Sdc.Utils.ImageCreatorService;
+ interface IPaletteScope {
+ components: any;
+ currentComponent: any;
+ model: any;
+ displaySortedCategories: any;
+ expandedSection: string;
+
+ p2pVL: Models.Components.Component;
+ mp2mpVL: Models.Components.Component;
+ vlType: string;
+ dragElement: JQuery;
+ dragbleNode: {
+ event: JQueryEventObject,
+ components: Models.DisplayComponent,
+ ui: any
+ }
+
+ sectionClick: (section: string)=>void;
+ searchComponents: (searchText: string)=>void;
+ onMouseOver: (displayComponent: Models.DisplayComponent)=>void;
+ onMouseOut: (displayComponent: Models.DisplayComponent)=>void;
+ dragStartCallback: (event: JQueryEventObject, ui, displayComponent: Models.DisplayComponent)=>void;
+ dragStopCallback: ()=>void;
+ onDragCallback: (event:JQueryEventObject) => void;
+ setElementTemplate: (e: JQueryEventObject)=>void;
+
+ isOnDrag: boolean;
+ isDragable: boolean;
+ isLoading: boolean;
+ isViewOnly: boolean;
+ }
+
+ export class Palette implements ng.IDirective {
+ constructor(private $log: ng.ILogService,
+ private LeftPaletteLoaderService,
+ private sdcConfig,
+ private ComponentFactory,
+ private ComponentInstanceFactory: Utils.ComponentInstanceFactory,
+ private NodesFactory: Utils.NodesFactory,
+ private CompositionGraphGeneralUtils: Graph.Utils.CompositionGraphGeneralUtils,
+ private EventListenerService: Services.EventListenerService,
+ private sdcMenu: Models.IAppMenu) {
+
+ }
+
+ private fetchingComponentFromServer: boolean = false;
+ private nodeHtmlSubstitute: JQuery;
+
+ scope = {
+ components: '=',
+ currentComponent: '=',
+ isViewOnly: '=',
+ isLoading: '='
+ };
+ restrict = 'E';
+ templateUrl = '/app/scripts/directives/graphs-v2/palette/palette.html';
+
+ link = (scope: IPaletteScope, el: JQuery) => {
+ this.nodeHtmlSubstitute = $('<div class="node-substitute"><span></span><img /></div>');
+ el.append(this.nodeHtmlSubstitute);
+
+ this.initComponents(scope);
+ this.initScopeVls(scope);
+ this.initEvents(scope);
+ this.initDragEvents(scope);
+ this._initExpandedSection(scope, '');
+ };
+
+ private leftPanelResourceFilter(resourcesNotAbstract: Array<Models.DisplayComponent>, resourceFilterTypes: Array<string>): Array<Models.DisplayComponent> {
+ let filterResources = _.filter(resourcesNotAbstract, (component) => {
+ return resourceFilterTypes.indexOf(component.getComponentSubType()) > -1;
+ });
+ return filterResources;
+ }
+
+ private initLeftPanel(leftPanelComponents: Array<Models.DisplayComponent>, resourceFilterTypes: Array<string>): Models.LeftPanelModel {
+ let leftPanelModel = new Models.LeftPanelModel();
+
+ if (resourceFilterTypes && resourceFilterTypes.length) {
+ leftPanelComponents = this.leftPanelResourceFilter(leftPanelComponents, resourceFilterTypes);
+ }
+ leftPanelModel.numberOfElements = leftPanelComponents && leftPanelComponents.length || 0;
+
+ if (leftPanelComponents && leftPanelComponents.length) {
+
+ let categories: any = _.groupBy(leftPanelComponents, 'mainCategory');
+ for (let category in categories)
+ categories[category] = _.groupBy(categories[category], 'subCategory');
+
+ leftPanelModel.sortedCategories = categories;
+ }
+ return leftPanelModel;
+ }
+
+ private initScopeVls(scope: IPaletteScope): void {
+ let vls = this.LeftPaletteLoaderService.getFullDataComponentList(Utils.Constants.ResourceType.VL);
+ scope.vlType = null;
+ vls.forEach((item) => {
+ let key = _.find(Object.keys(item.capabilities), (key) => {
+ return _.includes(key.toLowerCase(), 'linkable');
+ });
+ let linkable = item.capabilities[key];
+ if (linkable) {
+ if (linkable[0].maxOccurrences == '2') {
+ scope.p2pVL = _.find(vls, (component: Models.Components.Component) => {
+ return component.uniqueId === item.uniqueId;
+ });
+
+ } else {//assuming unbounded occurrences
+ scope.mp2mpVL = _.find(vls, (component: Models.Components.Component) => {
+ return component.uniqueId === item.uniqueId;
+ });
+ }
+ }
+ });
+ };
+
+ private initEvents(scope: IPaletteScope) {
+ /**
+ *
+ * @param section
+ */
+ scope.sectionClick = (section: string) => {
+ if (section === scope.expandedSection) {
+ scope.expandedSection = '';
+ return;
+ }
+ scope.expandedSection = section;
+ };
+
+ scope.onMouseOver = (displayComponent: Models.DisplayComponent) => {
+ if (scope.isOnDrag) {
+ return;
+ }
+ scope.isOnDrag = true;
+
+ this.EventListenerService.notifyObservers(GRAPH_EVENTS.ON_PALETTE_COMPONENT_HOVER_IN, displayComponent);
+ this.$log.debug('palette::onMouseOver:: fired');
+
+ if (this.CompositionGraphGeneralUtils.componentRequirementsAndCapabilitiesCaching.containsKey(displayComponent.uniqueId)) {
+ this.$log.debug(`palette::onMouseOver:: component id ${displayComponent.uniqueId} found in cache`);
+ let cacheComponent: Models.Components.Component = this.CompositionGraphGeneralUtils.componentRequirementsAndCapabilitiesCaching.getValue(displayComponent.uniqueId);
+
+ //TODO: Danny: fire event to highlight matching nodes
+ //showMatchingNodes(cacheComponent);
+ return;
+ }
+
+ this.$log.debug(`palette::onMouseOver:: component id ${displayComponent.uniqueId} not found in cache, initiating server get`);
+ // This will bring the component from the server including requirements and capabilities
+ // Check that we do not fetch many times, because only in the success we add the component to componentRequirementsAndCapabilitiesCaching
+ if (this.fetchingComponentFromServer) {
+ return;
+ }
+
+ this.fetchingComponentFromServer = true;
+ this.ComponentFactory.getComponentFromServer(displayComponent.componentSubType, displayComponent.uniqueId)
+ .then((component: Models.Components.Component) => {
+ this.$log.debug(`palette::onMouseOver:: component id ${displayComponent.uniqueId} fetch success`);
+ this.LeftPaletteLoaderService.updateSpecificComponentLeftPalette(component, scope.currentComponent.componentType);
+ this.CompositionGraphGeneralUtils.componentRequirementsAndCapabilitiesCaching.setValue(component.uniqueId, component);
+ this.fetchingComponentFromServer = false;
+
+ //TODO: Danny: fire event to highlight matching nodes
+ //showMatchingNodes(component);
+ })
+ .catch(() => {
+ this.$log.debug('palette::onMouseOver:: component id fetch error');
+ this.fetchingComponentFromServer = false;
+ });
+
+
+ };
+
+ scope.onMouseOut = () => {
+ scope.isOnDrag = false;
+ this.EventListenerService.notifyObservers(GRAPH_EVENTS.ON_PALETTE_COMPONENT_HOVER_OUT);
+ }
+ }
+
+ private initComponents(scope: IPaletteScope) {
+ scope.searchComponents = (searchText: any): void => {
+ scope.displaySortedCategories = this._searchComponents(searchText, scope.model.sortedCategories);
+ this._initExpandedSection(scope, searchText);
+ };
+
+ scope.isDragable = scope.currentComponent.isComplex();
+ let entityType: string = scope.currentComponent.componentType.toLowerCase();
+ let resourceFilterTypes: Array<string> = this.sdcConfig.resourceTypesFilter[entityType];
+
+ scope.components = this.LeftPaletteLoaderService.getLeftPanelComponentsForDisplay(scope.currentComponent.componentType);
+ scope.model = this.initLeftPanel(scope.components, resourceFilterTypes);
+ scope.displaySortedCategories = angular.copy(scope.model.sortedCategories);
+ }
+
+ private _initExpandedSection(scope: IPaletteScope, searchText: string): void {
+ if (searchText == '') {
+ let isContainingCategory: boolean = false;
+ let categoryToExpand: string;
+ if (scope.currentComponent && scope.currentComponent.categories && scope.currentComponent.categories[0]) {
+ categoryToExpand = this.sdcMenu.categoriesDictionary[scope.currentComponent.categories[0].name];
+ for (let category in scope.model.sortedCategories) {
+ if (categoryToExpand == category) {
+ isContainingCategory = true;
+ break;
+ }
+ }
+ }
+ isContainingCategory ? scope.expandedSection = categoryToExpand : scope.expandedSection = 'Generic';
+ }
+ else {
+ scope.expandedSection = Object.keys(scope.displaySortedCategories).sort()[0];
+ }
+ };
+
+ private initDragEvents(scope: IPaletteScope) {
+ scope.dragStartCallback = (event: IDragDropEvent, ui, displayComponent: Models.DisplayComponent): void => {
+ if (scope.isLoading || !scope.isDragable || scope.isViewOnly) {
+ return;
+ }
+
+ let component = _.find(this.LeftPaletteLoaderService.getFullDataComponentListWithVls(scope.currentComponent.componentType), (componentFullData: Models.DisplayComponent) => {
+ return displayComponent.uniqueId === componentFullData.uniqueId;
+ });
+ this.EventListenerService.notifyObservers(GRAPH_EVENTS.ON_PALETTE_COMPONENT_DRAG_START, scope.dragElement, component);
+
+ scope.isOnDrag = true;
+
+
+
+ // this.graphUtils.showMatchingNodes(component, myDiagram, scope.sdcConfig.imagesPath);
+ // document.addEventListener('mousemove', moveOnDocument);
+ event.dataTransfer.component = component;
+ };
+
+ scope.dragStopCallback = () => {
+ scope.isOnDrag = false;
+ };
+
+ scope.onDragCallback = (event:IDragDropEvent): void => {
+ this.EventListenerService.notifyObservers(GRAPH_EVENTS.ON_PALETTE_COMPONENT_DRAG_ACTION, event);
+ };
+ scope.setElementTemplate = (e) => {
+ let dragComponent: Models.Components.Component = _.find(this.LeftPaletteLoaderService.getFullDataComponentListWithVls(scope.currentComponent.componentType),
+ (fullComponent: Models.Components.Component) => {
+ return (<any>angular.element(e.currentTarget).scope()).component.uniqueId === fullComponent.uniqueId;
+ });
+ let componentInstance: Models.ComponentsInstances.ComponentInstance = this.ComponentInstanceFactory.createComponentInstanceFromComponent(dragComponent);
+ let node: Models.Graph.CompositionCiNodeBase = this.NodesFactory.createNode(componentInstance);
+
+ // myDiagram.dragFromPalette = node;
+ this.nodeHtmlSubstitute.find("img").attr('src', node.img);
+ scope.dragElement = this.nodeHtmlSubstitute.clone().show();
+
+ return scope.dragElement;
+ };
+ }
+
+ private _searchComponents = (searchText: string, categories: any): void => {
+ let displaySortedCategories = angular.copy(categories);
+ if (searchText != '') {
+ angular.forEach(categories, function (category: any, categoryKey) {
+
+ angular.forEach(category, function (subcategory: Array<Models.DisplayComponent>, subcategoryKey) {
+ let filteredResources = [];
+ angular.forEach(subcategory, function (component: Models.DisplayComponent) {
+
+ let resourceFilterTerm: string = component.searchFilterTerms;
+ if (resourceFilterTerm.indexOf(searchText.toLowerCase()) >= 0) {
+ filteredResources.push(component);
+ }
+ });
+ if (filteredResources.length > 0) {
+ displaySortedCategories[categoryKey][subcategoryKey] = filteredResources;
+ }
+ else {
+ delete displaySortedCategories[categoryKey][subcategoryKey];
+ }
+ });
+ if (!(Object.keys(displaySortedCategories[categoryKey]).length > 0)) {
+ delete displaySortedCategories[categoryKey];
+ }
+
+ });
+ }
+ return displaySortedCategories;
+ };
+
+ public static factory = ($log,
+ LeftPaletteLoaderService,
+ sdcConfig,
+ ComponentFactory,
+ ComponentInstanceFactory,
+ NodesFactory,
+ CompositionGraphGeneralUtils,
+ EventListenerService,
+ sdcMenu) => {
+ return new Palette($log,
+ LeftPaletteLoaderService,
+ sdcConfig,
+ ComponentFactory,
+ ComponentInstanceFactory,
+ NodesFactory,
+ CompositionGraphGeneralUtils,
+ EventListenerService,
+ sdcMenu);
+ };
+ }
+
+ Palette.factory.$inject = [
+ '$log',
+ 'LeftPaletteLoaderService',
+ 'sdcConfig',
+ 'ComponentFactory',
+ 'ComponentInstanceFactory',
+ 'NodesFactory',
+ 'CompositionGraphGeneralUtils',
+ 'EventListenerService',
+ 'sdcMenu'
+ ];
+} \ No newline at end of file
diff --git a/catalog-ui/app/scripts/directives/graphs-v2/palette/palette.html b/catalog-ui/app/scripts/directives/graphs-v2/palette/palette.html
new file mode 100644
index 0000000000..a8dd827927
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/graphs-v2/palette/palette.html
@@ -0,0 +1,59 @@
+<div class="w-sdc-designer-leftbar">
+ <div class="w-sdc-designer-leftbar-title">Elements <span class="w-sdc-designer-leftbar-title-count">{{model.numberOfElements}}</span>
+ </div>
+
+ <div class="w-sdc-designer-leftbar-search">
+ <input type="text" class="w-sdc-designer-leftbar-search-input" placeholder="Search..."
+ data-ng-model="searchText" data-ng-change="searchComponents(searchText)"
+ ng-model-options="{ debounce: 500 }" data-tests-id="searchAsset"/>
+ <span class="w-sdc-search-icon leftbar" data-ng-class="{'cancel':searchText, 'magnification':!searchText}"
+ data-ng-click="searchText=''; searchComponents('',categories)"></span>
+ </div>
+ <div class="i-sdc-designer-leftbar-section"
+ data-ng-repeat="(entityCategory, objCategory) in displaySortedCategories track by $index"
+ data-ng-class="{'expanded': expandedSection.indexOf(entityCategory) !== -1}">
+ <div class="i-sdc-designer-leftbar-section-title pointer" data-ng-click="sectionClick(entityCategory)"
+ data-tests-id="leftbar-section-title-{{entityCategory}}">
+ {{entityCategory}}
+ <div class="i-sdc-designer-leftbar-section-title-icon"></div>
+ </div>
+ <div class="i-sdc-designer-leftbar-section-content"
+ data-ng-repeat="(subCategory, components) in objCategory track by $index">
+ <div class="i-sdc-designer-leftbar-section-content-subcat i-sdc-designer-leftbar-section-content-item">
+ {{subCategory}}
+ </div>
+ <div class="i-sdc-designer-leftbar-section-content-item"
+ data-ng-class="{'default-pointer': isViewOnly}"
+ data-ng-mouseover="!isViewOnly && onMouseOver(component)"
+ data-ng-mouseleave="!isViewOnly && onMouseOut()"
+ data-drag="{{!isViewOnly}}"
+ data-jqyoui-options="{revert: 'invalid', helper:setElementTemplate, appendTo:'body', cursorAt: {left:38, top: 38}, cursor:'move'}"
+ jqyoui-draggable="{index:{{$index}},animate:true,onStart:'dragStartCallback(component)',onStop:'dragStopCallback()', onDrag:'onDragCallback()'}"
+ data-ng-repeat="component in components | orderBy: 'displayName' track by $index"
+ data-tests-id={{component.displayName}}>
+ <div class="i-sdc-designer-leftbar-section-content-item-icon-ph">
+ <div class="medium {{component.iconClass}}"
+ data-tests-id="leftbar-section-content-item-{{component.displayName}}">
+ <div class="{{component.certifiedIconClass}}" uib-tooltip="Not certified"
+ tooltip-class="uib-custom-tooltip" tooltip-placement="bottom" tooltip-popup-delay="700">
+ </div>
+ </div>
+ </div>
+ <div class="i-sdc-designer-leftbar-section-content-item-info">
+ <span class="i-sdc-designer-leftbar-section-content-item-info-title"
+ uib-tooltip="{{component.displayName}}" tooltip-class="uib-custom-tooltip"
+ tooltip-placement="bottom" tooltip-popup-delay="700">
+ {{component.displayName}}</span>
+ <div class="i-sdc-designer-leftbar-section-content-item-info-text">
+ V.{{component.version}}
+ </div>
+ <div class="i-sdc-designer-leftbar-section-content-item-info-text"> Type:
+ {{component.componentSubType}}
+ <a data-ng-click="openViewerModal(component)"
+ class="i-sdc-designer-leftbar-section-content-item-info-text-link hand">More</a>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+</div> \ No newline at end of file
diff --git a/catalog-ui/app/scripts/directives/graphs-v2/palette/palette.less b/catalog-ui/app/scripts/directives/graphs-v2/palette/palette.less
new file mode 100644
index 0000000000..85657a43a5
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/graphs-v2/palette/palette.less
@@ -0,0 +1,92 @@
+.drag-icon-border{
+ border: 7px solid red;
+ border-radius: 500px;
+ -webkit-border-radius: 500px;
+ -moz-border-radius: 500px;
+ width: 53px;
+ height: 53px;
+}
+
+.drag-icon-circle{
+ width: 60px;
+ height: 60px;
+ -webkit-border-radius: 50%;
+ -moz-border-radius: 50%;
+ border-radius: 50%;
+ position: relative;
+
+}
+
+
+@green-shadow: rgba(29, 154, 149, 0.3);
+@red-shadow: rgba(218, 31, 61, 0.3);
+.drag-icon-circle .sprite-resource-icons {
+ position: absolute;
+ top: 10px;
+ left: 10px;
+}
+
+.drag-icon-circle.red {
+ background: @red-shadow;
+}
+
+.drag-icon-circle.green {
+ background: @green-shadow;
+}
+
+
+.node-substitute {
+ display: none;
+ position: absolute;
+ z-index: 9999;
+ height: 80px;
+ width: 80px;
+ border-radius: 50%;
+ text-align: center;
+
+ span {
+ display: inline-block;
+ vertical-align: middle;
+ height: 100%;
+ }
+
+ img {
+ height: 40px;
+ width: 40px;
+ box-shadow: 0 0 0 10px @green-shadow;
+ border-radius: 50%;
+
+ -webkit-user-drag: none;
+ -moz-user-drag: none;
+ user-drag: none;
+ }
+ &.red img {
+ box-shadow: 0 0 0 10px @red-shadow;
+ }
+ &.bounce img {
+ -moz-animation:bounceOut 0.3s linear;
+ -webkit-animation:bounceOut 0.3s linear;
+ animation:bounceOut 0.3s linear;
+ }
+}
+
+@keyframes bounceOut {
+ 0%{ box-shadow: 0 0 0 10px @green-shadow; width: 40px; height: 40px; }
+ 60%{ box-shadow: 0 0 0 0px @green-shadow; width: 60px; height: 60px; }
+ 85%{ box-shadow: 0 0 0 0px @green-shadow; width: 75px; height: 75px; }
+ 100%{ box-shadow: 0 0 0 0px @green-shadow; width: 60px; height: 60px; }
+}
+
+@-moz-keyframes bounceOut {
+ 0%{ box-shadow: 0 0 0 10px @green-shadow; width: 40px; height: 40px; }
+ 60%{ box-shadow: 0 0 0 0px @green-shadow; width: 60px; height: 60px; }
+ 85%{ box-shadow: 0 0 0 0px @green-shadow; width: 75px; height: 75px; }
+ 100%{ box-shadow: 0 0 0 0px @green-shadow; width: 60px; height: 60px; }
+}
+
+@-webkit-keyframes bounceOut {
+ 0%{ box-shadow: 0 0 0 10px @green-shadow; width: 40px; height: 40px; }
+ 60%{ box-shadow: 0 0 0 0px @green-shadow; width: 60px; height: 60px; }
+ 85%{ box-shadow: 0 0 0 0px @green-shadow; width: 75px; height: 75px; }
+ 100%{ box-shadow: 0 0 0 0px @green-shadow; width: 60px; height: 60px; }
+}
diff --git a/catalog-ui/app/scripts/directives/graphs-v2/relation-menu/relation-menu.html b/catalog-ui/app/scripts/directives/graphs-v2/relation-menu/relation-menu.html
new file mode 100644
index 0000000000..a0a9e4af27
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/graphs-v2/relation-menu/relation-menu.html
@@ -0,0 +1,63 @@
+<div class="link-menu-open" data-tests-id="link-menu-open" data-ng-show="isLinkMenuOpen" ng-style="{left: relationMenuDirectiveObj.menuPosition.x, top: relationMenuDirectiveObj.menuPosition.y}" clicked-outside="{onClickedOutside: 'hideRelationMatch()', clickedOutsideEnable: 'isLinkMenuOpen'}" >
+ <h4 sdc-smart-tooltip>{{relationMenuDirectiveObj.leftSideLink.componentInstance.name | resourceName}}</h4>
+ <h4 sdc-smart-tooltip>{{relationMenuDirectiveObj.rightSideLink.componentInstance.name | resourceName}}</h4>
+
+ <p>Select one of the options below to connect</p>
+
+ <perfect-scrollbar scroll-y-margin-offset="0" include-padding="true" class="scrollbar-container">
+ <div class="inner-title" data-ng-show="hasMatchesToShow(relationMenuDirectiveObj.leftSideLink.requirements, relationMenuDirectiveObj.rightSideLink.selectedMatch)">Requirements</div>
+ <div class="link-item" data-tests-id="link-item-requirements" data-ng-repeat="(req ,matchArr) in relationMenuDirectiveObj.leftSideLink.requirements"
+ data-ng-click="relationMenuDirectiveObj.leftSideLink.selectMatchArr(matchArr); updateSelectionText()"
+ data-ng-show="showMatch(relationMenuDirectiveObj.rightSideLink.selectedMatch, matchArr)"
+ data-ng-class="{ 'selected': relationMenuDirectiveObj.leftSideLink.selectedMatch === matchArr}">
+ <div sdc-smart-tooltip>{{matchArr[0].requirement.getFullTitle()}}</div>
+ </div>
+
+ <div class="inner-title" data-ng-show="hasMatchesToShow(relationMenuDirectiveObj.leftSideLink.capabilities, relationMenuDirectiveObj.rightSideLink.selectedMatch)">Capabilities</div>
+ <div class="link-item" data-tests-id="link-item-capabilities" data-ng-repeat="(cap, matchArr) in relationMenuDirectiveObj.leftSideLink.capabilities"
+ data-ng-click="relationMenuDirectiveObj.leftSideLink.selectMatchArr(matchArr); updateSelectionText()"
+ data-ng-show="showMatch(relationMenuDirectiveObj.rightSideLink.selectedMatch, matchArr)"
+ data-ng-class="{ 'selected': relationMenuDirectiveObj.leftSideLink.selectedMatch === matchArr}">
+ <div sdc-smart-tooltip>{{matchArr[0].capability.getFullTitle()}}</div>
+ </div>
+ </perfect-scrollbar>
+
+ <perfect-scrollbar scroll-y-margin-offset="0" include-padding="true" class="scrollbar-container">
+ <div class="inner-title" data-ng-show="hasMatchesToShow(relationMenuDirectiveObj.rightSideLink.requirements, relationMenuDirectiveObj.leftSideLink.selectedMatch)">Requirements</div>
+ <div class="link-item" data-tests-id="link-item-requirements" data-ng-repeat="(req, matchArr) in relationMenuDirectiveObj.rightSideLink.requirements"
+ data-ng-click="relationMenuDirectiveObj.rightSideLink.selectMatchArr(matchArr); updateSelectionText()"
+ data-ng-show="showMatch(relationMenuDirectiveObj.leftSideLink.selectedMatch, matchArr)"
+ data-ng-class="{ 'selected': relationMenuDirectiveObj.rightSideLink.selectedMatch === matchArr}">
+ <div sdc-smart-tooltip>{{matchArr[0].secondRequirement ? matchArr[0].secondRequirement.getFullTitle() : matchArr[0].requirement.getFullTitle()}}</div>
+ </div>
+
+ <div class="inner-title" data-ng-show="hasMatchesToShow(relationMenuDirectiveObj.rightSideLink.capabilities, relationMenuDirectiveObj.leftSideLink.selectedMatch)">Capabilities</div>
+ <div class="link-item" data-tests-id="link-item-capabilities" data-ng-repeat="(cap, matchArr) in relationMenuDirectiveObj.rightSideLink.capabilities"
+ data-ng-click="relationMenuDirectiveObj.rightSideLink.selectMatchArr(matchArr); updateSelectionText()"
+ data-ng-show="showMatch(relationMenuDirectiveObj.leftSideLink.selectedMatch, matchArr)"
+ data-ng-class="{ 'selected': relationMenuDirectiveObj.rightSideLink.selectedMatch === matchArr}">
+ <div sdc-smart-tooltip>{{matchArr[0].capability.getFullTitle()}}</div>
+ </div>
+ </perfect-scrollbar>
+
+ <div class="vl-type" data-ng-class="{'disabled': !relationMenuDirectiveObj.leftSideLink.selectedMatch[0].secondRequirement || !relationMenuDirectiveObj.rightSideLink.selectedMatch[0].secondRequirement}">
+ <sdc-radio-button sdc-model="relationMenuDirectiveObj.vlType" value="ptp"
+ disabled="!relationMenuDirectiveObj.leftSideLink.selectedMatch[0].secondRequirement || !relationMenuDirectiveObj.rightSideLink.selectedMatch[0].secondRequirement || !relationMenuDirectiveObj.p2pVL"
+ text="Point to point" elem-id="radioPTP" elem-name="vlType"></sdc-radio-button>
+
+ <sdc-radio-button sdc-model="relationMenuDirectiveObj.vlType" value="mptmp"
+ disabled="!relationMenuDirectiveObj.leftSideLink.selectedMatch[0].secondRequirement || !relationMenuDirectiveObj.rightSideLink.selectedMatch[0].secondRequirement || !relationMenuDirectiveObj.mp2mpVL"
+ text="Multi point" elem-id="radioMPTMP" elem-name="vlType"></sdc-radio-button>
+
+ <span class="sprite-new info-icon" tooltips tooltip-content="You are required to choose the type of the Virtual Link."></span>
+ </div>
+
+ <div class="result" sdc-smart-tooltip>&#8203;{{relationMenuDirectiveObj.selectionText}}
+
+ </div>
+
+ <button class="tlv-btn grey" data-tests-id="link-menu-button-cancel" data-ng-click="hideRelationMatch()">Cancel</button>
+ <button class="tlv-btn blue" data-tests-id="link-menu-button-connect" data-ng-disabled="!relationMenuDirectiveObj.leftSideLink.selectedMatch || !relationMenuDirectiveObj.rightSideLink.selectedMatch ||
+ (relationMenuDirectiveObj.leftSideLink.selectedMatch[0].secondRequirement && !relationMenuDirectiveObj.vlType)"
+ data-ng-click="saveRelation()">Connect</button>
+</div>
diff --git a/catalog-ui/app/scripts/directives/graphs-v2/relation-menu/relation-menu.less b/catalog-ui/app/scripts/directives/graphs-v2/relation-menu/relation-menu.less
new file mode 100644
index 0000000000..dea814dbec
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/graphs-v2/relation-menu/relation-menu.less
@@ -0,0 +1,118 @@
+.link-menu-open {
+ display: block !important;
+ color: @main_color_m;
+ font-size: 14px;
+ position: absolute;
+ z-index: 99999;
+ border-radius: 2px;
+ background-color: #ffffff;
+ box-shadow: 0px 0px 6px 0px rgba(0, 0, 0, 0.5);
+ width: 460px;
+ height: 418px;
+
+ h4 {
+ width: 50%;
+ float: left;
+ background-color: @tlv_color_u;
+ font-size: 14px;
+ font-weight: bold;
+ line-height: 36px;
+ margin: 0;
+ padding: 0 15px;
+
+ & + h4 {
+ border-left: #d8d8d8 1px solid;
+ }
+ }
+ p {
+ clear: both;
+ text-indent: 15px;
+ border-bottom: #d8d8d8 1px solid;
+ line-height: 34px;
+ margin: 0;
+ color: @func_color_s;
+ }
+
+ .scrollbar-container {
+ height: 232px;
+ width: 50%;
+ float: left;
+ margin-bottom: 5px;
+ .perfect-scrollbar;
+
+ & + .scrollbar-container {
+ border-left: #d8d8d8 1px solid;
+ }
+
+ .inner-title {
+ width: 189px;
+ margin: 5px auto 3px auto;
+ //text-indent: 10px;
+ color: @func_color_s;
+ text-transform: uppercase;
+ font-weight: bold;
+
+ //&:not(:first-child) {
+ // margin-top: 10px;
+ //}
+ }
+
+ .link-item {
+ padding: 0 10px;
+ line-height: 23px;
+ height: 23px;
+ text-indent: 5px;
+ .hand;
+
+ &.selected {
+ background-color: @tlv_color_v;
+ }
+ }
+ }
+
+ .vl-type {
+ height: 33px;
+ border-top: #d8d8d8 solid 1px;
+ clear: both;
+ padding: 0 10px;
+ line-height: 32px;
+ color: @main_color_m;
+
+ &.disabled {
+ background-color: #f2f2f2;
+ color: @color_m;
+ }
+ .info-icon {
+ float:right;
+ margin-top: 9px;
+ }
+ .tlv-radio {
+ margin-right: 10px;
+ }
+ }
+
+ .result {
+ background-color: @main_color_m;
+ line-height: 29px;
+ color: #ffffff;
+ padding: 0 15px;
+ }
+
+ button {
+ float: right;
+ margin-top: 9px;
+ margin-right: 10px;
+ }
+}
+.link-menu-item {
+ cursor: pointer;
+ line-height: 24px;
+ padding: 0 10px;
+ &:hover {
+ color: @color_a;
+ }
+}
+.link-menu::before {
+ right: inherit !important;
+ left: 50px;
+} \ No newline at end of file
diff --git a/catalog-ui/app/scripts/directives/graphs-v2/relation-menu/relation-menu.ts b/catalog-ui/app/scripts/directives/graphs-v2/relation-menu/relation-menu.ts
new file mode 100644
index 0000000000..22a2d078b7
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/graphs-v2/relation-menu/relation-menu.ts
@@ -0,0 +1,113 @@
+/*-
+ * ============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.Directives {
+ 'use strict';
+
+
+ export interface IRelationMenuScope extends ng.IScope {
+ relationMenuDirectiveObj:Models.RelationMenuDirectiveObj;
+ createRelation:Function;
+ isLinkMenuOpen:boolean;
+ hideRelationMatch:Function;
+ cancel:Function;
+
+ saveRelation();
+ showMatch(arr1:Array<Models.MatchBase>, arr2:Array<Models.MatchBase>):boolean;
+ hasMatchesToShow(matchesObj:Models.MatchBase, selectedMatch:Array<Models.MatchBase>);
+ updateSelectionText():void;
+
+ }
+
+
+ export class RelationMenuDirective implements ng.IDirective {
+
+ constructor(private $templateCache:ng.ITemplateCacheService,
+ private $filter:ng.IFilterService
+ ) {
+ }
+
+ scope = {
+ relationMenuDirectiveObj: '=',
+ isLinkMenuOpen: '=',
+ createRelation: '&',
+ cancel:'&'
+ };
+
+ restrict = 'E';
+ replace = true;
+ template = ():string => {
+ return this.$templateCache.get('/app/scripts/directives/graphs-v2/relation-menu/relation-menu.html');
+ };
+
+ link = (scope:IRelationMenuScope, element:JQuery, $attr:ng.IAttributes) => {
+
+ scope.saveRelation = ():void=> {
+ let chosenMatches:Array<any> = _.intersection(scope.relationMenuDirectiveObj.rightSideLink.selectedMatch, scope.relationMenuDirectiveObj.leftSideLink.selectedMatch);
+ let chosenMatch:Models.MatchBase = chosenMatches[0];
+ let chosenVL:Models.Components.Component;
+ if ("mptmp" === scope.relationMenuDirectiveObj.vlType) {
+ chosenVL = scope.relationMenuDirectiveObj.mp2mpVL;
+ } else {
+ chosenVL = scope.relationMenuDirectiveObj.p2pVL;
+ }
+ scope.createRelation()(chosenMatch,chosenVL);
+ };
+
+
+ scope.hideRelationMatch = () => {
+ scope.isLinkMenuOpen = false;
+ scope.cancel();
+ };
+
+ //to show options in link menu
+ scope.showMatch = (arr1:Array<Models.MatchBase>, arr2:Array<Models.MatchBase>):boolean => {
+ return !arr1 || !arr2 || _.intersection(arr1, arr2).length > 0;
+ };
+
+ //to show requirements/capabilities title
+ scope.hasMatchesToShow = (matchesObj:Models.MatchBase, selectedMatch:Array<Models.MatchBase>):boolean => {
+ let result:boolean = false;
+ _.forEach(matchesObj, (matchesArr:Array<Models.MatchBase>) => {
+ if (!result) {
+ result = scope.showMatch(matchesArr, selectedMatch);
+ }
+ });
+ return result;
+ };
+
+
+ scope.updateSelectionText = ():void => {
+ let left:string = scope.relationMenuDirectiveObj.leftSideLink.selectedMatch ? this.$filter('resourceName')(scope.relationMenuDirectiveObj.leftSideLink.selectedMatch[0].getDisplayText('left')) : '';
+ let both:string = scope.relationMenuDirectiveObj.leftSideLink.selectedMatch && scope.relationMenuDirectiveObj.rightSideLink.selectedMatch ? ' - ' +
+ this.$filter('resourceName')(scope.relationMenuDirectiveObj.leftSideLink.selectedMatch[0].requirement.relationship) + ' - ' : '';
+ let right:string = scope.relationMenuDirectiveObj.rightSideLink.selectedMatch ? this.$filter('resourceName')(scope.relationMenuDirectiveObj.rightSideLink.selectedMatch[0].getDisplayText('right')) : '';
+ scope.relationMenuDirectiveObj.selectionText = left + both + right;
+ };
+
+
+ }
+ public static factory = ($templateCache:ng.ITemplateCacheService , $filter:ng.IFilterService)=> {
+ return new RelationMenuDirective($templateCache, $filter);
+ };
+ }
+
+ RelationMenuDirective.factory.$inject = ['$templateCache', '$filter'];
+}
diff --git a/catalog-ui/app/scripts/directives/info-tooltip/info-tooltip.html b/catalog-ui/app/scripts/directives/info-tooltip/info-tooltip.html
new file mode 100644
index 0000000000..5c2bdcf5f1
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/info-tooltip/info-tooltip.html
@@ -0,0 +1,10 @@
+<div>
+ <span class="sprite-new info-icon hand" data-ng-click="showMessage=!showMessage"></span>
+ <div class="info-tooltip" data-ng-show="showMessage">
+ <div class="info-tooltip-arrow"></div>
+ <div class="info-tooltip-content" data-ng-class="direction">
+ <span class="close-tooltip sprite-new close-info-tooltip-button" data-ng-click="showMessage=false"></span>
+ <p class="info-tooltip-message" translate="{{infoMessageTranslate}}"></p>
+ </div>
+ </div>
+</div>
diff --git a/catalog-ui/app/scripts/directives/info-tooltip/info-tooltip.less b/catalog-ui/app/scripts/directives/info-tooltip/info-tooltip.less
new file mode 100644
index 0000000000..8811af16a4
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/info-tooltip/info-tooltip.less
@@ -0,0 +1,39 @@
+.info-tooltip {
+ position: fixed;
+ z-index: 1070;
+ display: block;
+ width: 250px;
+ .info-tooltip-arrow {
+ width: 0;
+ height: 0;
+ border-style: solid;
+ border-width: 0 5px 5px 5px;
+ border-color: transparent transparent @main_color_a transparent;
+ position: relative;
+ left: 2px;
+ }
+ .info-tooltip-content {
+ box-shadow: 0px 3px 6px 0px rgba(0, 0, 0, 0.5);
+ border: 1px solid @main_color_o;
+ border-radius: 3px;
+ border-top: 3px solid @main_color_a;
+ position: relative;
+ background-color: white;
+ &.right{
+ left: -13px;
+ }
+ &.left{
+ left: -223px;
+ }
+ .close-tooltip{
+ float: right;
+ margin: 5px;
+ }
+
+ .info-tooltip-message{
+ margin: 15px;
+ word-break: normal;
+ font-size: 14px;
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/directives/info-tooltip/info-tooltip.ts b/catalog-ui/app/scripts/directives/info-tooltip/info-tooltip.ts
new file mode 100644
index 0000000000..cd81b14ce8
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/info-tooltip/info-tooltip.ts
@@ -0,0 +1,60 @@
+/*-
+ * ============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/25/2016.
+ */
+/// <reference path="../../references"/>
+module Sdc.Directives {
+ 'use strict';
+
+ export interface IInfoTooltipScope extends ng.IScope {
+ infoMessageTranslate:string;
+ direction:string;
+ }
+
+
+ export class InfoTooltipDirective implements ng.IDirective {
+
+ constructor(private $templateCache:ng.ITemplateCacheService) {
+ }
+
+ scope = {
+ infoMessageTranslate:'@',
+ direction:'@'//get 'right' or 'left', the default is 'right'
+ };
+
+ restrict = 'E';
+ replace = true;
+ template = ():string => {
+ return this.$templateCache.get('/app/scripts/directives/info-tooltip/info-tooltip.html');
+ };
+
+ link = (scope:IInfoTooltipScope, element:any, $attr:any) => {
+ scope.direction = scope.direction || 'right';
+ };
+
+ public static factory = ($templateCache:ng.ITemplateCacheService)=> {
+ return new InfoTooltipDirective($templateCache);
+ };
+
+ }
+
+ InfoTooltipDirective.factory.$inject = ['$templateCache'];
+}
diff --git a/catalog-ui/app/scripts/directives/invalid-characters/invalid-characters.ts b/catalog-ui/app/scripts/directives/invalid-characters/invalid-characters.ts
new file mode 100644
index 0000000000..7ab98b0d23
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/invalid-characters/invalid-characters.ts
@@ -0,0 +1,72 @@
+/*-
+ * ============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.Directives {
+ 'use strict';
+
+ export class InvalidCharactersDirective implements ng.IDirective {
+
+ constructor() {}
+
+ require = 'ngModel';
+
+ link = (scope, elem, attrs, ngModel) => {
+
+ let invalidCharacters = [];
+
+ attrs.$observe('invalidCharacters', (val:string) => {
+ invalidCharacters = val.split('');
+ validate(ngModel.$viewValue);
+ });
+
+ let validate: Function = function (value) {
+
+ let valid:boolean = true;
+
+ if(value) {
+ for (let i = 0; i < invalidCharacters.length; i++) {
+ if (value.indexOf(invalidCharacters[i]) != - 1) {
+ valid = false;
+ }
+ }
+ }
+
+ ngModel.$setValidity('invalidCharacters', valid);
+ if(!value) {
+ ngModel.$setPristine();
+ }
+ return value;
+ };
+
+ //For DOM -> model validation
+ ngModel.$parsers.unshift(validate);
+ //For model -> DOM validation
+ ngModel.$formatters.unshift(validate);
+
+ };
+
+ public static factory = ()=> {
+ return new InvalidCharactersDirective();
+ };
+
+ }
+
+ InvalidCharactersDirective.factory.$inject = [];
+}
diff --git a/catalog-ui/app/scripts/directives/layout/top-nav/top-nav.html b/catalog-ui/app/scripts/directives/layout/top-nav/top-nav.html
new file mode 100644
index 0000000000..40b1e86d90
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/layout/top-nav/top-nav.html
@@ -0,0 +1,54 @@
+<nav class="top-nav">
+
+ <div class="asdc-app-title-wrapper">
+ <a class="asdc-app-title">SDC</a> <!-- data-ui-sref="dashboard" -->
+ <div class="asdc-version"> v.{{version}}</div>
+ </div>
+
+ <ul class="top-menu" data-ng-show="!menuModel">
+ <!-- no hierarchy & dropdowns mode -->
+ <li data-ng-repeat="item in topLvlMenu.menuItems"
+ data-ng-class="{'selected': $index == topLvlMenu.selectedIndex}">
+ <a data-ng-click="menuItemClick(topLvlMenu, item)"
+ data-tests-id="main-menu-button-{{item.text | lowercase}}">{{item.text}}</a>
+ </li>
+ </ul>
+
+ <ul class="top-menu" data-ng-show="menuModel">
+ <!-- with hierarchy & dropdowns mode -->
+ <li data-ng-repeat-start="groupItem in menuModel"
+ data-ng-class="{'selected': $last }">
+ <a data-ng-click="menuItemClick(groupItem, groupItem.menuItems[groupItem.selectedIndex])"
+ data-tests-id="breadcrumbs-button-{{$index}}">
+ {{groupItem.menuItems[groupItem.selectedIndex].text}}
+ </a>
+ </li>
+ <li data-ng-repeat-end="" class="triangle-dropdown"
+ data-ng-class="{'item-click': groupItem.itemClick}" data-ng-mouseover="groupItem.itemClick = true">
+ <div class="triangle"><span class="sprite-new arrow-right"></span></div>
+ <perfect-scrollbar scroll-y-margin-offset="15" include-padding="true">
+ <ul>
+ <li data-ng-repeat="ddItem in groupItem.menuItems"
+ data-ng-click="menuItemClick(groupItem, ddItem)"
+ data-ng-class="{'selected': $index == groupItem.selectedIndex, 'disabled': ddItem.isDisabled}"
+ data-tests-id="sub-menu-button-{{ddItem.text | lowercase}}">
+ <span sdc-smart-tooltip="">{{ddItem.text}}</span>
+ </li>
+ </ul>
+ </perfect-scrollbar>
+ </li>
+ </ul>
+
+ <div class="top-search" data-ng-hide="hideSearch === true">
+ <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 class="notification-icon" data-ng-disabled= "progress > 0" data-ng-class="{'disabled' : progress > 0}" data-ng-if="user.role === 'DESIGNER' && notificationIconCallback" data-ng-click="notificationIconCallback()" tooltips tooltip-side="left" tooltip-content="Vendor Software Product Repository" data-tests-id="repository-icon"></div>
+
+</nav>
diff --git a/catalog-ui/app/scripts/directives/layout/top-nav/top-nav.less b/catalog-ui/app/scripts/directives/layout/top-nav/top-nav.less
new file mode 100644
index 0000000000..65021bdc4d
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/layout/top-nav/top-nav.less
@@ -0,0 +1,218 @@
+.top-nav {
+ position: fixed;
+ top: @header_height;
+ background-color: @main_color_p;
+ .box-shadow(0px 1px 3px 0px rgba(0, 0, 0, 0.33));
+ width: 100%;
+ height: @top_nav_height;
+ line-height: @top_nav_height;
+ z-index: 10;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+
+ .asdc-app-title-wrapper {
+ flex-grow: 1;
+ line-height: 16px;
+ margin: 0 20px;
+
+ a.asdc-app-title {
+ .m_18_r;
+ text-decoration: none;
+ }
+
+ .asdc-version {
+ .m_12_r;
+ .opacity(0.8);
+ line-height: 14px;
+ flex-grow: 1;
+ }
+
+ }
+
+ ul.top-menu {
+ list-style-type: none;
+ margin: 0 0 0 20px;
+ padding: 0;
+ flex-grow: 999;
+
+ & > li {
+ float: left;
+ cursor: pointer;
+ line-height: 50px;
+ height: 50px;
+ padding: 0 20px;
+
+ &.selected {
+ border-bottom: solid 4px @main_color_a;
+
+ a {
+ color: @func_color_s;
+ }
+ }
+
+ /*&:hover {
+ border-bottom: solid 4px @main_color_a;
+ }*/
+
+ a {
+ font-family: @font-omnes-medium;
+ color: @main_color_m;
+ font-size: 18px;
+ display: block;
+ text-align: center;
+ text-decoration: none;
+ }
+
+ &.triangle-dropdown {
+ padding: 0;
+ position: relative;
+
+ div.triangle {
+ margin-top: 15px;
+ border-radius: 2px;
+ width: 17px;
+ height: 18px;
+
+ //temp use - until new triangle gets in
+ line-height: 18px;
+ text-align: center;
+ font-size: 10px;
+
+ &:hover {
+ background-color: rgba(156, 156, 156, 0.2);
+
+ span {
+ .arrow-right-hover;
+ }
+ }
+ }
+
+ + li a {
+ font-size: 16px;
+ }
+
+ .ps-container {
+ .perfect-scrollbar;
+ position: absolute;
+ left: 0;
+ top: 40px;
+ z-index: 1;
+
+ overflow: hidden;
+ max-height: 0;
+ -webkit-transition: max-height 200ms ease-in;
+ -moz-transition: max-height 200ms ease-in;
+ -o-transition: max-height 200ms ease-in;
+ transition: max-height 200ms ease-in;
+
+ div ul {
+
+ padding: 0;
+ background-color: white;
+
+ li {
+
+ height: 35px;
+ background-color: white;
+ font-size: 13px;
+ width: 150px;
+ line-height: 35px;
+ padding: 0 10px;
+
+ &.disabled {
+ opacity: 1;
+ }
+ &.selected {
+ background-color: @tlv_color_v;
+ font-weight: bold;
+ }
+ &:hover {
+ color: @main_color_a;
+ }
+ span {
+ height: 35px;
+ width: 130px;
+ display: inline-block;
+ }
+ }
+ }
+ }
+ &.item-click:hover .ps-container,
+ &.item-click:active .ps-container {
+ max-height: 500px;
+ border: 1px solid @func_color_b;
+ border-radius: 2px;
+ box-shadow: 0px 2px 2px 0px rgba(24, 24, 25, 0.1);
+
+ div ul {
+
+ }
+ }
+ }
+ }
+
+ }
+
+ .top-search {
+ position: relative;
+ flex-grow: 1;
+ padding: 0 20px;
+
+ input.search-text {
+ .border-radius(2px);
+ width: 245px;
+ height: 32px;
+ line-height: 32px;
+ border: 1px solid @main_color_o;
+ 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 */
+ /* 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: 19px;
+ right: 26px;
+ }
+
+ }
+
+ .notification-icon {
+ cursor: pointer;
+ flex-grow: 1;
+ margin: 0 10px 6px 0;
+ .sprite-new;
+ .vsp-list-icon;
+
+ &:hover {
+ .vsp-list-icon-hover;
+ }
+
+ &:active {
+ .vsp-list-icon-active;
+ }
+
+ }
+
+}
diff --git a/catalog-ui/app/scripts/directives/layout/top-nav/top-nav.ts b/catalog-ui/app/scripts/directives/layout/top-nav/top-nav.ts
new file mode 100644
index 0000000000..356e43b7f7
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/layout/top-nav/top-nav.ts
@@ -0,0 +1,155 @@
+/*-
+ * ============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.Directives {
+ 'use strict';
+
+ export interface ITopNavScope extends ng.IScope {
+ topLvlSelectedIndex: number;
+ hideSearch: boolean;
+ searchBind: any;
+ menuModel: Array<Utils.MenuItemGroup>;
+
+ topLvlMenu: Utils.MenuItemGroup;
+ goToState(state:string, params:Array<any>):ng.IPromise<boolean>;
+ menuItemClick: Function;
+ user: Models.IUserProperties;
+ version:string;
+ }
+
+
+ export class TopNavDirective implements ng.IDirective {
+
+ constructor(private $templateCache:ng.ITemplateCacheService,
+ private $filter:ng.IFilterService,
+ private $state:ng.ui.IStateService,
+ private $q: ng.IQService,
+ private userResourceService: Sdc.Services.IUserResourceClass
+ ) {
+ }
+
+ public replace = true;
+ public restrict = 'E';
+ public transclude = false;
+
+
+ scope = {
+ topLvlSelectedIndex: '@?',
+ hideSearch: '=',
+ searchBind: '=',
+ version: '@',
+ notificationIconCallback: '=',
+ menuModel: '=?',
+ };
+
+ template = ():string => {
+ return this.$templateCache.get('/app/scripts/directives/layout/top-nav/top-nav.html');
+ };
+
+ public link = (scope:ITopNavScope, $elem:ng.IAugmentedJQuery, $attrs:angular.IAttributes) => {
+
+ let getTopLvlSelectedIndexByState = ():number => {
+ if (!scope.topLvlMenu.menuItems) {
+ return 0;
+ }
+
+ let result = -1;
+
+ //set result to current state
+ scope.topLvlMenu.menuItems.forEach((item:Utils.MenuItem, index:number)=> {
+ if (item.state === this.$state.current.name) {
+ result = index;
+ }
+ });
+
+ //if it's a different state , checking previous state param
+ if (result === -1) {
+ scope.topLvlMenu.menuItems.forEach((item:Utils.MenuItem, index:number)=> {
+ if (item.state === this.$state.params['previousState']) {
+ result = index;
+ }
+ });
+ }
+
+ if (result === -1) {
+ result = 0;
+ }
+
+ return result;
+ };
+
+ scope.user = this.userResourceService.getLoggedinUser();
+
+ let tmpArray:Array<Utils.MenuItem> = [
+ new Utils.MenuItem(this.$filter('translate')("TOP_MENU_HOME_BUTTON"), null, "dashboard", "goToState", null, null),
+ new Utils.MenuItem(this.$filter('translate')("TOP_MENU_CATALOG_BUTTON"), null, "catalog", "goToState", null, null)
+ ];
+
+ // Only designer can perform onboarding
+ if (scope.user && scope.user.role === 'DESIGNER'){
+ tmpArray.push(new Utils.MenuItem(this.$filter('translate')("TOP_MENU_ON_BOARD_BUTTON"), null, "onboardVendor", "goToState", null, null));
+ }
+
+ scope.topLvlMenu = new Utils.MenuItemGroup(0, tmpArray , true );
+ scope.topLvlMenu.selectedIndex = isNaN(scope.topLvlSelectedIndex) ? getTopLvlSelectedIndexByState() : scope.topLvlSelectedIndex;
+
+ let generateMenu = () => {
+ if (scope.menuModel && scope.menuModel[0] !== scope.topLvlMenu) {
+ scope.menuModel.unshift(scope.topLvlMenu);
+ }
+ };
+ scope.$watch('menuModel', generateMenu);
+
+ generateMenu();
+
+ /////scope functions////
+
+ scope.goToState = (state:string, params:Array<any>):ng.IPromise<boolean> => {
+ let deferred = this.$q.defer();
+ this.$state.go(state, params && params.length > 0 ? [0] : undefined);
+ deferred.resolve(true);
+ return deferred.promise;
+ };
+
+ scope.menuItemClick = (itemGroup:Utils.MenuItemGroup, item:Utils.MenuItem) => {
+
+ itemGroup.itemClick = false;
+
+ let onSuccess = ():void => {
+ itemGroup.selectedIndex = itemGroup.menuItems.indexOf(item);
+ };
+ let onFailed = ():void => {};
+
+ if (item.callback) {
+ (item.callback.apply(undefined, item.params)).then(onSuccess, onFailed);
+ } else {
+ scope[item.action](item.state, item.params).then(onSuccess, onFailed);
+ }
+ };
+ };
+
+ public static factory = ($templateCache:ng.ITemplateCacheService, $filter:ng.IFilterService, $state:ng.ui.IStateService, $q: ng.IQService, userResourceService: Sdc.Services.IUserResourceClass)=> {
+ return new TopNavDirective($templateCache, $filter, $state,$q, userResourceService);
+ };
+
+ }
+
+ TopNavDirective.factory.$inject = ['$templateCache', '$filter', '$state','$q', 'Sdc.Services.UserResourceService'];
+}
diff --git a/catalog-ui/app/scripts/directives/layout/top-progress/top-progress.html b/catalog-ui/app/scripts/directives/layout/top-progress/top-progress.html
new file mode 100644
index 0000000000..ab2c8e364e
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/layout/top-progress/top-progress.html
@@ -0,0 +1,22 @@
+<div class="top-progress">
+
+ <!--======================= Top progress var =======================-->
+ <div data-ng-if="progressValue>0 && progressValue<100">
+ <span class="sdc-progress-title">{{progressMessage}}<span class="progress-percentage">{{progressValue}}&nbsp;%</span></span>
+ <div class="sdc-progress">
+ <div class="progress-bar progress-bar-striped active" role="progressbar" aria-valuenow="{{progressValue}}" aria-valuemin="0" aria-valuemax="100" data-ng-style="{width: progressValue+'%'}"></div>
+ </div>
+ </div>
+
+ <div class="sdc-progress-success-wrapper" data-ng-if="progressValue===100">
+ <span class="sdc-progress-success"></span>
+ <span class="sdc-progress-success-title">{{progressMessage}}</span>
+ </div>
+
+ <div class="sdc-progress-error-wrapper" data-ng-if="progressValue===-1">
+ <span class="sdc-progress-error"></span>
+ <span class="sdc-progress-error-title">{{progressMessage}}</span>
+ </div>
+ <!--======================= Top progress var =======================-->
+
+</div>
diff --git a/catalog-ui/app/scripts/directives/layout/top-progress/top-progress.less b/catalog-ui/app/scripts/directives/layout/top-progress/top-progress.less
new file mode 100644
index 0000000000..acce826f80
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/layout/top-progress/top-progress.less
@@ -0,0 +1,58 @@
+.top-progress {
+ text-align: left;
+
+ .sdc-progress-title {
+ .n_12_r;
+
+ .progress-percentage {
+ float: right;
+ }
+ }
+
+ .sdc-progress {
+ position: relative;
+ display: block;
+ height: 6px;
+ background-color: @main_color_o;
+ border-radius: 3px;
+ box-shadow: inset 0 1px 2px rgba(0,0,0,.1);
+
+ .progress-bar {
+ border-radius: 3px;
+ background-color: @main_color_a;
+ }
+
+ }
+
+ .sdc-progress-success-wrapper {
+ display: flex;
+ align-items: flex-end;
+
+ .sdc-progress-success-title {
+ .d_12_r;
+ margin-left: 10px;
+ }
+
+ .sdc-progress-success {
+ .sprite-new;
+ .success-circle;
+ }
+ }
+
+ .sdc-progress-error-wrapper {
+ display: flex;
+ align-items: flex-end;
+
+ .sdc-progress-error-title {
+ .q_12_r;
+ margin-left: 10px;
+ }
+
+ .sdc-progress-error {
+ .sprite-new;
+ .error-icon;
+ }
+
+ }
+
+}
diff --git a/catalog-ui/app/scripts/directives/layout/top-progress/top-progress.ts b/catalog-ui/app/scripts/directives/layout/top-progress/top-progress.ts
new file mode 100644
index 0000000000..8e8a289281
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/layout/top-progress/top-progress.ts
@@ -0,0 +1,57 @@
+/*-
+ * ============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.Directives {
+ 'use strict';
+
+ export interface ITopProgressScope extends ng.IScope {
+ progressValue:number;
+ progressMessage:string;
+ }
+
+ export class TopProgressDirective implements ng.IDirective {
+
+ constructor(private $templateCache:ng.ITemplateCacheService) {}
+
+ public replace = true;
+ public restrict = 'E';
+ public transclude = false;
+
+ scope = {
+ progressValue: '=',
+ progressMessage: '='
+ };
+
+ template = ():string => {
+ return this.$templateCache.get('/app/scripts/directives/layout/top-progress/top-progress.html');
+ };
+
+ public link = (scope:ITopProgressScope, $elem:ng.IAugmentedJQuery, $attrs:angular.IAttributes) => {
+
+ };
+
+ public static factory = ($templateCache:ng.ITemplateCacheService)=> {
+ return new TopProgressDirective($templateCache);
+ };
+
+ }
+
+ TopProgressDirective.factory.$inject = ['$templateCache'];
+}
diff --git a/catalog-ui/app/scripts/directives/loader/loader-directive.html b/catalog-ui/app/scripts/directives/loader/loader-directive.html
new file mode 100644
index 0000000000..e40b059a57
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/loader/loader-directive.html
@@ -0,0 +1,4 @@
+<div data-ng-if="display" data-tests-id="tlv-loader">
+ <div class="tlv-loader-back " data-ng-class="{'tlv-loader-relative':relative}"></div>
+ <div class="tlv-loader {{size}}"></div>
+</div>
diff --git a/catalog-ui/app/scripts/directives/loader/loader-directive.less b/catalog-ui/app/scripts/directives/loader/loader-directive.less
new file mode 100644
index 0000000000..ae0b41aab1
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/loader/loader-directive.less
@@ -0,0 +1,74 @@
+.tlv-loader-back {
+ background-color: @main_color_p;
+ position: fixed;
+ top: 50px;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ z-index: 9999;
+ opacity: 0.5;
+}
+
+.tlv-loader-relative { position: absolute; top: 0;}
+
+.tlv-loader {
+ z-index: 10002;
+}
+
+@keyframes fadein {
+ from { opacity: 0; }
+ to { opacity: 0.8; }
+}
+
+/* Firefox < 16 */
+@-moz-keyframes fadein {
+ from { opacity: 0; }
+ to { opacity: 0.8; }
+}
+
+/* Safari, Chrome and Opera > 12.1 */
+@-webkit-keyframes fadein {
+ from { opacity: 0; }
+ to { opacity: 0.8; }
+}
+
+/* Internet Explorer */
+@-ms-keyframes fadein {
+ from { opacity: 0; }
+ to { opacity: 0.8; }
+}
+
+/* Opera < 12.1 */
+@-o-keyframes fadein {
+ from { opacity: 0; }
+ to { opacity: 0.8; }
+}
+
+@keyframes fadeout {
+ from { opacity: 0.8; }
+ to { opacity: 0; }
+}
+
+/* Firefox < 16 */
+@-moz-keyframes fadeout {
+ from { opacity: 0.8; }
+ to { opacity: 0; }
+}
+
+/* Safari, Chrome and Opera > 12.1 */
+@-webkit-keyframes fadeout {
+ from { opacity: 0.8; }
+ to { opacity: 0; }
+}
+
+/* Internet Explorer */
+@-ms-keyframes fadeout {
+ from { opacity: 0.8; }
+ to { opacity: 0; }
+}
+
+/* Opera < 12.1 */
+@-o-keyframes fadeout {
+ from { opacity: 0.8; }
+ to { opacity: 0; }
+}
diff --git a/catalog-ui/app/scripts/directives/loader/loader-directive.ts b/catalog-ui/app/scripts/directives/loader/loader-directive.ts
new file mode 100644
index 0000000000..77c8977ac5
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/loader/loader-directive.ts
@@ -0,0 +1,155 @@
+/*-
+ * ============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.Directives {
+ 'use strict';
+ export interface ILoaderScope extends ng.IScope {
+ display: boolean; // Toggle show || hide scroll
+ size: string; // small || medium || large
+ elementSelector: string; // Jquery selector to hide and scroll inside
+ relative: boolean; // Will use the parent of <loader> element and hide it and scroll inside
+ loaderType: string;
+ }
+
+ export class LoaderDirective implements ng.IDirective {
+
+ constructor(private $templateCache: ng.ITemplateCacheService, private EventListenerService: Services.EventListenerService) {
+ }
+
+ /*
+ * relative is used when inserting the HTML loader inside some div <loader data-display="isLoading" relative="true"></loader>
+ * elementSelector when we want to pass the Jquery selector of the loader.
+ */
+ scope = {
+ display: '=',
+ size: '@?',
+ elementSelector: '@?',
+ relative: '=?',
+ loaderType: '@?'
+ };
+
+ public replace = false;
+ public restrict = 'E';
+ template = (): string => {
+ return this.$templateCache.get('/app/scripts/directives/loader/loader-directive.html');
+ };
+
+ link = (scope: ILoaderScope, element: any) => {
+
+ let interval;
+
+ this.EventListenerService.registerObserverCallback(Utils.Constants.EVENTS.SHOW_LOADER_EVENT, (loaderType)=> {
+ if (scope.loaderType !== loaderType) {
+ return;
+ }
+ scope.display = true;
+ });
+ this.EventListenerService.registerObserverCallback(Utils.Constants.EVENTS.HIDE_LOADER_EVENT, (loaderType)=> {
+ if (scope.loaderType !== loaderType) {
+ return;
+ }
+ scope.display = false;
+ });
+
+ let calculateSizesForFixPosition = (positionStyle: string): void => {
+ // This is problematic, I do not want to change the parent position.
+ // set the loader on all the screen
+ let parentPosition = element.parent().position();
+ let parentWidth = element.parent().width();
+ let parentHeight = element.parent().height();
+ element.css('position', positionStyle);
+ element.css('top', parentPosition.top);
+ element.css('left', parentPosition.left);
+ element.css('width', parentWidth);
+ element.css('height', parentHeight);
+ };
+
+ let setStyle = (positionStyle: string): void => {
+
+ switch (positionStyle) {
+ case 'absolute':
+ case 'fixed':
+ // The parent size is not set yet, still loading, so need to use interval to update the size.
+ interval = window.setInterval(()=> {
+ calculateSizesForFixPosition(positionStyle);
+ }, 2000);
+ break;
+ default:
+ // Can change the parent position to relative without causing style issues.
+ element.parent().css('position', 'relative');
+ break;
+ }
+ };
+
+ // This should be executed after the dom loaded
+ window.setTimeout((): void => {
+
+ element.css('display', 'none');
+
+ if (scope.elementSelector) {
+ let elemParent = angular.element(scope.elementSelector);
+ let positionStyle: string = elemParent.css('position');
+ setStyle(positionStyle);
+ }
+
+ if (scope.relative === true) {
+ let positionStyle: string = element.parent().css('position');
+ setStyle(positionStyle);
+ }
+
+ if (!scope.size) {
+ scope.size = 'large';
+ }
+
+ }, 0);
+
+ if (scope.elementSelector) {
+
+ }
+
+ function cleanUp() {
+ clearInterval(interval);
+ }
+
+ scope.$watch("display", (newVal, oldVal) => {
+ element.css('display', 'none');
+ if (newVal === true) {
+ window.setTimeout((): void => {
+ element.css('display', 'block');
+ }, 500);
+ } else {
+ window.setTimeout((): void => {
+ element.css('display', 'none');
+ }, 0);
+ }
+ });
+
+ scope.$on('$destroy', cleanUp);
+
+ };
+
+ public static factory = ($templateCache: ng.ITemplateCacheService, EventListenerService: Services.EventListenerService)=> {
+ return new LoaderDirective($templateCache, EventListenerService);
+ };
+
+ }
+
+ LoaderDirective.factory.$inject = ['$templateCache', 'EventListenerService'];
+}
diff --git a/catalog-ui/app/scripts/directives/modal/sdc-modal.html b/catalog-ui/app/scripts/directives/modal/sdc-modal.html
new file mode 100644
index 0000000000..a8419f162d
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/modal/sdc-modal.html
@@ -0,0 +1,18 @@
+<div data-ng-class="{'w-sdc-modal': type===undefined, 'w-sdc-classic-modal': type==='classic'}">
+ <div class="w-sdc-modal-head">
+ <span data-ng-if="header" class="w-sdc-modal-head-text">{{header}}</span>
+ <span data-ng-if="headerTranslate" class="w-sdc-modal-head-text" translate="{{headerTranslate}}" translate-values="{{headerTranslateValues}}"></span>
+ <div data-ng-if="showCloseButton==='true'" class="w-sdc-modal-close" data-ng-click="cancel()"></div>
+ </div>
+ <div class="w-sdc-modal-body" data-ng-class="{'classic': type==='classic'}">
+ <ng-transclude></ng-transclude>
+ </div>
+ <div class="w-sdc-modal-footer" data-ng-if="type==='classic' && buttons!==undefined">
+ <button data-ng-repeat="button in buttons"
+ data-tests-id="{{button.name}}"
+ class="tlv-btn {{button.css}}"
+ data-ng-class="{'disabled': button.disabled===true}"
+ data-ng-disabled="button.disabled===true"
+ data-ng-click="button.callback()">{{button.name}}</button>
+ </div>
+</div>
diff --git a/catalog-ui/app/scripts/directives/modal/sdc-modal.less b/catalog-ui/app/scripts/directives/modal/sdc-modal.less
new file mode 100644
index 0000000000..d8dfdbb73b
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/modal/sdc-modal.less
@@ -0,0 +1,10 @@
+.ellipsis-directive-more-less {
+ .a_9;
+ .bold;
+ .hand;
+ float: right;
+ margin-right: 17px;
+ line-height: 23px;
+ text-decoration: underline;
+ text-align: left;
+}
diff --git a/catalog-ui/app/scripts/directives/modal/sdc-modal.ts b/catalog-ui/app/scripts/directives/modal/sdc-modal.ts
new file mode 100644
index 0000000000..338035c9f1
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/modal/sdc-modal.ts
@@ -0,0 +1,103 @@
+/*-
+ * ============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.Directives {
+ 'use strict';
+
+ export interface ISdcModalScope extends ng.IScope {
+ modal:ng.ui.bootstrap.IModalServiceInstance;
+ hideBackground:string;
+ ok():void;
+ close(result:any):void;
+ cancel(reason:any):void;
+ }
+
+ export interface ISdcModalButton {
+ name:string;
+ css:string;
+ disabled?:boolean;
+ callback:Function;
+ }
+
+ export class SdcModalDirective implements ng.IDirective {
+
+ constructor(
+ private $templateCache: ng.ITemplateCacheService
+ ) {}
+
+ scope = {
+ modal: '=',
+ type: '@',
+ header: '@',
+ headerTranslate: '@',
+ headerTranslateValues: '@',
+ showCloseButton: '@',
+ hideBackground: '@',
+ buttons: '=',
+ getCloseModalResponse: '='
+ };
+
+ public replace = true;
+ public restrict = 'E';
+ public transclude = true;
+
+ template = (): string => {
+ return this.$templateCache.get('/app/scripts/directives/modal/sdc-modal.html');
+ };
+
+ link = (scope:ISdcModalScope, $elem:any) => {
+
+ if (scope.hideBackground==="true"){
+ $(".modal-backdrop").css('opacity','0');
+ }
+
+ scope.close = function (result:any) {
+ scope.modal.close(result);
+ };
+
+ scope.ok = function () {
+ scope.modal.close();
+ };
+
+ scope.cancel = function (reason:any) {
+ if(this.getCloseModalResponse)
+ scope.modal.dismiss(this.getCloseModalResponse());
+ else {
+ scope.modal.dismiss();
+ }
+ };
+
+ if (scope.modal) {
+ scope.modal.result.then(function (selectedItem) {
+ //$scope.selected = selectedItem;
+ }, function () {
+ //console.info('Modal dismissed at: ' + new Date());
+ });
+ }
+ }
+
+ public static factory = ($templateCache: ng.ITemplateCacheService)=> {
+ return new SdcModalDirective($templateCache);
+ };
+
+ }
+
+ SdcModalDirective.factory.$inject = ['$templateCache'];
+}
diff --git a/catalog-ui/app/scripts/directives/page-scroller/page-scroller.html b/catalog-ui/app/scripts/directives/page-scroller/page-scroller.html
new file mode 100644
index 0000000000..7359386901
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/page-scroller/page-scroller.html
@@ -0,0 +1,22 @@
+<div class="sdc-page-scroller">
+
+ <nav data-ng-if="showNav!==false" class="welcome-nav">
+ <div data-ng-if="showCloseButton===true" data-ng-click="onCloseButtonClick()" class="asdc-welcome-close"></div>
+ <ul>
+ <li data-ng-repeat="slide in slidesData | orderBy:'+position'"><a href="#{{slide.id}}" data-ng-click="onNavButtonClick(slide)" class=""></a></li>
+ </ul>
+ </nav>
+
+ <div class="nav-previous-next" data-ng-if="showPreviousNext===true">
+ <span class="go-prev" data-ng-click="goToPrevSlide()">previous slide</span>
+ <span class="go-next" data-ng-click="goToNextSlide()">next slide</span>
+ </div>
+
+ <div class="slides-container">
+ <section data-ng-repeat="slide in slidesData | orderBy:'+position'" class="slide" id="{{slide.id}}" on-last-repeat>
+ <ng-include src="slide.url"></ng-include>
+ </section>
+ </div>
+
+</div>
+
diff --git a/catalog-ui/app/scripts/directives/page-scroller/page-scroller.less b/catalog-ui/app/scripts/directives/page-scroller/page-scroller.less
new file mode 100644
index 0000000000..14f8568f07
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/page-scroller/page-scroller.less
@@ -0,0 +1,98 @@
+.sdc-page-scroller {
+
+ /****************** Navigation ***************/
+ nav {
+ position: fixed;
+ top: 0;
+ right: 0;
+ z-index: 100;
+ display: flex;
+ flex-direction: column;
+ width: 100px;
+ bottom: 0;
+ background-color: #000;
+ align-items: center;
+ justify-content: center;
+ }
+
+ nav ul {
+ list-style: none;
+ text-align: center;
+ margin-top: 0;
+ padding: 0;
+ }
+
+ nav ul li {
+ display: block;
+ margin-bottom: 15px;
+
+ }
+
+ nav ul li:last-child {
+
+ }
+
+ nav a {
+ display: block;
+ height: 6px;
+ width: 6px;
+ border-radius: 50%;
+ background-color: #4a4c4d;
+ }
+
+ nav a.active {
+ position: relative;
+ }
+
+ nav a.active::after {
+ content: '';
+ display: block;
+ position: absolute;
+ border: 2px solid #0198d1;
+ width: 16px;
+ height: 16px;
+ border-radius: 50%;
+ top: -5px;
+ left: -5px;
+ }
+
+ /****************** Previous Next navigation ***************/
+ .go-prev, .go-next {
+ cursor: pointer;
+ font-weight: bold;
+ text-decoration: underline;
+ }
+
+ .slides-container {
+ position: absolute;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ overflow-y: hidden;
+ z-index: 10;
+ }
+
+ .slide {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ }
+
+ .slide .centered {
+ width: 60%;
+ margin: 200px auto 0;
+ }
+
+ .slide .centered h1 {
+ text-align: center;
+ }
+
+ .slide .centered p {
+ text-align: center;
+ margin-top: 20px;
+ font-size: 20px;
+ }
+
+}
diff --git a/catalog-ui/app/scripts/directives/page-scroller/page-scroller.ts b/catalog-ui/app/scripts/directives/page-scroller/page-scroller.ts
new file mode 100644
index 0000000000..bb89f9a55a
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/page-scroller/page-scroller.ts
@@ -0,0 +1,247 @@
+/*-
+ * ============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.Directives {
+ 'use strict';
+
+ export interface SlideData {
+ url: string;
+ id: string;
+ index: number;
+ callback: Function;
+ }
+
+ export interface ISdcPageScrollDirectiveScope extends ng.IScope {
+ slidesData:Array<SlideData>;
+ showNav: boolean;
+ showPreviousNext: boolean;
+ currentSlide:SlideData;
+ showCloseButton:boolean;
+ closeButtonCallback:Function;
+ startSlideIndex:number;
+
+ onNavButtonClick(slideName):void;
+ onCloseButtonClick():void;
+ goToPrevSlide():void;
+ goToNextSlide():void;
+ goToSlide(slide:SlideData):void;
+ onSlideChangeEnd():void;
+ onMouseWheel(event):void;
+ onKeyDown(event):void;
+ onResize(event):void;
+ gotoSlideIndex(index):void;
+ }
+
+ export class SdcPageScrollDirective implements ng.IDirective {
+
+ constructor(private $templateCache:ng.ITemplateCacheService) {
+
+ }
+
+ scope = {
+ slidesData: '=',
+ showNav: '=',
+ showPreviousNext: '=',
+ showCloseButton: '=',
+ closeButtonCallback: '=',
+ startSlideIndex: '=?'
+ };
+
+ public replace = true;
+ public restrict = 'E';
+ private delayExec:any;
+
+ template = ():string => {
+ return this.$templateCache.get('/app/scripts/directives/page-scroller/page-scroller.html');
+ };
+
+ link = ($scope:ISdcPageScrollDirectiveScope, $elem:JQuery, attr:any) => {
+ let isAnimating = false; //Animating flag - is our app animating
+ let pageHeight = $(window).innerHeight(); //The height of the window
+ let slidesContainer;
+ let navButtons;
+ let slides:any; //Only graph-links that starts with
+
+ //Key codes for up and down arrows on keyboard. We'll be using this to navigate change slides using the keyboard
+ let keyCodes = {
+ UP : 38,
+ DOWN: 40
+ };
+
+ $scope.onCloseButtonClick = ():void => {
+ if ($scope.closeButtonCallback){
+ $scope.closeButtonCallback();
+ };
+ };
+
+ // Wait for the dom to load (after ngRepeat).
+ $scope.$on('onRepeatLast', (scope, element, attrs) => {
+ slides = $(".slide", slidesContainer);
+ slidesContainer = $(".slides-container");
+ navButtons = $("nav a").filter("[href^='#']");
+
+ // Adding event listeners
+ $(window).on("resize", (e) => {$scope.onResize(e);}).resize();
+ $(window).on("mousewheel DOMMouseScroll", (e) => {$scope.onMouseWheel(e);});
+ $(document).on("keydown", (e) => {$scope.onKeyDown(e);});
+
+ //Going to the first slide
+ if ($scope.startSlideIndex){
+ $scope.gotoSlideIndex($scope.startSlideIndex);
+ } else {
+ $scope.gotoSlideIndex(0);
+ }
+
+ });
+
+ $scope.gotoSlideIndex = (index) => {
+ $scope.goToSlide($scope.slidesData[index]);
+ };
+
+ // When a button is clicked - first get the button href, and then slide to the container, if there's such a container
+ $scope.onNavButtonClick = (slide:SlideData):void => {
+ $scope.goToSlide(slide);
+ };
+
+ // If there's a previous slide, slide to it
+ $scope.goToPrevSlide = ():void => {
+ let previousSlide = $scope.slidesData[$scope.currentSlide.index-1];
+ if (previousSlide) {
+ $scope.goToSlide(previousSlide);
+ }
+ };
+
+ // If there's a next slide, slide to it
+ $scope.goToNextSlide = ():void => {
+ let nextSlide = $scope.slidesData[$scope.currentSlide.index+1];
+ if (nextSlide) {
+ $scope.goToSlide(nextSlide);
+ }
+ };
+
+ // Actual transition between slides
+ $scope.goToSlide = (slide:SlideData):void => {
+ //console.log("start goToSlide");
+ //If the slides are not changing and there's such a slide
+ if(!isAnimating && slide) {
+ //setting animating flag to true
+ isAnimating = true;
+ $scope.currentSlide = slide;
+ $scope.currentSlide.callback();
+
+ //Sliding to current slide
+ let calculatedY = pageHeight * ($scope.currentSlide.index);
+ //console.log("$scope.currentSlide.index: " + $scope.currentSlide.index + " | calculatedY: " + calculatedY);
+
+ $('.slides-container').animate(
+ {
+ scrollTop: calculatedY + 'px'
+ },
+ {
+ duration: 1000,
+ specialEasing: {
+ width: "linear",
+ height: "easeInOutQuart"
+ },
+ complete: function() {
+ $scope.onSlideChangeEnd();
+ }
+ }
+ );
+
+ //Animating menu items
+ $(".sdc-page-scroller nav a.active").removeClass("active");
+ $(".sdc-page-scroller nav [href='#" + $scope.currentSlide.id + "']").addClass("active");
+ }
+ };
+
+ // Once the sliding is finished, we need to restore "isAnimating" flag.
+ // You can also do other things in this function, such as changing page title
+ $scope.onSlideChangeEnd = ():void => {
+
+
+
+ isAnimating = false;
+ };
+
+ // When user scrolls with the mouse, we have to change slides
+ $scope.onMouseWheel = (event):void => {
+ //Normalize event wheel delta
+ let delta = event.originalEvent.wheelDelta / 30 || -event.originalEvent.detail;
+
+ //If the user scrolled up, it goes to previous slide, otherwise - to next slide
+ if(delta < -1) {
+ this.delayAction($scope.goToNextSlide);
+ } else if(delta > 1) {
+ this.delayAction($scope.goToPrevSlide);
+ }
+ event.preventDefault();
+ };
+
+ // Getting the pressed key. Only if it's up or down arrow, we go to prev or next slide and prevent default behaviour
+ // This way, if there's text input, the user is still able to fill it
+ $scope.onKeyDown = (event):void => {
+ let PRESSED_KEY = event.keyCode;
+
+ if(PRESSED_KEY == keyCodes.UP){
+ $scope.goToPrevSlide();
+ event.preventDefault();
+ } else if(PRESSED_KEY == keyCodes.DOWN){
+ $scope.goToNextSlide();
+ event.preventDefault();
+ }
+ };
+
+ // When user resize it's browser we need to know the new height, so we can properly align the current slide
+ $scope.onResize = (event):void => {
+ //This will give us the new height of the window
+ let newPageHeight = $(window).innerHeight();
+
+ // If the new height is different from the old height ( the browser is resized vertically ), the slides are resized
+ if(pageHeight !== newPageHeight) {
+ pageHeight = newPageHeight;
+ }
+ };
+ };
+
+ private initSlides = ():void => {
+ //pageHeight
+ };
+
+ private delayAction = (action:Function):void => {
+ clearTimeout(this.delayExec);
+ this.delayExec = setTimeout(function () {
+ action();
+ }, 100);
+ };
+
+ public static factory = ($templateCache:ng.ITemplateCacheService)=> {
+ return new SdcPageScrollDirective($templateCache);
+ };
+
+ }
+
+ SdcPageScrollDirective.factory.$inject = ['$templateCache'];
+
+}
+
+
+
+
diff --git a/catalog-ui/app/scripts/directives/perfect-scrollbar/angular-perfect-scrollbar.ts b/catalog-ui/app/scripts/directives/perfect-scrollbar/angular-perfect-scrollbar.ts
new file mode 100644
index 0000000000..b53a059a40
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/perfect-scrollbar/angular-perfect-scrollbar.ts
@@ -0,0 +1,159 @@
+/*-
+ * ============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.Directives {
+
+ 'use strict';
+
+ export interface IPerfectScrollerScope extends ng.IScope {
+ //update(event:string): void;
+ }
+
+ export class PerfectScrollerDirective implements ng.IDirective {
+
+ constructor(
+ private $templateCache: ng.ITemplateCacheService,
+ private $parse:any,
+ private $window:any) {
+
+ }
+
+ replace = true;
+ restrict = 'EA';
+ transclude = true;
+
+ template = (): string => {
+ return '<div><div ng-transclude></div></div>';
+ };
+
+ link = ($scope:IPerfectScrollerScope, $elem, $attr) => {
+ let self = this;
+ let options = {};
+
+ let psOptions = [
+ 'wheelSpeed', 'wheelPropagation', 'minScrollbarLength', 'useBothWheelAxes',
+ 'useKeyboard', 'suppressScrollX', 'suppressScrollY', 'scrollXMarginOffset',
+ 'scrollYMarginOffset', 'includePadding'//, 'onScroll', 'scrollDown'
+ ];
+
+ for (let i=0, l=psOptions.length; i<l; i++) {
+ let opt = psOptions[i];
+ if ($attr[opt] !== undefined) {
+ options[opt] = self.$parse($attr[opt])();
+ }
+ }
+
+ $scope.$evalAsync(function() {
+ $elem.perfectScrollbar(options);
+ let onScrollHandler = self.$parse($attr.onScroll)
+ $elem.scroll(function(){
+ let scrollTop = $elem.scrollTop()
+ let scrollHeight = $elem.prop('scrollHeight') - $elem.height()
+ $scope.$apply(function() {
+ onScrollHandler($scope, {
+ scrollTop: scrollTop,
+ scrollHeight: scrollHeight
+ })
+ })
+ });
+ });
+
+ /*
+ $scope.update = (event:string): void => {
+ $scope.$evalAsync(function() {
+ //if ($attr.scrollDown == 'true' && event != 'mouseenter') {
+ if (event != 'mouseenter') {
+ setTimeout(function () {
+ $($elem).scrollTop($($elem).prop("scrollHeight"));
+ }, 100);
+ }
+ $elem.perfectScrollbar('update');
+ });
+ };
+ */
+
+ // This is necessary when you don't watch anything with the scrollbar
+ $elem.bind('mouseenter', function(){
+ //console.log("mouseenter");
+ $elem.perfectScrollbar('update');
+ });
+
+ $elem.bind('mouseleave', function(){
+ //console.log("mouseleave");
+ setTimeout(function () {
+ $(window).trigger('mouseup');
+ $elem.perfectScrollbar('update');
+ }, 10);
+ });
+
+ $elem.bind('click', function(){
+ //console.log("click");
+ // Wait 500 milliseconds until the collapse finish closing and update.
+ setTimeout(function () {
+ $elem.perfectScrollbar('update');
+ }, 500);
+ });
+
+ /**
+ * Check if the content of the scroller was changed, and if changed update the scroller.
+ * Because DOMSubtreeModified event is fire many time (while filling the content), I'm checking that
+ * there is at least 100 milliseconds between DOMSubtreeModified events to update the scrollbar.
+ * @type {boolean}
+ */
+ let insideDOMSubtreeModified=false;
+ $elem.bind('DOMSubtreeModified', function(){
+ if (insideDOMSubtreeModified==false) {
+ insideDOMSubtreeModified=true;
+ setTimeout(function () {
+ insideDOMSubtreeModified=false;
+ $elem.perfectScrollbar('update');
+ }, 100);
+ }
+ });
+
+ // Possible future improvement - check the type here and use the appropriate watch for non-arrays
+ if ($attr.refreshOnChange) {
+ $scope.$watchCollection($attr.refreshOnChange, function() {
+ $elem.perfectScrollbar('update');
+ });
+ }
+
+ /*
+ // this is from a pull request - I am not totally sure what the original issue is but seems harmless
+ if ($attr.refreshOnResize) {
+ self.$window.on('resize', function(e){$scope.update(e)});
+ }
+ */
+
+ $elem.bind('$destroy', function() {
+ //self.$window.off('resize', function(e){$scope.update(e)});
+ $elem.perfectScrollbar('destroy');
+ });
+
+ };
+
+ public static factory = ($templateCache: ng.ITemplateCacheService, $parse:any, $window:any)=> {
+ return new PerfectScrollerDirective($templateCache, $parse, $window);
+ };
+
+ }
+
+ PerfectScrollerDirective.factory.$inject = ['$templateCache','$parse','$window'];
+}
diff --git a/catalog-ui/app/scripts/directives/print-graph-screen/print-graph-screen.ts b/catalog-ui/app/scripts/directives/print-graph-screen/print-graph-screen.ts
new file mode 100644
index 0000000000..8204928e6f
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/print-graph-screen/print-graph-screen.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.Directives {
+ 'use strict';
+
+ export interface IPrintGraphScreenScope extends ng.IScope {
+ entity:Models.Components.Component;
+ }
+
+
+ export class PrintGraphScreenDirective implements ng.IDirective {
+
+ constructor(
+ private $filter: ng.IFilterService,
+ private sdcMenu:Models.IAppMenu,
+ private sdcConfig:Models.IAppConfigurtaion,
+ private urlToBase64Service:Sdc.Services.UrlToBase64Service
+ ) {}
+
+ scope = {
+ entity: '='
+ };
+ restrict = 'A';
+ link = (scope:IPrintGraphScreenScope, element:any) => {
+
+
+ element.bind('click', function() {
+ printScreen();
+ });
+
+
+ // TODO we need to implement export to PDF in cytoscape
+ let printScreen = ():void => {
+
+ //
+ // let pdf :any = new jsPDF('landscape', 'mm', 'a4');
+ // pdf.setProperties({
+ // title: scope.entity.name,
+ // subject: 'Design Snapshot for ' + scope.entity.name,
+ // author: scope.entity.creatorFullName,
+ // keywords: scope.entity.tags.join(', '),
+ // creator: scope.entity.creatorFullName
+ // });
+ //
+ // // A4 measures is 210 × 297 millimeters
+ // let pdfWidth :number = 297,
+ // pdfHeight :number = 210,
+ // leftColumnWidth :number = 80;
+ //
+ // //left bar background
+ // pdf.setDrawColor(0);
+ // pdf.setFillColor(248, 249, 251);
+ // pdf.rect(0, 0, leftColumnWidth, pdfHeight, 'F');
+ //
+ // //entity name
+ // pdf.setFontSize(12);
+ // pdf.setTextColor(38, 61, 77);
+ // let splitTitle :any = pdf.splitTextToSize(scope.entity.name, 50);
+ // pdf.text(22, 15 - (splitTitle.length - 1) * 2, splitTitle);
+ //
+ // //line
+ // pdf.setLineWidth(0.2);
+ // pdf.setDrawColor(208, 209, 213);
+ // pdf.line(0, 28, leftColumnWidth, 28);
+ //
+ //
+ // pdf.setFontSize(10);
+ // let properties :any = getPdfProperties();
+ //
+ // let topOffset :number = 39, lines;
+ // properties.forEach( (item:any) => {
+ // if (!item.value) {
+ // return;
+ // }
+ // if (item.title === 'Description:') {
+ // topOffset += 5;
+ // }
+ //
+ // pdf.setTextColor(38, 61, 77);
+ // pdf.text(5, topOffset, item.title);
+ // pdf.setTextColor(102, 102, 102);
+ // lines = pdf.splitTextToSize(item.value, 49);
+ // pdf.text(5 + item.offset, topOffset, lines[0]);
+ // if (lines.length > 1) {
+ // lines = pdf.splitTextToSize(item.value.substring(lines[0].length + 1), 65);
+ // if (lines.length > 8) {
+ // lines = lines.slice(0, 7);
+ // lines[lines.length - 1] += '...';
+ // }
+ // pdf.text(5, topOffset + 4, lines);
+ // topOffset += 4 * (lines.length);
+ // }
+ //
+ // topOffset += 6;
+ // });
+ //
+ //
+ // //another background in case the text was too long
+ // let declarationLineOffset :number = 176;
+ // pdf.setDrawColor(0);
+ // pdf.setFillColor(248, 249, 251);
+ // pdf.rect(0, declarationLineOffset, leftColumnWidth, pdfHeight - declarationLineOffset, 'F');
+ // //line
+ // pdf.setLineWidth(0.2);
+ // pdf.setDrawColor(208, 209, 213);
+ // pdf.line(0, declarationLineOffset, leftColumnWidth, declarationLineOffset);
+ //
+ // //declaration
+ // pdf.setFontSize(10.5);
+ // pdf.setTextColor(38, 61, 77);
+ // pdf.text(5, 185, 'Declaration');
+ // pdf.setFontSize(9);
+ // pdf.setTextColor(102, 102, 102);
+ // pdf.setFontType('bold');
+ // pdf.text(5, 190, this.$filter('translate')('PDF_FILE_DECLARATION_BOLD'));
+ // pdf.setFontType('normal');
+ // pdf.text(5, 194, pdf.splitTextToSize(this.$filter('translate')('PDF_FILE_DECLARATION'), 65));
+ //
+ // //entity icon
+ // let self = this;
+ // let addEntityIcon:Function = () => {
+ // let iconPath:string = self.sdcConfig.imagesPath + '/styles/images/';
+ // if (scope.entity.isService()) {
+ // iconPath += 'service-icons/' + scope.entity.icon + '.png';
+ // } else {
+ // iconPath += 'resource-icons/' + scope.entity.icon + '.png';
+ // }
+ // self.urlToBase64Service.downloadUrl(iconPath, (base64string:string):void => {
+ // if (base64string) {
+ // pdf.addImage(base64string, 'JPEG', 5, 7, 15, 15);
+ // }
+ // pdf.save(scope.entity.name + '.pdf');
+ // });
+ // };
+ //
+ // //actual snapshop of canvas
+ //
+ // let diagramDiv :any = document.getElementById('myDiagram');
+ // let diagram :any = null;// Sdc.Graph.Diagram.fromDiv(diagramDiv), canvasImg = new Image();
+ // diagram.startTransaction('print screen');
+ // let canvasImgBase64:any = diagram.makeImageData({
+ // //scale: 1,
+ // // size: new Sdc.Graph.Size(pdfHeight * 5, NaN),
+ // background: 'white',
+ // type: 'image/jpeg'
+ // });
+ // diagramDiv.firstElementChild.toDataURL();
+ // diagram.commitTransaction('print screen');
+ //
+ // canvasImg.onload = () => {
+ // if (canvasImg.height > 0) {
+ // let canvasImgRatio:number = Math.min((pdfWidth - leftColumnWidth - 15) / canvasImg.width, pdfHeight / canvasImg.height);
+ // let canvasImgWidth:number = canvasImg.width * canvasImgRatio,
+ // canvasImgHeight:number = canvasImg.height * canvasImgRatio;
+ // let canvasImgOffset:number = (pdfHeight - canvasImgHeight) / 2;
+ // pdf.addImage(canvasImg, 'JPEG', leftColumnWidth, canvasImgOffset, canvasImgWidth, canvasImgHeight);
+ //
+ // addEntityIcon();
+ // }
+ // };
+ //
+ // if(canvasImg.src === 'data:,') { //empty canvas
+ // addEntityIcon();
+ // } else {
+ // canvasImg.src = canvasImgBase64;
+ // }
+ };
+
+ let getPdfProperties = ():Array<any> => {
+ return [
+ {title: this.$filter('translate')('GENERAL_LABEL_TYPE'), value: scope.entity.getComponentSubType(), offset: 10},
+ {title: this.$filter('translate')('GENERAL_LABEL_VERSION'), value: scope.entity.version, offset: 15},
+ {title: this.$filter('translate')('GENERAL_LABEL_CATEGORY'), value: scope.entity.categories.length ? scope.entity.categories[0].name : '', offset: 16},
+ {title: this.$filter('translate')('GENERAL_LABEL_CREATION_DATE'), value: this.$filter('date')(scope.entity.creationDate, 'MM/dd/yyyy'), offset: 24},
+ {title: this.$filter('translate')('GENERAL_LABEL_AUTHOR'), value: scope.entity.creatorFullName, offset: 13},
+ {title: this.$filter('translate')('GENERAL_LABEL_CONTACT_ID'), value: scope.entity.contactId, offset: 41},
+ {title: this.$filter('translate')('GENERAL_LABEL_STATUS'), value: (<any>this.sdcMenu).LifeCycleStatuses[scope.entity.lifecycleState].text, offset: 13},
+ {title: this.$filter('translate')('GENERAL_LABEL_PROJECT_CODE'), value: scope.entity.projectCode, offset: 15},
+ {title: this.$filter('translate')('GENERAL_LABEL_DESCRIPTION'), value: scope.entity.description, offset: 20},
+ {title: this.$filter('translate')('GENERAL_LABEL_TAGS'), value: scope.entity.tags.join(', '), offset: 10}
+ ];
+ };
+
+ };
+
+ public static factory = ($filter:ng.IFilterService, sdcMenu:Models.IAppMenu, sdcConfig:Models.IAppConfigurtaion, urlToBase64Service:Sdc.Services.UrlToBase64Service)=> {
+ return new PrintGraphScreenDirective($filter, sdcMenu, sdcConfig, urlToBase64Service);
+ };
+
+ }
+
+ PrintGraphScreenDirective.factory.$inject = ['$filter', 'sdcMenu', 'sdcConfig', 'Sdc.Services.UrlToBase64Service'];
+}
diff --git a/catalog-ui/app/scripts/directives/property-types/data-type-fields-structure/data-type-fields-structure.html b/catalog-ui/app/scripts/directives/property-types/data-type-fields-structure/data-type-fields-structure.html
new file mode 100644
index 0000000000..b4583fd304
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/property-types/data-type-fields-structure/data-type-fields-structure.html
@@ -0,0 +1,82 @@
+<div class="data-type-fields-structure">
+ <div class="open-close">
+ <div class="open-close-button" data-ng-class="{'expand':expand,'collapse':!expand}" data-ng-click="expandAndCollapse()"></div>
+ <span class="data-type-name">{{typeName.replace("org.openecomp.datatypes.heat.","")}}</span>
+ </div>
+ <div data-ng-show="expand" data-ng-repeat="property in dataTypeProperties" class="property">
+ <div class="i-sdc-form-item property-name">
+ <div tooltips tooltip-content="{{property.name}}">
+ <input class="i-sdc-form-input"
+ type="text"
+ data-ng-disabled="true"
+ value="{{property.name}}"/>
+ </div>
+ </div>
+ <!--<div class="property-value">-->
+ <div data-ng-if="dataTypesService.isDataTypeForDataTypePropertyType(property,types)" class="inner-structure">
+ <fields-structure value-obj-ref="(valueObjRef[property.name])"
+ type-name="property.type"
+ parent-form-obj="parentFormObj"
+ fields-prefix-name="fieldsPrefixName+property.name"
+ read-only="readOnly"
+ default-value="{{currentTypeDefaultValue[property.name]}}"
+ types="types"></fields-structure>
+ </div>
+ <div data-ng-if="!dataTypesService.isDataTypeForDataTypePropertyType(property,types)" ng-switch="property.type">
+ <div ng-switch-when="map">
+ <type-map value-obj-ref="valueObjRef[property.name]"
+ schema-property="property.schema.property"
+ parent-form-obj="parentFormObj"
+ fields-prefix-name="fieldsPrefixName+property.name"
+ read-only="readOnly"
+ default-value="{{currentTypeDefaultValue[property.name]}}"
+ types="types"></type-map>
+ </div>
+ <div ng-switch-when="list">
+ <type-list value-obj-ref="valueObjRef[property.name]"
+ schema-property="property.schema.property"
+ parent-form-obj="parentFormObj"
+ fields-prefix-name="fieldsPrefixName+property.name"
+ read-only="readOnly"
+ default-value="{{currentTypeDefaultValue[property.name]}}"
+ types="types"></type-list>
+ </div>
+ <div ng-switch-default class="primitive-value-field">
+ <div class="i-sdc-form-item" data-ng-class="{error:(parentFormObj[fieldsPrefixName+property.name].$dirty && parentFormObj[fieldsPrefixName+property.name].$invalid)}">
+ <input class="i-sdc-form-input"
+ data-tests-id="{{fieldsPrefixName+property.name}}"
+ ng-if="!((property.simpleType||property.type) == 'boolean')"
+ data-ng-maxlength="100"
+ data-ng-disabled="readOnly"
+ maxlength="100"
+ data-ng-model="valueObjRef[property.name]"
+ type="text"
+ name="{{fieldsPrefixName+property.name}}"
+ data-ng-pattern="getValidationPattern((property.simpleType||property.type))"
+ data-ng-model-options="{ debounce: 200 }"
+ data-ng-change="!parentFormObj[fieldsPrefixName+property.name].$error.pattern && ('integer'==property.type && parentFormObj[fieldsPrefixName+property.name].$setValidity('pattern', validateIntRange(valueObjRef[property.name])) || onValueChange(property.name, (property.simpleType||property.type)))"
+ autofocus />
+ <select class="i-sdc-form-select"
+ data-tests-id="{{fieldsPrefixName+property.name}}"
+ ng-if="(property.simpleType||property.type) == 'boolean'"
+ data-ng-disabled="readOnly"
+ name="{{fieldsPrefixName+property.name}}"
+ data-ng-change="onValueChange(property.name,'boolean')"
+ data-ng-model="valueObjRef[property.name]"
+ data-ng-options="option.v as option.n for option in [{ n: '', v: undefined }, { n: 'false', v: false }, { n: 'true', v: true }]">
+ </select>
+
+ <div class="input-error" data-ng-show="parentFormObj[fieldsPrefixName+property.name].$dirty && parentFormObj[fieldsPrefixName+property.name].$invalid">
+ <span ng-show="parentFormObj[fieldsPrefixName+property.name].$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '100' }"></span>
+ <span ng-show="parentFormObj[fieldsPrefixName+property.name].$error.pattern" translate="PROPERTY_EDIT_PATTERN"></span>
+ <span ng-show="parentFormObj[fieldsPrefixName+property.name].$error.customValidation" translate="PROPERTY_EDIT_MAP_UNIQUE_KEYS"></span>
+ </div>
+ </div>
+ </div>
+ </div>
+ <!--</div>-->
+
+ </div>
+</div>
+
+
diff --git a/catalog-ui/app/scripts/directives/property-types/data-type-fields-structure/data-type-fields-structure.less b/catalog-ui/app/scripts/directives/property-types/data-type-fields-structure/data-type-fields-structure.less
new file mode 100644
index 0000000000..5c65fdc9dc
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/property-types/data-type-fields-structure/data-type-fields-structure.less
@@ -0,0 +1,90 @@
+.data-type-fields-structure{
+ background-color: @tlv_color_v;
+ padding:10px;
+ display: table-caption;
+ .open-close{
+ position: relative;
+ .open-close-button{
+ position: absolute;
+ top: 50%;
+ margin-top: -7px;
+ &.expand{
+ .sprite-new;
+ .expand-collapse-minus-icon;
+ }
+ &.collapse{
+ .sprite-new;
+ .expand-collapse-plus-icon;
+ }
+ }
+
+ }
+
+
+ .data-type-name{
+ .m_16_m;
+ margin-left: 22px;
+ }
+
+ .i-sdc-form-input:disabled{
+ .disabled;
+ }
+
+ .property{
+ display: flex;
+ min-width: 365px;
+ min-height: 46px;
+ input[type="text"],select{
+ width: 170px;
+ }
+ .property-name{
+ float: left;
+ margin-top: 8px;
+ }
+ .primitive-value-field{
+ float: right;
+ margin-top: 8px;
+ margin-left: 10px;
+ }
+ .inner-structure{
+ display: -webkit-box;
+ }
+ }
+
+ [ng-switch-when="map"]{
+ margin-top: 8px;
+ margin-left: 10px;
+ .map-item{
+ border: solid 1px @main_color_o;
+ min-width: 401px;
+ min-height: 69px;
+ float: none !important;
+ }
+ .add-map-item{
+ width: auto;
+ float: none;
+ &:nth-child(1){
+ position: relative;
+ top: 6px;
+ }
+ .add-btn{
+ float: none;
+ }
+ }
+
+ }
+
+ [ng-switch-when="list"]{
+ float: left;
+ margin-top: 8px;
+ margin-left: 10px;
+ min-width: 280px;
+ .dt-list-item {
+ border: solid 1px @main_color_o;
+ }
+ .list-value-items{
+ width:280px;
+ }
+ }
+}
+
diff --git a/catalog-ui/app/scripts/directives/property-types/data-type-fields-structure/data-type-fields-structure.ts b/catalog-ui/app/scripts/directives/property-types/data-type-fields-structure/data-type-fields-structure.ts
new file mode 100644
index 0000000000..94567ca36b
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/property-types/data-type-fields-structure/data-type-fields-structure.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 obarda on 1/27/2016.
+ */
+/// <reference path="../../../references"/>
+module Sdc.Directives {
+ 'use strict';
+
+ export interface IDataTypeFieldsStructureScope extends ng.IScope {
+ parentFormObj:ng.IFormController;
+ dataTypeProperties:Array<Models.DataTypePropertyModel>;
+ typeName:string;
+ valueObjRef:any;
+ propertyNameValidationPattern: RegExp;
+ fieldsPrefixName:string;
+ readOnly:boolean;
+ currentTypeDefaultValue:any;
+ types:Models.DataTypesMap;
+ expandByDefault:boolean;
+ expand:boolean;
+ expanded:boolean;
+ dataTypesService:Sdc.Services.DataTypesService;
+
+ expandAndCollapse():void;
+ getValidationPattern(type:string):RegExp;
+ validateIntRange(value:string):boolean;
+ onValueChange(propertyName:string, type:string):void
+ }
+
+
+ export class DataTypeFieldsStructureDirective implements ng.IDirective {
+
+ constructor(private $templateCache:ng.ITemplateCacheService,
+ private DataTypesService:Sdc.Services.DataTypesService,
+ private PropertyNameValidationPattern: RegExp,
+ private ValidationUtils:Sdc.Utils.ValidationUtils) {
+ }
+
+ scope = {
+ valueObjRef: '=',
+ typeName: '=',
+ parentFormObj: '=',
+ fieldsPrefixName: '=',
+ readOnly: '=',
+ defaultValue: '@',
+ types: '=',
+ expandByDefault: '='
+ };
+
+ restrict = 'E';
+ replace = true;
+ template = ():string => {
+ return this.$templateCache.get('/app/scripts/directives/property-types/data-type-fields-structure/data-type-fields-structure.html');
+ };
+ public types=Utils.Constants.PROPERTY_DATA.TYPES;
+
+ //get data type properties array and return object with the properties and their default value
+ //(for example: get: [{name:"prop1",defaultValue:1 ...},{name:"prop2", defaultValue:"bla bla" ...}]
+ // return: {prop1: 1, prop2: "bla bla"}
+ private getDefaultValue = (dataTypeProperties:Array<Models.DataTypePropertyModel>):any => {
+ let defaultValue = {};
+ for(let i=0; i < dataTypeProperties.length; i++){
+ if(dataTypeProperties[i].type!='string'){
+ if(dataTypeProperties[i].defaultValue){
+ defaultValue[dataTypeProperties[i].name] = JSON.parse(dataTypeProperties[i].defaultValue);
+ }
+ }else{
+ defaultValue[dataTypeProperties[i].name] = dataTypeProperties[i].defaultValue;
+ }
+ }
+ return defaultValue;
+ };
+
+ private initDataOnScope = (scope:any, $attr:any):void =>{
+ scope.dataTypesService = this.DataTypesService;
+ scope.dataTypeProperties = this.DataTypesService.getFirsLevelOfDataTypeProperties(scope.typeName,scope.types);
+ if($attr.defaultValue){
+ scope.currentTypeDefaultValue = JSON.parse($attr.defaultValue);
+ }else{
+ scope.currentTypeDefaultValue = this.getDefaultValue(scope.dataTypeProperties);
+ }
+
+ if(!scope.valueObjRef) {
+ scope.valueObjRef = {};
+ }
+
+ _.forEach(scope.currentTypeDefaultValue, (value, key)=> {
+ if(!scope.valueObjRef[key]){
+ if(typeof scope.currentTypeDefaultValue[key] == 'object'){
+ angular.copy(scope.currentTypeDefaultValue[key], scope.valueObjRef[key]);
+ }else{
+ scope.valueObjRef[key] = scope.currentTypeDefaultValue[key];
+ }
+ }
+ });
+ };
+
+ private rerender = (scope:any):void =>{
+ scope.expanded = false;
+ scope.expand = false;
+ if(scope.expandByDefault){
+ scope.expandAndCollapse();
+ }
+ };
+
+ link = (scope:IDataTypeFieldsStructureScope, element:any, $attr:any) => {
+ scope.propertyNameValidationPattern = this.PropertyNameValidationPattern;
+
+ scope.$watchCollection('[typeName,fieldsPrefixName]', (newData:any):void => {
+ this.rerender(scope);
+ });
+
+
+ scope.expandAndCollapse = ():void => {
+ if(!scope.expanded){
+ this.initDataOnScope(scope,$attr);
+ scope.expanded=true;
+ }
+ scope.expand=!scope.expand;
+ };
+
+ scope.getValidationPattern = (type:string):RegExp => {
+ return this.ValidationUtils.getValidationPattern(type);
+ };
+
+ scope.validateIntRange = (value:string):boolean => {
+ return !value || this.ValidationUtils.validateIntRange(value);
+ };
+
+ scope.onValueChange = (propertyName:string, type:string):void => {
+ scope.valueObjRef[propertyName] = !angular.isUndefined(scope.valueObjRef[propertyName]) ? scope.valueObjRef[propertyName] : scope.currentTypeDefaultValue[propertyName];
+ if(scope.valueObjRef[propertyName] && type != 'string'){
+ scope.valueObjRef[propertyName] = JSON.parse(scope.valueObjRef[propertyName]);
+ }
+ };
+ };
+
+ public static factory = ($templateCache:ng.ITemplateCacheService,
+ DataTypesService:Sdc.Services.DataTypesService,
+ PropertyNameValidationPattern:RegExp,
+ ValidationUtils:Sdc.Utils.ValidationUtils)=> {
+ return new DataTypeFieldsStructureDirective($templateCache,DataTypesService,PropertyNameValidationPattern,ValidationUtils);
+ };
+ }
+
+ DataTypeFieldsStructureDirective.factory.$inject = ['$templateCache','Sdc.Services.DataTypesService','PropertyNameValidationPattern','ValidationUtils'];
+}
diff --git a/catalog-ui/app/scripts/directives/property-types/type-list/type-list-directive.html b/catalog-ui/app/scripts/directives/property-types/type-list/type-list-directive.html
new file mode 100644
index 0000000000..410a24e62b
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/property-types/type-list/type-list-directive.html
@@ -0,0 +1,57 @@
+<div>
+ <div data-ng-if="!isSchemaTypeDataType">
+ <div class="i-sdc-form-item list-new-item" data-ng-class="{error:(parentFormObj['listNewItem'+fieldsPrefixName].$dirty && parentFormObj['listNewItem'+fieldsPrefixName].$invalid)}">
+ <input class="i-sdc-form-input"
+ data-tests-id="listNewItem{{fieldsPrefixName}}"
+ ng-if="!((schemaProperty.simpleType||schemaProperty.type) == 'boolean')"
+ data-ng-disabled="readOnly"
+ data-ng-model="listNewItem.value"
+ type="text"
+ name="listNewItem{{fieldsPrefixName}}"
+ data-ng-pattern="getValidationPattern((schemaProperty.simpleType||schemaProperty.type))"
+ data-ng-model-options="{ debounce: 200 }"
+ placeholder="Type a value and then click ADD"
+ data-ng-maxlength="maxLength"
+ maxlength="{{maxLength}}"
+ sdc-keyboard-events="" key-enter="schemaProperty.type && !parentFormObj['listNewItem'+fieldsPrefixName].$invalid && listNewItem.value && addListItem"
+ autofocus />
+ <select class="i-sdc-form-select"
+ data-tests-id="listNewItem{{fieldsPrefixName}}"
+ ng-if="(schemaProperty.simpleType||schemaProperty.type) == 'boolean'"
+ data-ng-disabled="readOnly"
+ name="listNewItem{{fieldsPrefixName}}"
+ data-ng-model="listNewItem.value">
+ <option value="true">true</option>
+ <option value="false">false</option>
+ </select>
+ <div class="input-error" data-ng-show="parentFormObj['listNewItem'+fieldsPrefixName].$dirty && parentFormObj['listNewItem'+fieldsPrefixName].$invalid">
+ <span ng-show="parentFormObj['listNewItem'+fieldsPrefixName].$error.pattern" translate="PROPERTY_EDIT_PATTERN"></span>
+ <span ng-show="parentFormObj['listNewItem'+fieldsPrefixName].$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '{{maxLength}}' }"></span>
+ </div>
+ </div>
+ <div class="add-btn add-list-item" data-tests-id="add-list-item{{fieldsPrefixName}}"
+ data-ng-class="{'disabled': readOnly || !schemaProperty.type || parentFormObj['listNewItem'+fieldsPrefixName].$invalid || !listNewItem.value}" data-ng-click="addListItem()">Add</div>
+ <div class="list-value-items">
+ <span class="list-value-item" data-ng-repeat="value in valueObjRef track by $index">
+ {{value}}
+ <span class="delete-list-item sprite-new small-x-button" data-ng-click="deleteListItem($index)"></span>
+ </span>
+ </div>
+ </div>
+ <div data-ng-if="isSchemaTypeDataType">
+ <div class="dt-list">
+ <div data-ng-repeat="value in valueObjRef track by $index" class="dt-list-item">
+ <span class="delete-dt-list-item" data-ng-click="deleteListItem($index)"></span>
+ <fields-structure value-obj-ref="valueObjRef[$index]"
+ type-name="schemaProperty.type"
+ parent-form-obj="parentFormObj"
+ fields-prefix-name="fieldsPrefixName+''+$index"
+ read-only="readOnly"
+ types="types"></fields-structure>
+ </div>
+ <div class="add-btn add-list-item" data-tests-id="add-list-item"
+ data-ng-class="{'disabled': readOnly}" data-ng-click="listNewItem.value='{}';addListItem();">Add</div>
+ </div>
+
+ </div>
+</div>
diff --git a/catalog-ui/app/scripts/directives/property-types/type-list/type-list-directive.less b/catalog-ui/app/scripts/directives/property-types/type-list/type-list-directive.less
new file mode 100644
index 0000000000..eb4214e135
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/property-types/type-list/type-list-directive.less
@@ -0,0 +1,85 @@
+.list-new-item{
+ float: left;
+ width: 50%;
+ min-width: 221px;
+ margin-right: 15px;
+ input{
+ min-width: 221px;
+ }
+}
+
+.list-value-items{
+ -webkit-border-radius: 2px;
+ -moz-border-radius: 2px;
+ border-radius: 2px;
+ border: 1px solid @main_color_o;
+ padding-bottom: 10px;
+ min-height: 100px;
+ clear: both;
+ background-color: white;
+ .list-value-item{
+ display: inline-block;
+ background-color: @tlv_color_v;
+ margin: 10px 0 0 10px;
+ padding-left: 8px;
+ .delete-list-item{
+ margin: 0 6px 0 10px;
+ .hand;
+ }
+ }
+}
+
+.add-btn {
+ .f-color.a;
+ .f-type._14_m;
+ .hand;
+
+ &.add-list-item {
+ float: left;
+ margin-top: 5px;
+ width: 44px;
+ }
+
+ &:before {
+ .sprite-new;
+ .plus-icon;
+ margin-right: 5px;
+ content: "";
+
+ }
+ &:hover {
+ .f-color.b;
+ &:before {
+ .sprite-new;
+ .plus-icon-hover;
+ }
+ }
+
+}
+
+.dt-list{
+ display: table-caption;
+ .dt-list-item {
+ border-radius: 3px;
+ background-color: @tlv_color_v;
+ display: inline-block;
+ .delete-dt-list-item{
+ float: right;
+ position: relative;
+ top: 5px;
+ right: 5px;
+ .sprite-new;
+ .delete-icon;
+ &:hover{
+ .delete-icon-hover;
+ }
+ }
+ .data-type-name{
+ margin-right: 16px;
+ }
+ }
+ &>.add-list-item{
+ float:none;
+ }
+}
+
diff --git a/catalog-ui/app/scripts/directives/property-types/type-list/type-list-directive.ts b/catalog-ui/app/scripts/directives/property-types/type-list/type-list-directive.ts
new file mode 100644
index 0000000000..ce5ee1ffa6
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/property-types/type-list/type-list-directive.ts
@@ -0,0 +1,130 @@
+/*-
+ * ============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/15/2016.
+ */
+/// <reference path="../../../references"/>
+module Sdc.Directives {
+ 'use strict';
+ /// import Model = go.Model;
+
+ export interface ITypeListScope extends ng.IScope {
+ parentFormObj:ng.IFormController;
+ schemaProperty:Models.SchemaProperty;
+ isSchemaTypeDataType:boolean;
+ valueObjRef:any;
+ propertyNameValidationPattern: RegExp;
+ fieldsPrefixName:string;
+ readOnly:boolean;
+ listDefaultValue:any;
+ types:Models.DataTypesMap;
+ listNewItem:any;
+ maxLength:number;
+
+ getValidationPattern(type:string):RegExp;
+ validateIntRange(value:string):boolean;
+ addListItem():void;
+ deleteListItem(listItemIndex:number):void
+ }
+
+
+ export class TypeListDirective implements ng.IDirective {
+
+ constructor(private $templateCache:ng.ITemplateCacheService,
+ private DataTypesService:Sdc.Services.DataTypesService,
+ private PropertyNameValidationPattern: RegExp,
+ private ValidationUtils:Sdc.Utils.ValidationUtils) {
+ }
+
+ scope = {
+ valueObjRef: '=',//ref to list object in the parent value object
+ schemaProperty: '=',//get the schema.property object
+ parentFormObj: '=',//ref to parent form (get angular form object)
+ fieldsPrefixName: '=',//prefix for form fields names
+ readOnly: '=',//is form read only
+ defaultValue: '@',//this list default value
+ types: '=',//data types list
+ maxLength: '='
+ };
+
+ restrict = 'E';
+ replace = true;
+ template = ():string => {
+ return this.$templateCache.get('/app/scripts/directives/property-types/type-list/type-list-directive.html');
+ };
+
+ link = (scope:ITypeListScope, element:any, $attr:any) => {
+ scope.propertyNameValidationPattern = this.PropertyNameValidationPattern;
+
+ //reset valueObjRef when schema type is changed
+ scope.$watchCollection('schemaProperty.type', (newData:any):void => {
+ scope.isSchemaTypeDataType = this.DataTypesService.isDataTypeForSchemaType(scope.schemaProperty,scope.types);
+ //insert 1 empty item dt by default
+ if(scope.isSchemaTypeDataType && (!scope.valueObjRef||!scope.valueObjRef.length)){
+ scope.valueObjRef = scope.valueObjRef ||[];
+ scope.valueObjRef.push({});
+ }
+ });
+
+ //when user brows between properties in "edit property form"
+ scope.$watchCollection('fieldsPrefixName', (newData:any):void => {
+ scope.listNewItem={value:''};
+
+ if($attr.defaultValue){
+ scope.listDefaultValue = JSON.parse($attr.defaultValue);
+ }
+ });
+
+ scope.getValidationPattern = (type:string):RegExp => {
+ return this.ValidationUtils.getValidationPattern(type);
+ };
+
+ scope.validateIntRange = (value:string):boolean => {
+ return !value || this.ValidationUtils.validateIntRange(value);
+ };
+
+ scope.addListItem = ():void => {
+ scope.valueObjRef = scope.valueObjRef ||[];
+ let newVal = ((scope.schemaProperty.simpleType||scope.schemaProperty.type)==Utils.Constants.PROPERTY_TYPES.STRING?scope.listNewItem.value:JSON.parse(scope.listNewItem.value));
+ scope.valueObjRef.push(newVal);
+ scope.listNewItem.value = "";
+ };
+
+ scope.deleteListItem = (listItemIndex:number):void => {
+ scope.valueObjRef.splice(listItemIndex,1);
+ if (!scope.valueObjRef.length) {
+ if (scope.listDefaultValue ) {
+ angular.copy(scope.listDefaultValue, scope.valueObjRef);
+ }
+ }
+ };
+ };
+
+ public static factory = ($templateCache:ng.ITemplateCacheService,
+ DataTypesService:Sdc.Services.DataTypesService,
+ PropertyNameValidationPattern:RegExp,
+ ValidationUtils:Sdc.Utils.ValidationUtils)=> {
+ return new TypeListDirective($templateCache,DataTypesService,PropertyNameValidationPattern,ValidationUtils);
+ };
+ }
+
+ TypeListDirective.factory.$inject = ['$templateCache','Sdc.Services.DataTypesService','PropertyNameValidationPattern','ValidationUtils'];
+}
+
diff --git a/catalog-ui/app/scripts/directives/property-types/type-map/type-map-directive.html b/catalog-ui/app/scripts/directives/property-types/type-map/type-map-directive.html
new file mode 100644
index 0000000000..ed82b840dc
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/property-types/type-map/type-map-directive.html
@@ -0,0 +1,70 @@
+<div>
+ <div data-ng-repeat="i in getNumber(mapKeys.length) track by $index" class="map-item" data-ng-class="{'primitive-value-map':!isSchemaTypeDataType}">
+ <div class="i-sdc-form-item map-item-field" data-ng-class="{error:(parentFormObj['mapKey'+fieldsPrefixName+$index].$dirty && parentFormObj['mapKey'+fieldsPrefixName+$index].$invalid)}">
+ <label class="i-sdc-form-label required">Key</label>
+ <input class="i-sdc-form-input"
+ data-tests-id="mapKey{{fieldsPrefixName}}{{$index}}"
+ data-ng-model="mapKeys[$index]"
+ type="text"
+ name="mapKey{{fieldsPrefixName}}{{$index}}"
+ data-ng-pattern="propertyNameValidationPattern"
+ data-ng-model-options="{ debounce: 200 }"
+ data-ng-change="changeKeyOfMap(mapKeys[$index], $index,'mapKey'+fieldsPrefixName+$index);$event.stopPropagation();"
+ data-ng-disabled="readOnly"
+ data-required
+ autofocus/>
+ <div class="input-error" data-ng-show="parentFormObj['mapKey'+fieldsPrefixName+$index].$dirty && parentFormObj['mapKey'+fieldsPrefixName+$index].$invalid">
+ <span ng-show="parentFormObj['mapKey'+fieldsPrefixName+$index].$error.keyExist" translate="PROPERTY_EDIT_MAP_UNIQUE_KEYS"></span>
+ <span ng-show="parentFormObj['mapKey'+fieldsPrefixName+$index].$error.required" translate="VALIDATION_ERROR_REQUIRED" translate-values="{'field': 'Key' }"></span>
+ </div>
+ </div>
+ <div data-ng-if="!isSchemaTypeDataType" class="i-sdc-form-item map-item-field" data-ng-class="{error:(parentFormObj['mapValue'+fieldsPrefixName+$index].$dirty && parentFormObj['mapValue'+fieldsPrefixName+$index].$invalid)}">
+ <label class="i-sdc-form-label required">Value</label>
+ <input class="i-sdc-form-input"
+ ng-if="!((schemaProperty.simpleType||schemaProperty.type) == 'boolean')"
+ data-ng-disabled="readOnly"
+ data-ng-model="valueObjRef[mapKeys[$index]]"
+ type="text"
+ name="mapValue{{fieldsPrefixName}}{{$index}}"
+ data-tests-id="mapValue{{fieldsPrefixName}}{{$index}}"
+ data-ng-pattern="getValidationPattern((schemaProperty.simpleType||schemaProperty.type))"
+ data-ng-change="!parentFormObj['mapValue'+fieldsPrefixName+$index].$error.pattern && parseToCorrectType(valueObjRef, key, (schemaProperty.simpleType||schemaProperty.type))"
+ data-ng-model-options="{ debounce: 200 }"
+ data-ng-maxlength="maxLength"
+ maxlength="{{maxLength}}"
+ data-required
+ autofocus />
+ <select class="i-sdc-form-select"
+ data-tests-id="mapValue{{fieldsPrefixName}}{{$index}}"
+ ng-if="(schemaProperty.simpleType||schemaProperty.type) == 'boolean'"
+ data-ng-disabled="readOnly"
+ name="mapValue{{fieldsPrefixName}}{{$index}}"
+ data-ng-model="valueObjRef[mapKeys[$index]]"
+ data-required>
+ <option value="true">true</option>
+ <option value="false">false</option>
+ </select>
+ <div class="input-error" data-ng-show="parentFormObj['mapValue'+fieldsPrefixName+$index].$dirty && parentFormObj['mapValue'+fieldsPrefixName+$index].$invalid">
+ <span ng-show="parentFormObj['mapValue'+fieldsPrefixName+$index].$error.required" translate="VALIDATION_ERROR_REQUIRED" translate-values="{'field': 'Value' }"></span>
+ <span ng-show="parentFormObj['mapValue'+fieldsPrefixName+$index].$error.pattern" translate="PROPERTY_EDIT_PATTERN"></span>
+ <span ng-show="parentFormObj['mapValue'+fieldsPrefixName+$index].$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '{{maxLength}}' }"></span>
+ </div>
+ </div>
+ <div data-ng-if="isSchemaTypeDataType" class="i-sdc-form-item map-item-field">
+ <label class="i-sdc-form-label">Value</label>
+ <fields-structure value-obj-ref="valueObjRef[mapKeys[$index]]"
+ type-name="schemaProperty.type"
+ parent-form-obj="parentFormObj"
+ fields-prefix-name="'mapValue'+fieldsPrefixName+''+$index"
+ read-only="readOnly"
+ types="types"
+ ></fields-structure>
+ </div>
+ <span ng-click="deleteMapItem($index)" class="delete-map-item" data-tests-id="delete-map-item{{fieldsPrefixName}}{{$index}}" data-ng-class="{'disabled': readOnly}"></span>
+ </div>
+ <div class="add-map-item" data-ng-class="{'schema-data-type':isSchemaTypeDataType}">
+ <div class="add-btn" data-tests-id="add-map-item"
+ data-ng-class="{'disabled': readOnly || !schemaProperty.type || mapKeys.indexOf('')>-1}" data-ng-click="addMapItemFields()">Add</div>
+ </div>
+</div>
+
diff --git a/catalog-ui/app/scripts/directives/property-types/type-map/type-map-directive.less b/catalog-ui/app/scripts/directives/property-types/type-map/type-map-directive.less
new file mode 100644
index 0000000000..2480b626f2
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/property-types/type-map/type-map-directive.less
@@ -0,0 +1,83 @@
+.add-map-item{
+ &:nth-child(odd){
+ float: right;
+ }
+ &:nth-child(1){
+ float: none;
+ .add-btn{
+ float: none;
+ }
+ }
+ width: 400px;
+ .add-btn{
+ width: 44px;
+ float: right;
+ }
+ &.schema-data-type{
+ float:none;
+ .add-btn{
+ float: none;
+ }
+ }
+}
+
+.add-btn {
+ .f-color.a;
+ .f-type._14_m;
+ .hand;
+
+ &:before {
+ .sprite-new;
+ .plus-icon;
+ margin-right: 5px;
+ content: "";
+
+ }
+ &:hover {
+ .f-color.b;
+ &:before {
+ .sprite-new;
+ .plus-icon-hover;
+ }
+ }
+
+}
+
+.map-item{
+ min-width: 389px;
+ min-height: 65px;
+ background-color: @tlv_color_v;
+ border-radius: 3px;
+ margin-bottom: 8px;
+ float: left;
+ display: flex;
+ &:nth-child(even).primitive-value-map{
+ float: right;
+ }
+ .delete-map-item {
+ float: right;
+ position: relative;
+ top: 5px;
+ right: 5px;
+ .sprite-new;
+ .delete-icon;
+ &:hover{
+ .delete-icon-hover;
+ }
+ }
+ .map-item-field {
+ margin: 7px 12px !important;
+ float: left;
+ min-width: 170px;
+ min-height: 50px;
+ select{
+ width:171px;
+ }
+ input[type="text"]{
+ width: 170px;
+ }
+ &>.data-type-fields-structure{
+ padding: 0;
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/directives/property-types/type-map/type-map-directive.ts b/catalog-ui/app/scripts/directives/property-types/type-map/type-map-directive.ts
new file mode 100644
index 0000000000..d94ccf3886
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/property-types/type-map/type-map-directive.ts
@@ -0,0 +1,157 @@
+/*-
+ * ============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/15/2016.
+ */
+/// <reference path="../../../references"/>
+module Sdc.Directives {
+ 'use strict';
+
+ export interface ITypeMapScope extends ng.IScope {
+ parentFormObj:ng.IFormController;
+ schemaProperty:Models.SchemaProperty;
+ isSchemaTypeDataType:boolean;
+ valueObjRef:any;
+ mapKeys:Array<string>;//array of map keys
+ propertyNameValidationPattern: RegExp;
+ fieldsPrefixName:string;
+ readOnly:boolean;
+ mapDefaultValue:any;
+ types:Models.DataTypesMap;
+ maxLength:number;
+
+ getValidationPattern(type:string):RegExp;
+ validateIntRange(value:string):boolean;
+ changeKeyOfMap(newKey:string, index:number, fieldName:string):void;
+ deleteMapItem(index:number):void;
+ addMapItemFields():void;
+ parseToCorrectType(objectOfValues:any, locationInObj:string, type:string):void;
+ getNumber(num:number):Array<any>;
+ }
+
+
+ export class TypeMapDirective implements ng.IDirective {
+
+ constructor(private $templateCache:ng.ITemplateCacheService,
+ private DataTypesService:Sdc.Services.DataTypesService,
+ private PropertyNameValidationPattern: RegExp,
+ private ValidationUtils:Sdc.Utils.ValidationUtils,
+ private $timeout: ng.ITimeoutService) {
+ }
+
+ scope = {
+ valueObjRef: '=',//ref to map object in the parent value object
+ schemaProperty: '=',//get the schema.property object
+ parentFormObj: '=',//ref to parent form (get angular form object)
+ fieldsPrefixName: '=',//prefix for form fields names
+ readOnly: '=',//is form read only
+ defaultValue: '@',//this map default value
+ types: '=',//data types list
+ maxLength: '='
+ };
+
+ restrict = 'E';
+ replace = true;
+ template = ():string => {
+ return this.$templateCache.get('/app/scripts/directives/property-types/type-map/type-map-directive.html');
+ };
+
+ link = (scope:ITypeMapScope, element:any, $attr:any) => {
+ scope.propertyNameValidationPattern = this.PropertyNameValidationPattern;
+
+ //reset valueObjRef and mapKeys when schema type is changed
+ scope.$watchCollection('schemaProperty.type', (newData:any):void => {
+ scope.isSchemaTypeDataType = this.DataTypesService.isDataTypeForSchemaType(scope.schemaProperty,scope.types);
+ if(scope.valueObjRef){
+ scope.mapKeys = Object.keys(scope.valueObjRef);
+ }
+ });
+
+ //when user brows between properties in "edit property form"
+ scope.$watchCollection('fieldsPrefixName', (newData:any):void => {
+ if(!scope.valueObjRef) {
+ scope.valueObjRef={};
+ }
+ scope.mapKeys = Object.keys(scope.valueObjRef);
+
+ if($attr.defaultValue){
+ scope.mapDefaultValue = JSON.parse($attr.defaultValue);
+ }
+ });
+
+ //return dummy array in order to prevent rendering map-keys ng-repeat again when a map key is changed
+ scope.getNumber = (num:number):Array<any> => {
+ return new Array(num);
+ };
+
+ scope.getValidationPattern = (type:string):RegExp => {
+ return this.ValidationUtils.getValidationPattern(type);
+ };
+
+ scope.validateIntRange = (value:string):boolean => {
+ return !value || this.ValidationUtils.validateIntRange(value);
+ };
+
+ scope.changeKeyOfMap = (newKey:string, index:number, fieldName:string) : void => {
+ let oldKey = Object.keys(scope.valueObjRef)[index];
+ if(Object.keys(scope.valueObjRef).indexOf(newKey)>-1){
+ scope.parentFormObj[fieldName].$setValidity('keyExist', false);
+ }else{
+ scope.parentFormObj[fieldName].$setValidity('keyExist', true);
+ if(!scope.parentFormObj[fieldName].$invalid){
+ angular.copy(JSON.parse(JSON.stringify(scope.valueObjRef).replace('"'+oldKey+'":', '"'+newKey+'":')),scope.valueObjRef);//update key
+ }
+ }
+ };
+
+ scope.deleteMapItem=(index:number):void=>{
+ delete scope.valueObjRef[scope.mapKeys[index]];
+ scope.mapKeys.splice(index,1);
+ if (!scope.mapKeys.length) {//only when user removes all pairs of key-value fields - put the default
+ if ( scope.mapDefaultValue ) {
+ angular.copy(scope.mapDefaultValue, scope.valueObjRef);
+ scope.mapKeys = Object.keys(scope.valueObjRef);
+ }
+ }
+ };
+
+ scope.addMapItemFields = ():void => {
+ scope.valueObjRef['']= null;
+ scope.mapKeys = Object.keys(scope.valueObjRef);
+ };
+
+ scope.parseToCorrectType = (objectOfValues:any, locationInObj:string, type:string):void => {
+ if(objectOfValues[locationInObj] && type != Utils.Constants.PROPERTY_TYPES.STRING){
+ objectOfValues[locationInObj] = JSON.parse(objectOfValues[locationInObj]);
+ }
+ }
+ };
+
+ public static factory = ($templateCache:ng.ITemplateCacheService,
+ DataTypesService:Sdc.Services.DataTypesService,
+ PropertyNameValidationPattern:RegExp,
+ ValidationUtils:Sdc.Utils.ValidationUtils,
+ $timeout: ng.ITimeoutService)=> {
+ return new TypeMapDirective($templateCache,DataTypesService,PropertyNameValidationPattern,ValidationUtils,$timeout);
+ };
+ }
+
+ TypeMapDirective.factory.$inject = ['$templateCache','Sdc.Services.DataTypesService','PropertyNameValidationPattern','ValidationUtils','$timeout'];
+}
diff --git a/catalog-ui/app/scripts/directives/punch-out/punch-out.ts b/catalog-ui/app/scripts/directives/punch-out/punch-out.ts
new file mode 100644
index 0000000000..f00b7971a9
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/punch-out/punch-out.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=========================================================
+ */
+/// <reference path="../../references"/>
+module Sdc.Directives {
+ 'use strict';
+ export interface IPunchOutScope extends ng.IScope {
+ name: string;
+ data: any;
+ user: Models.IUserProperties;
+ onEvent: Function;
+ }
+
+ export class PunchOutDirective implements ng.IDirective {
+
+ constructor(
+ private sdcConfig: Sdc.Models.IAppConfigurtaion) {}
+
+ scope = {
+ name: '=',
+ data: '=',
+ user: '=',
+ onEvent: '&'
+ };
+
+ replace = false;
+ restrict = 'E';
+
+ link = (scope: IPunchOutScope, element: ng.IAugmentedJQuery):void => {
+ // global registry object
+ let PunchOutRegistry = window['PunchOutRegistry'];
+
+ let render = ():void => {
+ let cookieConfig = this.sdcConfig.cookie;
+ let props = {
+ name: scope.name,
+ options: {
+ data: scope.data,
+ apiRoot: this.sdcConfig.api.root,
+ apiHeaders: {
+ userId: {
+ name: cookieConfig.userIdSuffix,
+ value: scope.user.userId
+ },
+ userFirstName: {
+ name: cookieConfig.userFirstName,
+ value: scope.user.firstName
+ },
+ userLastName: {
+ name: cookieConfig.userLastName,
+ value: scope.user.lastName
+ },
+ userEmail: {
+ name: cookieConfig.userEmail,
+ value: scope.user.email
+ }
+ }
+ },
+ onEvent: (...args) => {
+ scope.$apply(() => {
+ scope.onEvent().apply(null, args);
+ });
+ }
+ };
+ PunchOutRegistry.render(props, element[0]);
+ };
+
+ let unmount = ():void => {
+ PunchOutRegistry.unmount(element[0]);
+ };
+
+ scope.$watch('data', render);
+ element.on('$destroy', unmount);
+ };
+
+ public static factory = (sdcConfig: Sdc.Models.IAppConfigurtaion) => {
+ return new PunchOutDirective(sdcConfig);
+ };
+
+ }
+
+ PunchOutDirective.factory.$inject = ['sdcConfig'];
+}
diff --git a/catalog-ui/app/scripts/directives/sdc-tabs/sdc-single-tab/sdc-single-tab-directive.ts b/catalog-ui/app/scripts/directives/sdc-tabs/sdc-single-tab/sdc-single-tab-directive.ts
new file mode 100644
index 0000000000..26390a7501
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/sdc-tabs/sdc-single-tab/sdc-single-tab-directive.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.Directives {
+ 'use strict';
+
+ export class SdcSingleTabDirective implements ng.IDirective {
+
+ constructor(private $compile:ng.ICompileService, private $parse:ng.IParseService) {
+ }
+ restrict = 'E';
+
+ link = (scope, elem:any, attrs:any, ctrl:any) => {
+ if(!elem.attr('inner-sdc-single-tab')) {
+ let name = this.$parse(elem.attr('ctrl'))(scope);
+ elem = elem.removeAttr('ctrl');
+ elem.attr('inner-sdc-single-tab', name);
+ this.$compile(elem)(scope);
+ }
+ };
+
+ public static factory = ($compile:ng.ICompileService, $parse:ng.IParseService)=> {
+ return new SdcSingleTabDirective($compile, $parse);
+ };
+ }
+
+ export class InnerSdcSingleTabDirective implements ng.IDirective {
+
+ constructor(private $templateCache:ng.ITemplateCacheService) {
+ }
+
+ scope = {
+ singleTab: "=",
+ isViewOnly: "="
+ };
+
+ replace = true;
+ restrict = 'A';
+ controller = '@';
+ template = '<div ng-include src="singleTab.templateUrl"></div>';
+
+ public static factory = ($templateCache:ng.ITemplateCacheService)=> {
+ return new InnerSdcSingleTabDirective($templateCache);
+ };
+ }
+
+ SdcSingleTabDirective.factory.$inject = ['$compile', '$parse'];
+ InnerSdcSingleTabDirective.factory.$inject = ['$templateCache'];
+
+}
diff --git a/catalog-ui/app/scripts/directives/sdc-tabs/sdc-single-tab/sdc-single-tab.less b/catalog-ui/app/scripts/directives/sdc-tabs/sdc-single-tab/sdc-single-tab.less
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/sdc-tabs/sdc-single-tab/sdc-single-tab.less
@@ -0,0 +1 @@
+
diff --git a/catalog-ui/app/scripts/directives/sdc-tabs/sdc-tabs-directive-view.html b/catalog-ui/app/scripts/directives/sdc-tabs/sdc-tabs-directive-view.html
new file mode 100644
index 0000000000..d51d221922
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/sdc-tabs/sdc-tabs-directive-view.html
@@ -0,0 +1,17 @@
+<div class="sdc-tabs-body">
+ <div class="sdc-tabs" ng-class="{'not-active': !isActive}">
+ <div class="sdc-tab-arrow" ng-click="isActive = !isActive">
+ <span class="sprite-new close-open-left-arrow" ng-class="{'close-open-right-arrow': !isActive}"></span>
+ </div>
+ <div ng-repeat="tab in tabs track by $index">
+ <div class="sdc-tab" ng-click="onTabSelected(tab)" data-tests-id="{{tab.name}}-tab" ng-mouseenter="hover = true"
+ ng-mouseleave="hover = false"
+ ng-class="{'last-tab':$last, 'first-tab': $first, 'selected' :tab.name === selectedTab.name }">
+ <div class="sdc-tab-icon sprite-new {{tab.icon}}" ng-class="{'selected' :tab.name === selectedTab.name, 'hover': hover}"></div>
+ </div>
+ </div>
+ </div>
+ <div class="sdc-single-tab-content" ng-if="isActive">
+ <sdc-single-tab class="sdc-single-tab-content-body" ng-if="selectedTab" ctrl="selectedTab.controller" data-dests-id="selected-tab" single-tab="selectedTab" is-view-only="isViewOnly"></sdc-single-tab>
+ </div>
+</div>
diff --git a/catalog-ui/app/scripts/directives/sdc-tabs/sdc-tabs-directive.ts b/catalog-ui/app/scripts/directives/sdc-tabs/sdc-tabs-directive.ts
new file mode 100644
index 0000000000..91d1744ae5
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/sdc-tabs/sdc-tabs-directive.ts
@@ -0,0 +1,69 @@
+/*-
+ * ============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.
+ */
+/// <reference path="../../references"/>
+module Sdc.Directives {
+ 'use strict';
+
+ export interface ISdcTabsDirectiveScope extends ng.IScope {
+ tabs:Array<Models.Tab>;
+ selectedTab: Models.Tab;
+ isActive: boolean;
+ onTabSelected(selectedTab: Models.Tab);
+ }
+
+ export class SdcTabsDirective implements ng.IDirective {
+
+ constructor(private $templateCache:ng.ITemplateCacheService) {
+ }
+
+ scope = {
+ tabs: "=",
+ selectedTab: "=?",
+ isViewOnly: "="
+ };
+
+ replace = true;
+ restrict = 'E';
+ template = ():string => {
+ return this.$templateCache.get('/app/scripts/directives/sdc-tabs/sdc-tabs-directive-view.html');
+ };
+
+ link = (scope:ISdcTabsDirectiveScope) => {
+ scope.isActive = true;
+
+ if(!scope.selectedTab){
+ scope.selectedTab = scope.tabs[0];
+ }
+
+ scope.onTabSelected = (selectedTab: Models.Tab) => {
+ scope.selectedTab = selectedTab;
+ }
+ };
+
+ public static factory = ($templateCache:ng.ITemplateCacheService)=> {
+ return new SdcTabsDirective($templateCache);
+ };
+ }
+
+ SdcTabsDirective.factory.$inject = ['$templateCache'];
+}
diff --git a/catalog-ui/app/scripts/directives/sdc-tabs/sdc-tabs.less b/catalog-ui/app/scripts/directives/sdc-tabs/sdc-tabs.less
new file mode 100644
index 0000000000..ad390010ed
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/sdc-tabs/sdc-tabs.less
@@ -0,0 +1,68 @@
+.sdc-tabs-body {
+ height: 100%;
+ width: 330px;
+ position: absolute;
+ .sdc-tabs {
+ display: inline-block;
+ width: 40px;
+ vertical-align: top;
+ position: relative;
+ z-index: 99;
+ right: 332px;
+ .sdc-tab-arrow {
+ cursor: pointer;
+ width: 40px;
+ height: 20px;
+ background-color: @tlv_color_u;
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.17);
+ text-align: center;
+ padding: 1px 4px 0px 0px;
+
+ &:hover {
+ background-color: @main_color_o;
+ }
+
+ }
+ .sdc-tab {
+ cursor: pointer;
+ width: 40px;
+ height: 43px;
+ background-color: @tlv_color_u;
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.17);
+ text-align: center;
+
+ .sdc-tab-icon {
+ margin-top: 12px;
+ }
+ }
+ .selected {
+ background-color: @tlv_color_t;
+ }
+
+ .last-tab {
+ border-bottom-left-radius: 12px;
+ }
+ }
+
+ .not-active {
+ // position: absolute;
+ right: 41px;
+ }
+
+ .sdc-single-tab-content {
+ padding: 15px 0px 0px 0px;
+ width: 290px;
+ background-color: @tlv_color_t;
+ height: 100%;
+ display: inline-block;
+ bottom: 0;
+ top: 0;
+ position: absolute;
+ box-shadow: 0.3px 1px 3px rgba(24, 24, 25, 0.42);
+ right: 331px;
+ .sdc-single-tab-content-body {
+ height: 100%;
+ display: flex;
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/directives/structure-tree/structure-tree-directive.html b/catalog-ui/app/scripts/directives/structure-tree/structure-tree-directive.html
new file mode 100644
index 0000000000..7d8a883b33
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/structure-tree/structure-tree-directive.html
@@ -0,0 +1,54 @@
+<div class="structure-tree">
+ <div class="component-container">
+ <div class="{{component.iconSprite}} small" ng-class="structureTree.serviceRoot.icon"></div>
+ <div class="component-container-text" tooltip-class="tooltip-custom break-word-tooltip" tooltips tooltip-content="&#8203;{{structureTree.serviceRoot.name}}"> {{structureTree.serviceRoot.name}}</div>
+ </div>
+ <ul>
+ <li data-ng-repeat="firstLevelResourcesInstances in structureTree.serviceRoot.resourceInstancesList">
+ <div class="component-container">
+ <div class="small {{firstLevelResourcesInstances.icon}}" ng-class="{'sprite-resource-icons': !component.isProduct(), 'sprite-services-icons': component.isProduct()}">
+ <div data-ng-class="{'non-certified':!firstLevelResourcesInstances.certified}"
+ tooltips tooltip-side="top" tooltip-content="Not certified">
+ </div>
+ </div>
+ <div class="component-container-text" tooltips tooltip-content="{{firstLevelResourcesInstances.name}}" > {{firstLevelResourcesInstances.name}} </div>
+ </div>
+ <ul>
+ <li data-ng-repeat="secondLevelResourcesInstances in firstLevelResourcesInstances.resourceInstancesList">
+ <div class="component-container">
+ <div class="sprite-resource-icons small" ng-class="secondLevelResourcesInstances.icon">
+ <div data-ng-class="{'non-certified':!secondLevelResourcesInstances.certified}"
+ tooltips tooltip-side="top" tooltip-content="Not certified">
+ </div>
+ </div>
+ <div class="component-container-text" tooltips tooltip-content="{{secondLevelResourcesInstances.name}}"> {{secondLevelResourcesInstances.name}} </div>
+ </div>
+ <ul>
+ <li data-ng-repeat="thirdLevelResourcesInstances in secondLevelResourcesInstances.resourceInstancesList">
+ <div class="component-container">
+ <div class="sprite-resource-icons small" ng-class="thirdLevelResourcesInstances.icon">
+ <div data-ng-class="{'non-certified':!thirdLevelResourcesInstances.certified}"
+ tooltips tooltip-side="top" tooltip-content="Not certified">
+ </div>
+ </div>
+ <div class="component-container-text" tooltips tooltip-content="{{thirdLevelResourcesInstances.name}}" > {{thirdLevelResourcesInstances.name}} </div>
+ </div>
+ <ul>
+ <li data-ng-repeat="forthLevelResourcesInstances in thirdLevelResourcesInstances.resourceInstancesList">
+ <div class="component-container">
+ <div class="sprite-resource-icons small" ng-class="forthLevelResourcesInstances.icon">
+ <div data-ng-class="{'non-certified':!forthLevelResourcesInstances.certified}"
+ tooltips tooltip-side="top" tooltip-content="Not certified">
+ </div>
+ </div>
+ <div class="component-container-text" tooltips tooltip-content="{{forthLevelResourcesInstances.name}}"> {{forthLevelResourcesInstances.name}} </div>
+ </div>
+ </li>
+ </ul>
+ </li>
+ </ul>
+ </li>
+ </ul>
+ </li>
+ </ul>
+</div>
diff --git a/catalog-ui/app/scripts/directives/structure-tree/structure-tree-directive.less b/catalog-ui/app/scripts/directives/structure-tree/structure-tree-directive.less
new file mode 100644
index 0000000000..094c3f70ba
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/structure-tree/structure-tree-directive.less
@@ -0,0 +1,68 @@
+.structure-tree{
+ padding: 9px 0px 10px 30px;
+ position: relative;
+ ul{
+ position: relative;
+ list-style: none;
+ padding-left:25px;
+ ::before{
+ content: "";
+ position: absolute;
+ left: -27px;
+ }
+ ::after{
+ content: "";
+ position: absolute;
+ left: -27px;
+ }
+ li{
+ position: relative;
+ &::before{
+ border-top: 1px solid #666666;
+ top: 20px;
+ width: 10px;
+ height: 0;
+ }
+ &::after{
+ border-left: 1px solid #666666;
+ height: 100%;
+ width: 0px;
+ top: -2px;
+ }
+ &:last-child::after{
+ height: 23px
+ }
+ }
+ }
+ .component-container{
+ display: inline-block;
+ margin: 6px 0px 0px -16px;
+ }
+ .component-container-icon{
+ display: inline-block;
+ }
+ .component-container-text{
+ padding-left: 8px;
+ float: right;
+
+ text-overflow: ellipsis;
+ max-width:120px;
+ display: inline-block;
+ white-space: nowrap;
+ font-size: 13px;
+ color: #666666;;
+ overflow: hidden;
+ line-height: 28px;
+ float: none;
+ }
+
+ .non-certified{
+ position: relative;
+ left: 18px;
+ bottom: 8px;
+ .sprite;
+ .s-sdc-state-non-certified;
+ display:block;
+ }
+
+}
diff --git a/catalog-ui/app/scripts/directives/structure-tree/structure-tree-directive.ts b/catalog-ui/app/scripts/directives/structure-tree/structure-tree-directive.ts
new file mode 100644
index 0000000000..1edce6f36e
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/structure-tree/structure-tree-directive.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.Directives {
+ 'use strict';
+
+
+ export interface IStructureTreeScope extends ng.IScope {
+
+ component: Models.Components.Component;
+ structureTree: StructureTree;
+ }
+
+ class StructureTree {
+
+ serviceRoot:ResourceInstanceNode;
+
+ constructor(private uniqueId:string, private resourceInstanceName:string, private resourceInstanceIcon:string, private certified:boolean) {
+ this.serviceRoot = new ResourceInstanceNode(uniqueId, resourceInstanceName, resourceInstanceIcon, certified);
+ }
+
+ }
+
+ class ResourceInstanceNode {
+ id:string;
+ icon:string;
+ name:string;
+ resourceInstancesList:Array<ResourceInstanceNode>;
+ isAlreadyInTree:boolean;
+ certified:boolean;
+
+
+ constructor(private uniqueId:string, private resourceInstanceName:string, private resourceInstanceIcon:string, certified:boolean) {
+ this.id = uniqueId;
+ this.name = resourceInstanceName;
+ this.icon = resourceInstanceIcon;
+ this.resourceInstancesList = [];
+ this.isAlreadyInTree = false;
+ this.certified = certified;
+ }
+ }
+
+ export class StructureTreeDirective implements ng.IDirective {
+
+
+ constructor(private $templateCache:ng.ITemplateCacheService) {
+ }
+
+ scope = {
+ component: '=',
+ };
+ restrict = 'E';
+ template = ():string => {
+ return this.$templateCache.get('/app/scripts/directives/structure-tree/structure-tree-directive.html');
+ };
+
+ link = (scope:IStructureTreeScope, $elem:any) => {
+
+ let RESOURCE_INSTANCE_LIST:string = "resourceInstancesChildesList";
+ let resourceInstanceMap:Utils.Dictionary<string, ResourceInstanceNode>;
+ let relations:Array<Models.RelationshipModel>;
+ //************* Start Building Tree Functions *******************//
+
+ //remove unnecessary instances
+ let initResourceInstanceMap = ():void => {
+
+ resourceInstanceMap = new Utils.Dictionary<string, ResourceInstanceNode>();
+
+ _.forEach(scope.component.componentInstances, (resourceInstance:Models.ComponentsInstances.ComponentInstance)=> {
+ if (_.some(Object.keys(resourceInstance.capabilities), (key:string)=> {
+ return 'tosca.capabilities.container' == key.toLowerCase();
+ }) || _.some(Object.keys(resourceInstance.requirements),(key:string)=> {
+ return 'tosca.capabilities.container' == key.toLowerCase();
+ })) {
+
+ let isCertified = 0 === (parseFloat(resourceInstance.componentVersion) % 1);
+ let node:ResourceInstanceNode = new ResourceInstanceNode(resourceInstance.uniqueId,
+ resourceInstance.name,
+ resourceInstance.icon,
+ isCertified);
+ resourceInstanceMap.setValue(resourceInstance.uniqueId, node);
+ }
+ });
+ };
+
+ //remove unnecessary relations
+ let initRelations = ():void => {
+ relations = _.filter(scope.component.componentInstancesRelations, (relation:Models.RelationshipModel)=> {
+ return resourceInstanceMap.containsKey(relation.fromNode) && resourceInstanceMap.containsKey(relation.toNode);
+ });
+ };
+
+ let buildTree = ():void => {
+ if (scope.component) {
+ scope.structureTree = new StructureTree(scope.component.uniqueId, scope.component.name, scope.component.icon, 'CERTIFIED' === scope.component.lifecycleState);
+ initResourceInstanceMap();
+ initRelations();
+
+ let parentNodesList = _.groupBy(relations, (node:any)=> {
+ return node.fromNode;
+ });
+
+ for (let parent in parentNodesList) {
+ _.forEach(parentNodesList[parent], (childNode)=> {
+ parentNodesList[parent][RESOURCE_INSTANCE_LIST] = [];
+ parentNodesList[parent][RESOURCE_INSTANCE_LIST].push(mergeAllSubtrees(childNode, parentNodesList));
+ });
+ }
+
+ //add the resourceInstanceList for the service root node
+ for (let parent in parentNodesList) {
+ let resourceInstanceNode:ResourceInstanceNode = resourceInstanceMap.getValue(parent);
+ resourceInstanceNode.resourceInstancesList = parentNodesList[parent];
+ resourceInstanceNode.resourceInstancesList = parentNodesList[parent][RESOURCE_INSTANCE_LIST];
+ resourceInstanceNode.isAlreadyInTree = true;
+ scope.structureTree.serviceRoot.resourceInstancesList.push(resourceInstanceNode);
+ }
+
+ // Add all node that have no connection to the rootNode
+ resourceInstanceMap.forEach((key:string, value:ResourceInstanceNode) => {
+ if (!value.isAlreadyInTree) {
+ scope.structureTree.serviceRoot.resourceInstancesList.push(value);
+ }
+ });
+ }
+ };
+
+ //this recursion is merging all the subtrees
+ let mergeAllSubtrees = (connectionData:any, parentNodesList:any):ResourceInstanceNode => {
+ let resourceInstanceNode:ResourceInstanceNode = resourceInstanceMap.getValue(connectionData.toNode);
+ resourceInstanceNode.isAlreadyInTree = true;
+ if (parentNodesList[resourceInstanceNode.id]) {
+ if (parentNodesList[resourceInstanceNode.id][RESOURCE_INSTANCE_LIST]) {
+ resourceInstanceNode.resourceInstancesList = parentNodesList[resourceInstanceNode.id][RESOURCE_INSTANCE_LIST];
+ }
+ else {
+ _.forEach(parentNodesList[resourceInstanceNode.id], (children)=> {
+ resourceInstanceNode.resourceInstancesList.push(mergeAllSubtrees(children, parentNodesList));
+ });
+ }
+ delete parentNodesList[resourceInstanceNode.id];
+ }
+ return resourceInstanceNode;
+ };
+ //************* End Building Tree Functions *******************//
+
+ //************* Start Watchers *******************//
+ scope.$watch('component.name', ():void => {
+ if (scope.structureTree)
+ scope.structureTree.serviceRoot.name = scope.component.name;
+ });
+
+ scope.$watch('component.icon', ():void => {
+ if (scope.structureTree)
+ scope.structureTree.serviceRoot.icon = scope.component.icon;
+ });
+
+ scope.$watchCollection('component.componentInstancesRelations', ():void => {
+ buildTree();
+ });
+
+ scope.$watchCollection('component.componentInstances', ():void => {
+ buildTree();
+ });
+
+ //************* End Watchers *******************//
+
+ buildTree();
+
+ };
+
+
+ public static factory = ($templateCache:ng.ITemplateCacheService) => {
+ return new StructureTreeDirective($templateCache);
+ };
+ }
+
+ StructureTreeDirective.factory.$inject = ['$templateCache'];
+
+}
diff --git a/catalog-ui/app/scripts/directives/tag/tag-directive.html b/catalog-ui/app/scripts/directives/tag/tag-directive.html
new file mode 100644
index 0000000000..28c22a7978
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/tag/tag-directive.html
@@ -0,0 +1,10 @@
+<div class="sdc-tag">
+ <div class="tag" data-tests-id="i-sdc-tag-text" sdc-smart-tooltip data-ng-bind="tagData.tag"></div>
+ <div class="category" data-ng-hide="hideTooltip===true">
+ <span class="relation-categoty-icon" data-tooltips data-tooltip-side="bottom" data-tooltip="'<span class='tag-tooltip-wrap'>{{tagData.tooltip}}</span>'" data-tooltip-enable="false"></span>
+ </div>
+
+ <div class="delete" data-ng-if="!hideDelete && !sdcDisable" data-ng-click="delete()" data-tests-id="i-sdc-tag-delete">
+ <span class="delete-icon"></span>
+ </div>
+</div>
diff --git a/catalog-ui/app/scripts/directives/tag/tag-directive.less b/catalog-ui/app/scripts/directives/tag/tag-directive.less
new file mode 100644
index 0000000000..f72e366ac6
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/tag/tag-directive.less
@@ -0,0 +1,51 @@
+.sdc-tag{
+
+ background-color:#F2F2F2 ;
+ .border-radius(4px);
+ min-width:150px;
+ height:30px;
+ display: flex;
+ align-items: center;
+ padding: 0 10px;
+ margin: 2px;
+
+ .tag{
+ display: inline-block;
+ }
+
+ .category{
+ margin-right: 4px;
+ margin-left: 25px;
+ width: 25px;
+
+ }
+ .relation-categoty-icon{
+ .sprite;
+ .sprite.relation-icon;
+ .hand;
+ vertical-align: middle;
+
+ }
+
+ .relation-categoty-icon:hover{
+ .sprite;
+ .sprite.relation-icon-hover;
+ }
+
+ .delete{
+
+ }
+ .delete-icon{
+ .sprite;
+ .sprite.x-btn-black;
+ .hand;
+ vertical-align: middle;
+ }
+}
+
+.tag-tooltip-wrap {
+ background-color: rgba(80, 99, 113, 0.9);
+ position: relative;
+ display: inline-block;
+ margin: -5px -14px 0px -14px;
+}
diff --git a/catalog-ui/app/scripts/directives/tag/tag-directive.ts b/catalog-ui/app/scripts/directives/tag/tag-directive.ts
new file mode 100644
index 0000000000..64d245e242
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/tag/tag-directive.ts
@@ -0,0 +1,71 @@
+/*-
+ * ============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.Directives {
+ 'use strict';
+
+ export class TagData {
+ tag:string;
+ tooltip:string;
+ id: string;
+ }
+
+ export interface ITagScope extends ng.IScope {
+ tagData: TagData;
+ onDelete: Function;
+ delete:Function;
+ hideTooltip:boolean;
+ hideDelete:boolean;
+ sdcDisable: boolean;
+ }
+
+ export class TagDirective implements ng.IDirective {
+
+ constructor(private $templateCache:ng.ITemplateCacheService) {
+ }
+
+ scope = {
+ tagData: '=',
+ onDelete: '&',
+ hideTooltip: '=',
+ hideDelete: '=',
+ sdcDisable: '='
+ };
+
+ replace = true;
+ restrict = 'EA';
+ template = ():string => {
+ return this.$templateCache.get('/app/scripts/directives/tag/tag-directive.html');
+ };
+
+ link = (scope:ITagScope) => {
+ scope.delete = ()=>{
+ scope.onDelete({'uniqueId':scope.tagData.id});
+ }
+ };
+
+ public static factory = ($templateCache:ng.ITemplateCacheService)=> {
+ return new TagDirective($templateCache);
+ };
+
+ }
+
+ TagDirective.factory.$inject = ['$templateCache'];
+}
diff --git a/catalog-ui/app/scripts/directives/tutorial/image-template.html b/catalog-ui/app/scripts/directives/tutorial/image-template.html
new file mode 100644
index 0000000000..7e7f7af356
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/tutorial/image-template.html
@@ -0,0 +1,7 @@
+<perfect-scrollbar include-padding="true" class="sdc-tutorial-container-content sdc-tutorial-image-template">
+ <div class="{{pageObject.data.imageClass}}"></div>
+ <div class="sdc-tutorial-image-template-text">
+ <h1 translate="{{pageObject.data.title}}"></h1>
+ <p class="sdc-welcome-page-description2" translate="{{pageObject.data.description}}"></p>
+ </div>
+</perfect-scrollbar>
diff --git a/catalog-ui/app/scripts/directives/tutorial/text-template.html b/catalog-ui/app/scripts/directives/tutorial/text-template.html
new file mode 100644
index 0000000000..dc1173be64
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/tutorial/text-template.html
@@ -0,0 +1,4 @@
+<perfect-scrollbar include-padding="true" class="sdc-tutorial-container-content sdc-tutorial-text-template">
+ <h1 translate="{{pageObject.data.title}}"></h1>
+ <p class="sdc-welcome-page-description2" translate="{{pageObject.data.description}}"></p>
+</perfect-scrollbar>
diff --git a/catalog-ui/app/scripts/directives/tutorial/tutorial-directive.html b/catalog-ui/app/scripts/directives/tutorial/tutorial-directive.html
new file mode 100644
index 0000000000..191752fc1f
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/tutorial/tutorial-directive.html
@@ -0,0 +1,22 @@
+<div class="sdc-tutorial-page" data-ng-if="showTutorial">
+ <div class="sdc-tutorial-container-wrapper">
+ <div class="sdc-tutorial-skip" translate="{{isFirstTime?'TUTRIAL_GENERAL_SKIP_BUTTON':'TUTRIAL_GENERAL_CLOSE_BUTTON'}}" data-ng-click="closeTutorial()"></div>
+ <div class="sdc-tutorial-container">
+ <div class="sdc-tutorial-container-tabs">
+ <div class="sdc-tutorial-container-tab" data-ng-repeat="tab in tabs" data-ng-class="{'selected': tab.id===pageObject.tab}">
+ <span translate="{{tab.name}}" data-ng-click="initPage(tab.defaultPage)"></span>
+ </div>
+ </div>
+ <ng-include src="templateUrl"></ng-include>
+ </div>
+
+ <div class="sdc-tutorial-footer">
+ <div class="sdc-tutorial-footer-prev-button"><span data-ng-show="hasPrevious()" translate="TUTRIAL_GENERAL_PREVIOUS_BUTTON" data-ng-click="previous()"></span></div>
+ <div class="sdc-tutorial-footer-page-counter"><span class="selected" data-ng-bind="currentPageIndex+1"></span>/<span class="total" data-ng-bind="totalPages"></span></div>
+ <div class="sdc-tutorial-footer-next-button">
+ <span data-ng-if="hasNext()" translate="TUTRIAL_GENERAL_NEXT_BUTTON" data-ng-click="next()"></span>
+ <span data-ng-if="(currentPageIndex+1) === totalPages" translate="TUTRIAL_GENERAL_NEXT_BUTTON_END" data-ng-click="closeAndShowLastPage()"></span>
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/catalog-ui/app/scripts/directives/tutorial/tutorial-directive.less b/catalog-ui/app/scripts/directives/tutorial/tutorial-directive.less
new file mode 100644
index 0000000000..410a54e9c1
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/tutorial/tutorial-directive.less
@@ -0,0 +1,213 @@
+.sdc-tutorial-page {
+
+ width: 100%;
+ height: 100%;
+ background-color: rgba(0,0,0,0.8);
+
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: 3000;
+
+ .sdc-tutorial-container-wrapper {
+ display: flex;
+ flex-direction: column;
+ }
+
+ .sdc-tutorial-container {
+ .bg_c;
+ width: 830px;
+ height: 466px;
+ box-shadow: 1px 2px 2px 0px rgba(0, 0, 0, 0.35);
+ }
+
+ .sdc-tutorial-container-tabs {
+ height: 56px;
+ display: flex;
+ flex-direction: row;
+ }
+
+ .sdc-tutorial-container-tab {
+ .a_6;
+ flex-grow: 1;
+ align-items: center;
+ justify-content: center;
+ display: flex;
+ height: 56px;
+ position: relative;
+ opacity: 0.8;
+
+ span {
+ .hand;
+ }
+
+ &::after {
+ content: '';
+ display: block;
+ border-right: solid 1px ;
+ border-color: rgba(59, 123, 155, 0.31);
+ height: 28px;
+ right: 0;
+ position: absolute;
+ top: 14px; //(56-28)/2
+ width: 1px;
+ }
+
+ &:last-child:after {
+ display: none;
+ }
+
+ &.selected {
+ opacity: 1;
+ .bold;
+ }
+
+ }
+
+ .sdc-tutorial-container-content {
+ .bg_a;
+ .perfect-scrollbar;
+ display: flex;
+ align-items: center;
+ height: 410px;
+ }
+
+ .sdc-tutorial-skip {
+ .c_1;
+ .hand;
+ text-align: right;
+ margin-bottom: 9px;
+ }
+
+ .sdc-tutorial-footer {
+ .c_4;
+ margin-top: 9px;
+
+ .sdc-tutorial-footer-prev-button {
+ float: left;
+ position: relative;
+ padding-left: 14px;
+ .noselect;
+
+ span {
+ .hand;
+ &::before {
+ content: '<';
+ display: block;
+ position: absolute;
+ left: 0;
+ top: 0;
+ }
+ }
+ }
+
+ .sdc-tutorial-footer-page-counter {
+ .e_3;
+ position: absolute;
+ left: 50%;
+ margin-top: 2px;
+ cursor: default;
+ .noselect;
+
+ .selected {
+ .c_3;
+ .bold;
+ margin-right: 2px;
+ }
+
+ .total {
+ margin-left: 2px;
+ }
+ }
+
+ .sdc-tutorial-footer-next-button {
+ float: right;
+ position: relative;
+ padding-right: 14px;
+ .noselect;
+
+ span {
+ .hand;
+
+ &::after {
+ content: '>';
+ display: block;
+ position: absolute;
+ right: 0;
+ top: 0;
+ }
+ }
+ }
+
+ }
+
+}
+
+///////////////// TEXT TEMPLATE
+.sdc-tutorial-text-template {
+
+ padding: 20px 65px;
+
+ h1 {
+ .c_15;
+ margin-top: 0;
+ }
+
+ p {
+ .c_10;
+ }
+}
+
+///////////////// IMAGE TEMPLATE
+.sdc-tutorial-image-template {
+
+ .sdc-tutorial-image-template-text {
+ padding: 16px 38px;
+ height: 118px;
+ h1 {
+ .c_11;
+ margin: 0 0 4px 0;
+ }
+
+ p {
+ .c_4;
+ font-weight: 300;
+ line-height: 21px;
+ }
+
+ }
+
+ .sdc-tutorial-page-2-image { background: transparent url('../../../styles/images/tutorial/2.png') no-repeat 0 0; width: 830px; height: 292px;}
+ .sdc-tutorial-page-3-image { background: transparent url('../../../styles/images/tutorial/3.png') no-repeat 0 0; width: 830px; height: 292px;}
+ .sdc-tutorial-page-4-image { background: transparent url('../../../styles/images/tutorial/4.png') no-repeat 0 0; width: 830px; height: 292px;}
+ .sdc-tutorial-page-5-image { background: transparent url('../../../styles/images/tutorial/5.png') no-repeat 0 0; width: 830px; height: 292px;}
+ .sdc-tutorial-page-6-image { background: transparent url('../../../styles/images/tutorial/6.png') no-repeat 0 0; width: 830px; height: 292px;}
+ .sdc-tutorial-page-7-image { background: transparent url('../../../styles/images/tutorial/7.png') no-repeat 0 0; width: 830px; height: 292px;}
+ .sdc-tutorial-page-8-image { background: transparent url('../../../styles/images/tutorial/8.png') no-repeat 0 0; width: 830px; height: 292px;}
+
+ .sdc-tutorial-page-10-image { background: transparent url('../../../styles/images/tutorial/10.png') no-repeat 0 0; width: 830px; height: 292px;}
+ .sdc-tutorial-page-11-image { background: transparent url('../../../styles/images/tutorial/11.png') no-repeat 0 0; width: 830px; height: 292px;}
+
+ .sdc-tutorial-page-13-image { background: transparent url('../../../styles/images/tutorial/13.png') no-repeat 0 0; width: 830px; height: 292px;}
+ .sdc-tutorial-page-14-image { background: transparent url('../../../styles/images/tutorial/14.png') no-repeat 0 0; width: 830px; height: 292px;}
+ .sdc-tutorial-page-15-image { background: transparent url('../../../styles/images/tutorial/15.png') no-repeat 0 0; width: 830px; height: 292px;}
+ .sdc-tutorial-page-16-image { background: transparent url('../../../styles/images/tutorial/16.png') no-repeat 0 0; width: 830px; height: 292px;}
+ .sdc-tutorial-page-17-image { background: transparent url('../../../styles/images/tutorial/17.png') no-repeat 0 0; width: 830px; height: 292px;}
+ .sdc-tutorial-page-18-image { background: transparent url('../../../styles/images/tutorial/18.png') no-repeat 0 0; width: 830px; height: 292px;}
+ .sdc-tutorial-page-19-image { background: transparent url('../../../styles/images/tutorial/19.png') no-repeat 0 0; width: 830px; height: 292px;}
+ .sdc-tutorial-page-20-image { background: transparent url('../../../styles/images/tutorial/20.png') no-repeat 0 0; width: 830px; height: 292px;}
+ .sdc-tutorial-page-21-image { background: transparent url('../../../styles/images/tutorial/21.png') no-repeat 0 0; width: 830px; height: 292px;}
+ .sdc-tutorial-page-22-image { background: transparent url('../../../styles/images/tutorial/22.png') no-repeat 0 0; width: 830px; height: 292px;}
+ .sdc-tutorial-page-23-image { background: transparent url('../../../styles/images/tutorial/23.png') no-repeat 0 0; width: 830px; height: 292px;}
+ .sdc-tutorial-page-24-image { background: transparent url('../../../styles/images/tutorial/24.png') no-repeat 0 0; width: 830px; height: 292px;}
+ .sdc-tutorial-page-25-image { background: transparent url('../../../styles/images/tutorial/25.png') no-repeat 0 0; width: 830px; height: 292px;}
+ .sdc-tutorial-page-26-image { background: transparent url('../../../styles/images/tutorial/26.png') no-repeat 0 0; width: 830px; height: 292px;}
+ .sdc-tutorial-page-27-image { background: transparent url('../../../styles/images/tutorial/27.png') no-repeat 0 0; width: 830px; height: 292px;}
+ .sdc-tutorial-page-28-image { background: transparent url('../../../styles/images/tutorial/28.png') no-repeat 0 0; width: 830px; height: 292px;}
+ .sdc-tutorial-page-29-image { background: transparent url('../../../styles/images/tutorial/29.png') no-repeat 0 0; width: 830px; height: 292px;}
+ .sdc-tutorial-page-30-image { background: transparent url('../../../styles/images/tutorial/30.png') no-repeat 0 0; width: 830px; height: 292px;}
+
+}
diff --git a/catalog-ui/app/scripts/directives/tutorial/tutorial-directive.ts b/catalog-ui/app/scripts/directives/tutorial/tutorial-directive.ts
new file mode 100644
index 0000000000..7df35cade9
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/tutorial/tutorial-directive.ts
@@ -0,0 +1,147 @@
+/*-
+ * ============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.Directives {
+ 'use strict';
+ export interface ITutorialScope extends ng.IScope {
+ showTutorial:boolean;
+ isFirstTime:boolean;
+ templateUrl:string;
+ totalPages: number;
+ currentPageIndex: number;
+ page:number;
+ tabs:Array<string>;
+ tutorialData:any;
+ pageObject:any;
+
+ initPage:Function;
+ next:Function;
+ previous:Function;
+ hasNext():boolean;
+ hasPrevious():boolean;
+ closeTutorial:Function;
+ closeAndShowLastPage:Function;
+ }
+
+ export class TutorialDirective implements ng.IDirective {
+
+ constructor(
+ private $templateCache:ng.ITemplateCacheService,
+ private sdcConfig:Models.IAppConfigurtaion,
+ private $state:ng.ui.IStateService
+ ) {
+ }
+
+ scope = {
+ page: '=',
+ showTutorial: '=',
+ isFirstTime: '='
+ };
+
+ replace = false;
+ restrict = 'EA';
+ template = ():string => {
+ return this.$templateCache.get('/app/scripts/directives/tutorial/tutorial-directive.html');
+ };
+
+ link = (scope:ITutorialScope, $elem:any) => {
+
+ let findPageIndex:Function = (pageId:number):number=> {
+ for (let i:number=0;i<scope.totalPages;i++){
+ if (scope.tutorialData.pages[i].id===pageId){
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ let showCurrentPage:Function = ():void=> {
+ scope.pageObject = scope.tutorialData.pages[scope.currentPageIndex];
+ scope.templateUrl = '/app/scripts/directives/tutorial/' + scope.pageObject.template + '.html';
+ }
+
+ scope.tutorialData = this.sdcConfig.tutorial;
+
+ scope.closeTutorial = ()=> {
+ scope.showTutorial = false;
+ if(scope.isFirstTime){
+ scope.isFirstTime=false;
+ }
+ }
+
+ scope.closeAndShowLastPage = ()=> {
+ if(scope.isFirstTime){
+ this.$state.go('dashboard.tutorial-end');
+ }
+ scope.closeTutorial();
+ }
+
+ let init:Function = ():void => {
+ scope.tabs = scope.tutorialData.tabs;
+ scope.totalPages = scope.tutorialData.pages.length;
+ scope.initPage(scope.page);
+
+ }
+
+ scope.initPage = (pageId) => {
+ scope.currentPageIndex = findPageIndex(pageId);
+ showCurrentPage();
+ }
+
+ scope.next = ():void => {
+ if (scope.hasNext()){
+ scope.currentPageIndex++;
+ showCurrentPage();
+ }
+ }
+
+ scope.previous = ():void => {
+ if (scope.hasPrevious()){
+ scope.currentPageIndex--;
+ showCurrentPage();
+ }
+ }
+
+ scope.hasNext = ():boolean => {
+ return (scope.currentPageIndex+1) < scope.totalPages;
+ }
+
+ scope.hasPrevious = ():boolean => {
+ return scope.currentPageIndex>0;
+ }
+
+ angular.element(document).ready(function () {
+ init();
+ });
+
+ scope.$watch('showTutorial', (showTutorial:any):void => {
+ scope.initPage(scope.page);
+ });
+
+ };
+
+ public static factory = ($templateCache:ng.ITemplateCacheService, sdcConfig:Models.IAppConfigurtaion, $state:ng.ui.IStateService)=> {
+ return new TutorialDirective($templateCache, sdcConfig, $state);
+ };
+
+ }
+
+ TutorialDirective.factory.$inject = ['$templateCache', 'sdcConfig', '$state'];
+}
diff --git a/catalog-ui/app/scripts/directives/user-header-details/user-header-details-directive.html b/catalog-ui/app/scripts/directives/user-header-details/user-header-details-directive.html
new file mode 100644
index 0000000000..1c99a18ab5
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/user-header-details/user-header-details-directive.html
@@ -0,0 +1,9 @@
+<div class="w-sdc-header-user-container" data-tests-id="ffff">
+ <div class="w-sdc-header-user-icon"></div>
+ <div class="w-sdc-header-user-details">
+ <div sdc-smart-tooltip class="w-sdc-header-user-name" data-ng-bind="user.getName()"></div>
+ <div class="w-sdc-header-user-role" data-ng-bind="user.getRoleToView()"></div>
+ <div class="w-sdc-header-user-last-login" data-ng-show="user.getLastLogin()!==''">Last Login: {{user.getLastLogin() | date: 'MMM dd &nbsp; hh:mm a' : 'UTC'}}&nbsp;UTC</div>
+ </div>
+ <!--<div class="w-sdc-header-logout-icon"></div>-->
+</div>
diff --git a/catalog-ui/app/scripts/directives/user-header-details/user-header-details-directive.less b/catalog-ui/app/scripts/directives/user-header-details/user-header-details-directive.less
new file mode 100644
index 0000000000..a14db7c6ee
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/user-header-details/user-header-details-directive.less
@@ -0,0 +1,62 @@
+.w-sdc-header-user-container {
+ .b_7;
+ width: 400px;
+ .flex-fixed(400px);
+ padding: 0 23px;
+ display: flex;
+ justify-content: flex-end;
+}
+
+.w-sdc-header-user-icon {
+ background: no-repeat url('../../../styles/images/anonymous.jpg');
+ border-radius: 50%;
+ height: 47px;
+ width: 47px;
+ background-size: cover;
+ border: solid 2px @color_m;
+ .flex-fixed(47px);
+}
+
+.w-sdc-header-user-details {
+ padding: 4px 4px 4px 14px;
+ .vcenter;
+}
+
+.w-sdc-header-user-name {
+ max-width: 160px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ vertical-align: bottom;
+
+ .bold;
+ display: inline-block;
+}
+
+.w-sdc-header-user-role {
+ .bold;
+ display: inline-block;
+ margin-left: 6px;
+
+ &:before {
+ content: '';
+ margin-right: 8px;
+ border-left: 1px solid @color_m;
+ }
+}
+
+.w-sdc-header-user-last-login {
+ .font-type._3;
+ display: block;
+}
+
+.w-sdc-header-logout-icon {
+ background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAAUCAYAAACAl21KAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMDE0IDc5LjE1Njc5NywgMjAxNC8wOC8yMC0wOTo1MzowMiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTQgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjE5RjgxM0QxMDgzQTExRTVBQzA2OTg5ODlFNDVBNTZDIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjE5RjgxM0QyMDgzQTExRTVBQzA2OTg5ODlFNDVBNTZDIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6MTlGODEzQ0YwODNBMTFFNUFDMDY5ODk4OUU0NUE1NkMiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MTlGODEzRDAwODNBMTFFNUFDMDY5ODk4OUU0NUE1NkMiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz6Riq1bAAAA20lEQVR42mJ0CUv+z0Aa+AvEertXzrmGLMgCpZcD8S4iDfoDxHfQBWEGnQLasICBAsDEQCUwcAa5hqeEAHEKNVykB8SzgYZVUGpQPRDPAOJ2oGHNZBsEjF1QussC4mlAXAM0rAM5+tHDwQJIVRBh7m0gLgeql6ZarLHgcP4JIBWAJ+YYgdQUIPYH4k6g+gpyop8RGj6gcGoGGYLTRQRAIxBnAHEl0JAOvF4jAC4BcSrQkDkEw4hA9K8ZGpkW5jUzYGwkkFCwrQJ68Rc2gyKhmNii9hwQoxS1AAEGAO5PP/+ClIDAAAAAAElFTkSuQmCC');
+ height: 20px;
+ width: 18px;
+ position: absolute;
+ right: 20px;
+ top: 29px;
+}
+
+
diff --git a/catalog-ui/app/scripts/directives/user-header-details/user-header-details-directive.ts b/catalog-ui/app/scripts/directives/user-header-details/user-header-details-directive.ts
new file mode 100644
index 0000000000..46c43a266b
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/user-header-details/user-header-details-directive.ts
@@ -0,0 +1,72 @@
+/*-
+ * ============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.Directives {
+ 'use strict';
+ export interface IUserHeaderDetailsScope extends ng.IScope {
+ name: string;
+ role: string;
+ iconUrl: string;
+ UserResourceClass:Services.IUserResourceClass;
+ user: Models.IUser;
+ sdcConfig:Models.IAppConfigurtaion;
+ initUser:Function;
+ }
+
+ export class UserHeaderDetailsDirective implements ng.IDirective {
+
+ constructor(private $templateCache:ng.ITemplateCacheService, private $http:ng.IHttpService, private sdcConfig:Models.IAppConfigurtaion, private UserResourceClass:Services.IUserResourceClass) {
+ }
+
+ scope = {
+ iconUrl: '=?'
+ };
+
+ replace = true;
+ restrict = 'E';
+ template = ():string => {
+ return this.$templateCache.get('/app/scripts/directives/user-header-details/user-header-details-directive.html');
+ };
+
+ link = (scope:IUserHeaderDetailsScope) => {
+
+ scope.initUser = ():void => {
+ let defaultUserId:string;
+ let user:Services.IUserResource = this.UserResourceClass.getLoggedinUser();
+ if (!user) {
+ defaultUserId = this.$http.defaults.headers.common[this.sdcConfig.cookie.userIdSuffix];
+ user = this.UserResourceClass.get({id: defaultUserId}, ():void => {
+ scope.user = new Models.User(user);
+ });
+ } else {
+ scope.user = new Models.User(user);
+ }
+ };
+ scope.initUser();
+ };
+
+ public static factory = ($templateCache:ng.ITemplateCacheService, $http:ng.IHttpService, sdcConfig:Models.IAppConfigurtaion, UserResourceClass:Services.IUserResourceClass)=> {
+ return new UserHeaderDetailsDirective($templateCache, $http, sdcConfig, UserResourceClass);
+ };
+
+ }
+
+ UserHeaderDetailsDirective.factory.$inject = ['$templateCache', '$http', 'sdcConfig', 'Sdc.Services.UserResourceService'];
+}
diff --git a/catalog-ui/app/scripts/directives/utils/expand-collapse-menu-box/expand-collaps-menu-box.ts b/catalog-ui/app/scripts/directives/utils/expand-collapse-menu-box/expand-collaps-menu-box.ts
new file mode 100644
index 0000000000..9756ff9e49
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/utils/expand-collapse-menu-box/expand-collaps-menu-box.ts
@@ -0,0 +1,66 @@
+/*-
+ * ============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.Directives {
+ 'use strict';
+ export interface IExpandCollapseMenuBoxDirectiveScope extends ng.IScope {
+ menuItemsGroup: Utils.MenuItemGroup;
+ menuTitle: string;
+ parentScope: ng.IScope;
+ onMenuItemClick(menuItem: Utils.MenuItem):void;
+ }
+
+ export class ExpandCollapseMenuBoxDirective implements ng.IDirective {
+
+ constructor(private $templateCache:ng.ITemplateCacheService) {
+ }
+
+ scope = {
+ menuTitle: '@',
+ menuItemsGroup: '=',
+ parentScope: '='
+ };
+
+ public replace = false;
+ public restrict = 'AE';
+ public transclude = true;
+
+ template = ():string => {
+ return this.$templateCache.get('/app/scripts/directives/utils/expand-collapse-menu-box/expand-collapse-menu-box.html');
+ };
+
+ link = (scope:IExpandCollapseMenuBoxDirectiveScope, $elem:any) => {
+ scope.onMenuItemClick = (menuItem: Utils.MenuItem):void => {
+ let onSuccess = ():void => {
+ scope.menuItemsGroup.selectedIndex = scope.menuItemsGroup.menuItems.indexOf(menuItem);
+ };
+ let onFailed = ():void => {};
+ scope.parentScope[menuItem.action](menuItem.state).then(onSuccess, onFailed);
+ }
+ };
+
+ public static factory = ($templateCache:ng.ITemplateCacheService)=> {
+ return new ExpandCollapseMenuBoxDirective($templateCache);
+ };
+
+ }
+
+ ExpandCollapseMenuBoxDirective.factory.$inject = ['$templateCache'];
+}
diff --git a/catalog-ui/app/scripts/directives/utils/expand-collapse-menu-box/expand-collapse-menu-box.html b/catalog-ui/app/scripts/directives/utils/expand-collapse-menu-box/expand-collapse-menu-box.html
new file mode 100644
index 0000000000..bbd7e59e7c
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/utils/expand-collapse-menu-box/expand-collapse-menu-box.html
@@ -0,0 +1,15 @@
+<div class="expand-collapse-menu-box">
+ <expand-collapse expanded-selector=".w-sdc-designer-sidebar-section-content" class="expand-collapse-menu-box-title">
+ <div class="expand-collapse-menu-box-title-icon"></div>
+ <span class="w-sdc-designer-sidebar-section-title-text" data-ng-bind="menuTitle" tooltips tooltip-content="{{menuTitle}}"></span>
+ </expand-collapse>
+
+ <div class="w-sdc-designer-sidebar-section-content" >
+ <div class="i-sdc-designer-sidebar-section-content-item expand-collapse-menu-box-item"
+ ng-class="{'selected': $index == menuItemsGroup.selectedIndex}" ng-repeat="(key, menuItem) in menuItemsGroup.menuItems track by $index">
+ <div class="expand-collapse-menu-box-item-text" ng-click="onMenuItemClick(menuItem)" ng-class="{'disabled': menuItem.isDisabled }" data-tests-id="{{menuItem.text}}step" >{{menuItem.text}}</div>
+ </div>
+ </div>
+
+</div>
+
diff --git a/catalog-ui/app/scripts/directives/utils/expand-collapse-menu-box/expand-collapse-menu-box.less b/catalog-ui/app/scripts/directives/utils/expand-collapse-menu-box/expand-collapse-menu-box.less
new file mode 100644
index 0000000000..d8ceeaea71
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/utils/expand-collapse-menu-box/expand-collapse-menu-box.less
@@ -0,0 +1,55 @@
+.expand-collapse-menu-box {
+ line-height: 20px;
+ padding: 13px 0px 5px 10px;
+ background-color: @func_color_r;
+ margin: 3px 3px 5px 0px;
+
+
+ .expand-collapse-menu-box-title {
+ .f-type._18_m;
+ color: @main_color_m;
+ font-weight: bold;
+ .hand;
+ .w-sdc-designer-sidebar-section-title-text{
+ max-width: 185px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: inline-block;
+ white-space: nowrap;
+ }
+
+ &.expanded {
+ .expand-collapse-menu-box-title-icon {
+ transform: rotate(180deg);
+ }
+ }
+ }
+ .expand-collapse-menu-box-title-icon {
+ .hand;
+ .sprite-new;
+ .arrow-up;
+ margin-right: 6px;
+ transition: .3s all;
+ position: relative;
+
+ }
+ .w-sdc-designer-sidebar-section-content {
+ overflow: hidden;
+ padding-top: 13px;
+ .expand-collapse-menu-box-item {
+ .hand;
+ padding-left: 14px;
+ margin: 0px 0px 10px 10px;
+ font-family: @font-omnes-medium;
+ color: @main_color_m;
+
+ line-height: 18px;
+ &.selected {
+ padding-left: 10px;
+ font-weight: bold;
+ border-left: 4px solid @main_color_a;
+ }
+
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/directives/utils/expand-collapse/expand-collapse.html b/catalog-ui/app/scripts/directives/utils/expand-collapse/expand-collapse.html
new file mode 100644
index 0000000000..a2358ea2b7
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/utils/expand-collapse/expand-collapse.html
@@ -0,0 +1 @@
+<ng-transclude></ng-transclude>
diff --git a/catalog-ui/app/scripts/directives/utils/expand-collapse/expand-collapse.less b/catalog-ui/app/scripts/directives/utils/expand-collapse/expand-collapse.less
new file mode 100644
index 0000000000..d0d8fa3251
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/utils/expand-collapse/expand-collapse.less
@@ -0,0 +1,10 @@
+.ellipsis-directive-more-less {
+ .a_9;
+ .bold;
+ .hand;
+ float: right;
+ margin-right: 10px;
+ line-height: 23px;
+ text-decoration: underline;
+ text-align: left;
+}
diff --git a/catalog-ui/app/scripts/directives/utils/expand-collapse/expand-collapse.ts b/catalog-ui/app/scripts/directives/utils/expand-collapse/expand-collapse.ts
new file mode 100644
index 0000000000..b294da6c13
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/utils/expand-collapse/expand-collapse.ts
@@ -0,0 +1,136 @@
+/*-
+ * ============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.Directives {
+ 'use strict';
+ export interface IExpandCollapseScope extends ng.IScope {
+ toggle(): void;
+ collapsed: boolean;
+ expandedSelector: string;
+ content:string;
+ isCloseOnInit:boolean;
+ loadDataFunction: Function;
+ isLoadingData: boolean;
+ }
+
+ export class ExpandCollapseDirective implements ng.IDirective {
+
+ constructor(private $templateCache:ng.ITemplateCacheService) {
+ }
+
+ scope = {
+ expandedSelector: '@',
+ loadDataFunction: '&?',
+ isCloseOnInit: '=?'
+ };
+
+ public replace = false;
+ public restrict = 'AE';
+ public transclude = true;
+
+ template = ():string => {
+ return this.$templateCache.get('/app/scripts/directives/utils/expand-collapse/expand-collapse.html');
+ };
+
+ link = (scope:IExpandCollapseScope, $elem:any) => {
+ scope.collapsed = false;
+ scope.isLoadingData = false;
+ $elem.addClass('expanded');
+
+
+ if(scope.isCloseOnInit) {
+ window.setTimeout(function () {
+ toggle();
+ },0);
+ }
+
+ $elem.click(function(){
+ toggle();
+ });
+
+ let expand = ():void => {
+ $elem.addClass('expanded');
+ scope.collapsed = false;
+
+ let element = $(scope.expandedSelector)[0];
+ let prevWidth = element.style.height;
+ element.style.height = 'auto';
+ let endWidth = getComputedStyle(element).height;
+ element.style.height = prevWidth;
+ element.offsetHeight; // force repaint
+ element.style.transition = 'height .3s ease-in-out';
+ element.style.height = endWidth;
+ element.hidden = false;
+ element.addEventListener('transitionend', function transitionEnd(event) {
+ if (event['propertyName'] == 'height') {
+ element.style.transition = '';
+ element.style.height = 'auto';
+ element.removeEventListener('transitionend', transitionEnd, false);
+ }
+ }, false)
+ };
+
+ let collapse = ():void => {
+ $elem.removeClass('expanded');
+ scope.collapsed = true;
+
+ let element = $(scope.expandedSelector)[0];
+ element.style.height = getComputedStyle(element).height;
+ element.style.transition = 'height .5s ease-in-out';
+ element.offsetHeight; // force repaint
+ element.style.height = '0px';
+ element.hidden = true;
+ };
+
+ let toggle = ():void => {
+ if (scope.collapsed === true){
+ if(scope.loadDataFunction) {
+ scope.isLoadingData = true;
+ let onSuccess = () => {
+ window.setTimeout(function () {
+ expand();
+ scope.isLoadingData = false;
+ },0);
+ };
+ scope.loadDataFunction().then(onSuccess);
+ }
+ else {
+ if(scope.isLoadingData === false) {
+ expand();
+ }
+ }
+
+ } else {
+ if(scope.isLoadingData === false) {
+ collapse();
+ }
+ }
+ }
+
+ };
+
+ public static factory = ($templateCache:ng.ITemplateCacheService)=> {
+ return new ExpandCollapseDirective($templateCache);
+ };
+
+ }
+
+ ExpandCollapseDirective.factory.$inject = ['$templateCache'];
+}
diff --git a/catalog-ui/app/scripts/directives/utils/page-selector/page-selector.html b/catalog-ui/app/scripts/directives/utils/page-selector/page-selector.html
new file mode 100644
index 0000000000..4fbea447e2
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/utils/page-selector/page-selector.html
@@ -0,0 +1,9 @@
+<div class="i-sdc-left-sidebar-page-nav">
+ <ul data-ng-class="{'expanded': expanded===true}">
+ <li data-ng-repeat="item in list | filter:exceptSelectedComparator"
+ data-ng-click="expanded=false"
+ class="sidebar-page-nav-item"
+ ui-sref="{{item.url}}">{{item.name}}</li>
+ </ul>
+ <div class="sidebar-page-nav-item-selected" data-ng-click="openCollapse()">{{selected}}<span data-ng-class="{'expanded': expanded===true}"></span></div>
+</div>
diff --git a/catalog-ui/app/scripts/directives/utils/page-selector/page-selector.less b/catalog-ui/app/scripts/directives/utils/page-selector/page-selector.less
new file mode 100644
index 0000000000..da70218263
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/utils/page-selector/page-selector.less
@@ -0,0 +1,51 @@
+.i-sdc-left-sidebar-page-nav {
+
+ height: 64px;
+
+ .sidebar-page-nav-item-selected,
+ .sidebar-page-nav-item {
+ .i_11;
+ background-color: #e0e5e9;
+ width: 100%;
+ height: 64px;
+ border-bottom: solid 1px #cccccc;
+ line-height: 64px;
+ text-align: center;
+ cursor: pointer;
+ vertical-align: middle;
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ }
+
+ .sidebar-page-nav-item-selected {
+ z-index: 1010;
+ position: absolute;
+ top: 0px;
+ }
+
+ .sidebar-page-nav-item-selected span {
+ .sprite;
+ .sprite.table-arrow;
+ position: absolute;
+ top: 28px;
+ margin-left: 10px;
+
+ &.expanded {
+ .sprite;
+ .sprite.table-arrow.opened;
+ top: 30px;
+ }
+ }
+
+ ul {
+ position: absolute;
+ top: 0px;
+ padding: 0;
+ width: 100%;
+ z-index: 99;
+ visibility: hidden; //Need this and not display none, so I can use the function: getComputedStyle
+ .box-shadow(0px 4px 2px -2px rgba(0, 0, 0, 0.36));
+ }
+
+}
diff --git a/catalog-ui/app/scripts/directives/utils/page-selector/page-selector.ts b/catalog-ui/app/scripts/directives/utils/page-selector/page-selector.ts
new file mode 100644
index 0000000000..c185fe1c15
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/utils/page-selector/page-selector.ts
@@ -0,0 +1,106 @@
+/*-
+ * ============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.Directives {
+ 'use strict';
+
+ class ListItem {
+ name;
+ url;
+ }
+
+ export interface IPageSelectorScope extends ng.IScope {
+ selected:string;
+ expanded: boolean;
+ list:Array<ListItem>;
+ exceptSelectedComparator(actual, expected):boolean;
+ openCollapse();
+ }
+
+ export class PageSelectorDirective implements ng.IDirective {
+
+ constructor(private $templateCache:ng.ITemplateCacheService) {
+ }
+
+ scope = {
+ list: '=',
+ selected: '@',
+ };
+
+ public replace = true;
+ public restrict = 'E';
+ public transclude = false;
+
+ private ulElement:HTMLElement;
+ private itemHeight:number = 64;
+
+ private getUlHeight = ():number => {
+ let tmp:string = getComputedStyle(this.ulElement).height;
+ //console.log("tmp: " + tmp);
+ let ulHeight:number = parseInt(tmp.substr(0,tmp.length-2));
+ //console.log("ulHeight: " + ulHeight);
+ return ulHeight;
+ };
+
+ template = ():string => {
+ return this.$templateCache.get('/app/scripts/directives/utils/page-selector/page-selector.html');
+ };
+
+ link = (scope:IPageSelectorScope, $elem:any) => {
+ scope.expanded=false;
+
+ window.setTimeout(() => {
+ this.ulElement = angular.element(".i-sdc-left-sidebar-page-nav ul")[0];
+ console.log("this.ulElement: " + this.ulElement);
+ console.log("this.itemHeight: " + this.itemHeight);
+ this.ulElement.style.top = (this.itemHeight - this.getUlHeight() - 5) + 'px';
+ this.ulElement.style.visibility = 'visible';
+ },10);
+
+ this.ulElement = angular.element(".i-sdc-left-sidebar-page-nav ul")[0];
+
+ scope.exceptSelectedComparator = (actual) => {
+ if (actual.name===scope.selected) {
+ return false;
+ }
+ return true;
+ };
+
+ scope.openCollapse = ():void => {
+ scope.expanded=!scope.expanded;
+ if (scope.expanded===true) {
+ this.ulElement.style.transition = 'top 0.4s ease-out';
+ this.ulElement.style.top = this.itemHeight + 'px';
+ } else {
+ this.ulElement.style.transition = 'top 0.4s ease-in';
+ this.ulElement.style.top = (this.itemHeight - this.getUlHeight() - 5) + 'px';
+ }
+ };
+
+ };
+
+ public static factory = ($templateCache:ng.ITemplateCacheService)=> {
+ return new PageSelectorDirective($templateCache);
+ };
+
+ }
+
+ PageSelectorDirective.factory.$inject = ['$templateCache'];
+}
diff --git a/catalog-ui/app/scripts/directives/utils/sdc-keyboard-events/sdc-keyboard-events.ts b/catalog-ui/app/scripts/directives/utils/sdc-keyboard-events/sdc-keyboard-events.ts
new file mode 100644
index 0000000000..9e61caa812
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/utils/sdc-keyboard-events/sdc-keyboard-events.ts
@@ -0,0 +1,106 @@
+/*-
+ * ============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.Directives {
+ 'use strict';
+
+ export interface ISdcKeyboardEventsScope extends ng.IScope {
+ keyEnter:Function;
+ keyShift:Function;
+ keyCtrl:Function;
+ keyEscape:Function;
+ keySpace:Function;
+ }
+
+ export class SdcKeyboardEventsDirective implements ng.IDirective {
+
+ constructor() {
+ }
+
+ scope = {
+ keyEnter: '=',
+ keyShift: '=',
+ keyCtrl: '=',
+ keyEscape: '=',
+ keySpace: '='
+ };
+
+ public replace = false;
+ public restrict = 'A';
+ public transclude = false;
+
+ link = (scope:ISdcKeyboardEventsScope, element:ng.IAugmentedJQuery, attrs:angular.IAttributes) => {
+
+ element.bind("keydown keypress", function (event) {
+ //console.log(event.which);
+ switch (event.which) {
+ case 13: // enter key
+ scope.$apply(function (){
+ if (scope.keyEnter) {
+ scope.keyEnter();
+ event.preventDefault();
+ }
+ });
+ break;
+ case 16: // shift key
+ scope.$apply(function (){
+ if (scope.keyShift) {
+ scope.keyShift();
+ event.preventDefault();
+ }
+ });
+ break;
+ case 17: // ctrl key
+ scope.$apply(function (){
+ if (scope.keyCtrl) {
+ scope.keyCtrl();
+ event.preventDefault();
+ }
+ });
+ break;
+ case 27: // escape key
+ scope.$apply(function (){
+ if (scope.keyEscape) {
+ scope.keyEscape();
+ event.preventDefault();
+ }
+ });
+ break;
+ case 32: // space key
+ scope.$apply(function (){
+ if (scope.keySpace) {
+ scope.keySpace();
+ event.preventDefault();
+ }
+ });
+ break;
+ }
+ });
+
+ };
+
+ public static factory = ()=> {
+ return new SdcKeyboardEventsDirective();
+ };
+
+ }
+
+ SdcKeyboardEventsDirective.factory.$inject = [];
+}
diff --git a/catalog-ui/app/scripts/directives/utils/sdc-tags/sdc-tags.html b/catalog-ui/app/scripts/directives/utils/sdc-tags/sdc-tags.html
new file mode 100644
index 0000000000..fb1ada69c3
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/utils/sdc-tags/sdc-tags.html
@@ -0,0 +1,27 @@
+<div class="tags-box" >
+ <input type="text"
+ name="{{elementName}}"
+ class="new-tag-input"
+ data-ng-class="{'view-mode':sdcDisabled}"
+ data-ng-change="validateName()"
+ data-ng-model="newTag"
+ data-ng-maxlength="50"
+ data-ng-pattern="pattern"
+ data-tests-id="i-sdc-tag-input"
+ maxlength="50"
+ sdc-keyboard-events
+ key-enter="addTag"
+
+ />
+ <perfect-scrollbar class="perfect-scrollbar tags-wrapper" data-ng-class="{'view-mode':sdcDisabled}" include-padding="true">
+ <div data-tests-id="i-sdc-tags-wrapper" >
+ <div class="group-tag" data-ng-show="specialTag">
+ <sdc-tag data-hide-tooltip="true" data-hide-delete="true"
+ data-tag-data="{tag: specialTag, id: specialTag }"></sdc-tag>
+ </div>
+ <div class="group-tag" ng-repeat="tag in tags track by $index">
+ <sdc-tag ng-if="tag != specialTag" data-on-delete="deleteTag(tag)" sdc-disable="sdcDisabled" data-hide-delete="sdcDisabled" data-hide-tooltip="true" data-tag-data="{tag: tag, id: tag }"></sdc-tag>
+ </div>
+ </div>
+ </perfect-scrollbar>
+</div>
diff --git a/catalog-ui/app/scripts/directives/utils/sdc-tags/sdc-tags.less b/catalog-ui/app/scripts/directives/utils/sdc-tags/sdc-tags.less
new file mode 100644
index 0000000000..942196e663
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/utils/sdc-tags/sdc-tags.less
@@ -0,0 +1,61 @@
+.tags-box {
+
+ height: 297px;
+ .bg_c;
+
+ .perfect-scrollbar {
+ height: 265px;
+ }
+
+ .new-tag-input {
+ display: block;
+
+ -webkit-border-bottom-left-radius: 0 !important;
+ -moz-border-radius-bottomleft: 0 !important;
+ -khtml-border-bottom-left-radius: 0 !important;
+ border-bottom-left-radius: 0 !important;
+
+ -webkit-border-bottom-right-radius: 0 !important;
+ -moz-border-radius-bottomright: 0 !important;
+ -khtml-border-bottom-right-radius: 0 !important;
+ border-bottom-right-radius: 0 !important;
+
+ border: solid 1px #d8d8d8;
+ width: 100%;
+ height: 30px;
+ line-height: 30px;
+ padding: 2px 10px;
+ outline: none;
+ }
+
+ .tags-wrapper {
+ padding: 10px;
+ .border-radius-bottom-left(2px);
+ .border-radius-bottom-right(2px);
+ border: solid 1px #d8d8d8;
+ border-top: none;
+
+ .group-tag {
+ display: inline-block;
+
+ .sdc-tag {
+ border: solid 1px @main_color_n;
+ background-color: @main_color_p;
+ min-width: auto;
+ .tag {
+ margin-right: 10px;
+ }
+ }
+ }
+ &.view-mode .group-tag {
+ opacity: 1;
+ background-color: #f8f8f8 !important;
+ .sdc-tag {
+ background: none;
+ border-color: @main_color_o;
+ }
+ }
+ }
+
+}
+
diff --git a/catalog-ui/app/scripts/directives/utils/sdc-tags/sdc-tags.ts b/catalog-ui/app/scripts/directives/utils/sdc-tags/sdc-tags.ts
new file mode 100644
index 0000000000..3f4147c920
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/utils/sdc-tags/sdc-tags.ts
@@ -0,0 +1,97 @@
+/*-
+ * ============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.Directives {
+ 'use strict';
+
+ export interface ISdcTagsScope extends ng.IScope {
+ tags:Array<string>;
+ specialTag:string;
+ newTag:string;
+ formElement:ng.IFormController;
+ elementName:string;
+ pattern:any;
+ sdcDisabled:boolean;
+ maxTags:number;
+ deleteTag(tag:string):void;
+ addTag(tag:string):void;
+ validateName():void;
+ }
+
+ export class SdcTagsDirective implements ng.IDirective {
+
+ constructor(private $templateCache:ng.ITemplateCacheService) {
+ }
+
+ scope = {
+ tags: '=',
+ specialTag: '=',
+ pattern: '=',
+ sdcDisabled: '=',
+ formElement: '=',
+ elementName: '@',
+ maxTags: '@'
+ };
+
+ public replace = false;
+ public restrict = 'E';
+ public transclude = false;
+
+ template = ():string => {
+ return this.$templateCache.get('/app/scripts/directives/utils/sdc-tags/sdc-tags.html');
+ };
+
+ link = (scope:ISdcTagsScope, element:ng.INgModelController) => {
+
+ scope.deleteTag = (tag:string):void => {
+ scope.tags.splice(scope.tags.indexOf(tag),1);
+ };
+
+ scope.addTag = ():void => {
+ let valid = scope.formElement[scope.elementName].$valid;
+ if (valid &&
+ scope.tags.length<scope.maxTags &&
+ scope.newTag &&
+ scope.newTag!=='' &&
+ scope.tags.indexOf(scope.newTag)===-1 &&
+ scope.newTag!==scope.specialTag) {
+ scope.tags.push(scope.newTag);
+ scope.newTag='';
+ }
+ };
+
+ scope.validateName = ():void => {
+ if (scope.tags.indexOf(scope.newTag)>-1) {
+ scope.formElement[scope.elementName].$setValidity('nameExist', false);
+ }else{
+ scope.formElement[scope.elementName].$setValidity('nameExist', true);
+ }
+ }
+
+ };
+
+ public static factory = ($templateCache:ng.ITemplateCacheService)=> {
+ return new SdcTagsDirective($templateCache);
+ };
+
+ }
+
+ SdcTagsDirective.factory.$inject = ['$templateCache'];
+}
diff --git a/catalog-ui/app/scripts/directives/utils/sdc_error_tooltip/sdc_error_tooltip.html b/catalog-ui/app/scripts/directives/utils/sdc_error_tooltip/sdc_error_tooltip.html
new file mode 100644
index 0000000000..376381b8af
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/utils/sdc_error_tooltip/sdc_error_tooltip.html
@@ -0,0 +1,6 @@
+<div class="i-sdc-form-item-error-message" style="display: none;">
+ <span class="i-sdc-form-item-error-icon-open"></span>
+ <ng-transclude>
+
+ </ng-transclude>
+</div>
diff --git a/catalog-ui/app/scripts/directives/utils/sdc_error_tooltip/sdc_error_tooltip.ts b/catalog-ui/app/scripts/directives/utils/sdc_error_tooltip/sdc_error_tooltip.ts
new file mode 100644
index 0000000000..dc30ea7f41
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/utils/sdc_error_tooltip/sdc_error_tooltip.ts
@@ -0,0 +1,109 @@
+/*-
+ * ============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.Directives {
+ 'use strict';
+
+ export interface ISdcErrorTooltipScope extends ng.IScope {
+ alignToSelector: string;
+ topMargin: string;
+ }
+
+ export class SdcErrorTooltipDirective implements ng.IDirective {
+
+ constructor(private $templateCache:ng.ITemplateCacheService) {
+ }
+
+ scope = {
+ alignToSelector: '@', // Jquery selector to align to
+ topMargin: '@' // The margin from the top, in case there is label or not the top margin is different.
+ };
+
+ public replace = false;
+ public restrict = 'E';
+ public transclude = true;
+
+ template = ():string => {
+ return this.$templateCache.get('/app/scripts/directives/utils/sdc_error_tooltip/sdc_error_tooltip.html');
+ };
+
+ link = (scope:ISdcErrorTooltipScope, $elem:any) => {
+ let _self = this;
+
+ $elem.addClass("i-sdc-form-item-error-icon");
+
+ // Calculate the position of the elements after they loaded to the dom.
+ window.setTimeout(function(){
+ _self.calculatePosition(scope, $elem);
+ },100);
+
+ $elem.bind('mouseover', function(){
+ $(".i-sdc-form-item-error-message",$elem).css("display", "block");
+ });
+
+ $elem.bind('mouseleave', function(){
+ $(".i-sdc-form-item-error-message",$elem).css("display", "none");
+ });
+
+ }
+
+ private calculatePosition(scope:ISdcErrorTooltipScope, $elem:any):void {
+ let leftMargin = 13;
+ let topMargin = scope.topMargin? parseInt(scope.topMargin) : 10;
+
+ if (scope.alignToSelector) {
+ // Set the position of the error, in case user add align-to-selector attribute
+ let jObj = $(scope.alignToSelector);
+ if (jObj.length > 0) {
+ let height1 = jObj.outerHeight();
+ $elem.css('left', jObj.position().left + jObj.outerWidth() + leftMargin);
+ //$elem.css('top', jObj.position().top + topMargin + (height1 / 2));
+ $elem.css('top', jObj.position().top + (height1 / 2) - 5); // Label margin is: 2
+ }
+ } else {
+ // Set the position of the error, according to the input element.
+ let inputElm = $elem.siblings('input');
+ let textareaElm = $elem.siblings('textarea');
+ let selectElm = $elem.siblings('select');
+ if (inputElm.length > 0) {
+ $elem.css('left', inputElm.outerWidth() + leftMargin);
+ $elem.css('top', inputElm.position().top + topMargin);
+ } else if (textareaElm.length > 0) {
+ $elem.css('left', textareaElm.outerWidth() + leftMargin);
+ let height2 = textareaElm.outerHeight();
+ let elmHeight2 = $elem.outerHeight();
+ //let top = textareaElm.position().top;
+ $elem.css('bottom', (height2 - (elmHeight2 / 2)) / 2);
+ } else if (selectElm.length > 0) {
+ $elem.css('left', selectElm.outerWidth() + leftMargin);
+ $elem.css('top', selectElm.position().top + topMargin);
+ }
+ }
+ }
+
+ public static factory = ($templateCache:ng.ITemplateCacheService)=> {
+ return new SdcErrorTooltipDirective($templateCache);
+ };
+
+ }
+
+ SdcErrorTooltipDirective.factory.$inject = ['$templateCache'];
+
+}
diff --git a/catalog-ui/app/scripts/directives/utils/sdc_messages/sdc-message.ts b/catalog-ui/app/scripts/directives/utils/sdc_messages/sdc-message.ts
new file mode 100644
index 0000000000..d41ef1ce04
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/utils/sdc_messages/sdc-message.ts
@@ -0,0 +1,179 @@
+/*-
+ * ============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.Directives {
+ 'use strict';
+ export interface ISdcMessageScope extends ng.IScope {
+ sdcTranslate: string;
+ sdcTranslateValues:string;
+ sdcAlign:string;
+ }
+
+ export class SdcMessageDirective implements ng.IDirective {
+
+ constructor(private $animate:any, private $filter:any, private $parse:any) {
+ }
+
+ scope = {
+ field: '=',
+ required: '@',
+ pattern: '@',
+ sdcTranslate: '@',
+ sdcTranslateValues: '@',
+ sdcAlign: '@'
+ };
+
+ public terminal = true;
+ public restrict = 'A';
+ public transclude = 'element';
+ public require = '^^sdcMessages';
+
+ link = (scope:ISdcMessageScope, $element:any, $attrs:any,sdcMessagesCtrl:any, $transclude:any) => {
+ let self = this;
+
+ let commentNode = $element[0];
+
+ let records;
+ let staticExp = $attrs.sdcMessage || $attrs.when;
+ let dynamicExp = $attrs.sdcMessageExp || $attrs.whenExp;
+ let assignRecords = function(items) {
+ records = items
+ ? (angular.isArray(items)
+ ? items
+ : items.split(/[\s,]+/))
+ : null;
+ sdcMessagesCtrl.reRender();
+ };
+
+ if (dynamicExp) {
+ assignRecords(scope.$eval(dynamicExp));
+ scope.$watchCollection(dynamicExp, assignRecords);
+ } else {
+ assignRecords(staticExp);
+ }
+
+ let currentElement, messageCtrl;
+ sdcMessagesCtrl.register(commentNode, messageCtrl = {
+ test: function (name) {
+ return self.contains(records, name);
+ },
+ attach: function () {
+ if (!currentElement) {
+ $transclude(scope, function (elm) {
+
+ self.$animate.enter(elm, null, $element);
+ currentElement = elm;
+
+ elm.addClass("i-sdc-form-item-error-message");
+
+ //$compile
+ let text;
+ if (scope.sdcTranslate) {
+ text = self.$filter('translate')(scope.sdcTranslate, scope.sdcTranslateValues);
+ } else {
+ //TODO: Need to handle this
+ //let t = elm.html();
+ //let t = angular.element("<span>" + elm.html() + "</span>");
+ //text = self.$parse(t);
+ }
+
+ //scope.sdcTranslateValues
+ elm.html(text);
+
+ elm.prepend("<span class='error'></span>");
+
+ // Adding OK to close the message
+ //let okElm = $('<span />').attr('class', 'ok').html('OK');
+ //okElm.click(function(e){
+ // messageCtrl.detach();
+ //});
+ //elm.append(okElm);
+
+ // Handle the position
+ if (scope.sdcAlign){
+ let choosenElm = $(scope.sdcAlign);
+ if (choosenElm.length > 0) {
+ let height1 = choosenElm.outerHeight();
+ let elmHeight1 = elm.outerHeight();
+ elm.css('left', choosenElm.outerWidth());
+ elm.css('bottom', (height1 - (elmHeight1 / 2)) / 2);
+ }
+ } else {
+ // Set the position of the error, according to the input element.
+ let inputElm = elm.parent().siblings('input');
+ let textareaElm = elm.parent().siblings('textarea');
+ let selectElm = elm.parent().siblings('select');
+ if (inputElm.length > 0) {
+ elm.css('left', inputElm.outerWidth());
+ elm.css('top', inputElm.position().top);
+ } else if (textareaElm.length > 0) {
+ elm.css('left', textareaElm.outerWidth());
+ let height = textareaElm.outerHeight();
+ let elmHeight = elm.outerHeight();
+ //let top = textareaElm.position().top;
+ elm.css('bottom', (height - (elmHeight / 2)) / 2);
+ } else if (selectElm.length > 0) {
+ elm.css('left', selectElm.outerWidth());
+ elm.css('top', selectElm.position().top);
+ }
+ }
+
+ // Each time we attach this node to a message we get a new id that we can match
+ // when we are destroying the node later.
+ let $$attachId = currentElement.$$attachId = sdcMessagesCtrl.getAttachId();
+
+ // in the event that the parent element is destroyed
+ // by any other structural directive then it's time
+ // to deregister the message from the controller
+ currentElement.on('$destroy', function () {
+ if (currentElement && currentElement.$$attachId === $$attachId) {
+ sdcMessagesCtrl.deregister(commentNode);
+ messageCtrl.detach();
+ }
+ });
+ });
+ }
+ },
+ detach: function () {
+ if (currentElement) {
+ let elm = currentElement;
+ currentElement = null;
+ self.$animate.leave(elm);
+ }
+ }
+ });
+ }
+
+ contains = (collection, key):any => {
+ if (collection) {
+ return angular.isArray(collection)
+ ? collection.indexOf(key) >= 0
+ : collection.hasOwnProperty(key);
+ }
+ }
+
+ public static factory = ($animate:any, $filter:any, $parse:any)=> {
+ return new SdcMessageDirective($animate, $filter, $parse);
+ };
+
+ }
+
+ SdcMessageDirective.factory.$inject = ['$animate', '$filter', '$parse'];
+}
diff --git a/catalog-ui/app/scripts/directives/utils/sdc_messages/sdc-messages.less b/catalog-ui/app/scripts/directives/utils/sdc_messages/sdc-messages.less
new file mode 100644
index 0000000000..d8dfdbb73b
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/utils/sdc_messages/sdc-messages.less
@@ -0,0 +1,10 @@
+.ellipsis-directive-more-less {
+ .a_9;
+ .bold;
+ .hand;
+ float: right;
+ margin-right: 17px;
+ line-height: 23px;
+ text-decoration: underline;
+ text-align: left;
+}
diff --git a/catalog-ui/app/scripts/directives/utils/sdc_messages/sdc-messages.ts b/catalog-ui/app/scripts/directives/utils/sdc_messages/sdc-messages.ts
new file mode 100644
index 0000000000..f8b435b1fa
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/utils/sdc_messages/sdc-messages.ts
@@ -0,0 +1,245 @@
+/*-
+ * ============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.Directives {
+ 'use strict';
+ export interface ISdcMessagesScope extends ng.IScope {
+ sdcMessages: any;
+ editForm:ng.IFormController;
+ }
+
+ export class SdcMessagesDirective implements ng.IDirective {
+
+ constructor() {}
+
+ scope = {
+ sdcMessages: '='
+ };
+
+ public restrict = 'AE';
+ public require = 'sdcMessages';
+ public controller = SdcMessagesController;
+
+ /*template = ():string => {
+ return this.$templateCache.get('/app/scripts/directives/utils/sdc-messages/sdc-messages.html');
+ };
+
+ public static factory = ($templateCache:ng.ITemplateCacheService)=> {
+ return new SdcMessagesDirective($templateCache);
+ };*/
+
+ public static factory = ()=> {
+ return new SdcMessagesDirective();
+ }
+
+ }
+
+ export class SdcMessagesController {
+
+ messages:any;
+ getAttachId:Function;
+ render:any;
+ reRender:Function;
+ register:Function;
+ deregister:Function;
+ head:any;
+
+ static '$inject' = [
+ '$element',
+ '$scope',
+ '$attrs',
+ '$animate'
+ ];
+
+ constructor(private $element:JQuery,
+ private $scope:ISdcMessagesScope,
+ private $attrs:ng.IAttributes,
+ private $animate:any
+ ) {
+
+ this.init();
+
+ }
+
+ init=():void => {
+ let self = this;
+
+ let ACTIVE_CLASS:string = 'ng-active';
+ let INACTIVE_CLASS:string = 'ng-inactive';
+
+ let ctrl = this;
+ let latestKey = 0;
+ let nextAttachId = 0;
+
+ this.getAttachId = function getAttachId() { return nextAttachId++; };
+
+ let messages = this.messages = {};
+ let renderLater, cachedCollection;
+
+ this.render = function(collection) {
+ collection = collection || {};
+
+ renderLater = false;
+ cachedCollection = collection;
+
+ // this is true if the attribute is empty or if the attribute value is truthy
+ let multiple = self.isAttrTruthy(self.$scope, self.$attrs['sdcMessagesMultiple']) || self.isAttrTruthy(self.$scope, self.$attrs['multiple']);
+
+ let unmatchedMessages = [];
+ let matchedKeys = {};
+ let messageItem = ctrl.head;
+ let messageFound = false;
+ let totalMessages = 0;
+
+ // we use != instead of !== to allow for both undefined and null values
+ while (messageItem != null) {
+ totalMessages++;
+ let messageCtrl = messageItem.message;
+
+ let messageUsed = false;
+ if (!messageFound) {
+ _.each(collection, function(value, key) {
+ if (!messageUsed && self.truthy(value) && messageCtrl.test(key)) {
+ // this is to prevent the same error name from showing up twice
+ if (matchedKeys[key]) return;
+ matchedKeys[key] = true;
+
+ messageUsed = true;
+ messageCtrl.attach();
+ }
+ });
+ }
+
+ if (messageUsed) {
+ // unless we want to display multiple messages then we should
+ // set a flag here to avoid displaying the next message in the list
+ messageFound = !multiple;
+ } else {
+ unmatchedMessages.push(messageCtrl);
+ }
+
+ messageItem = messageItem.next;
+ }
+
+ _.each(unmatchedMessages, function(messageCtrl) {
+ messageCtrl.detach();
+ });
+
+ unmatchedMessages.length !== totalMessages
+ ? ctrl.$animate.setClass(self.$element, ACTIVE_CLASS, INACTIVE_CLASS)
+ : ctrl.$animate.setClass(self.$element, INACTIVE_CLASS, ACTIVE_CLASS);
+ };
+
+ self.$scope.$watchCollection('sdcMessages' || self.$attrs['for'], function(newVal:any, oldVal:any){
+ ctrl.render(newVal);
+ });
+
+ this.reRender = function() {
+ if (!renderLater) {
+ renderLater = true;
+ self.$scope.$evalAsync(function() {
+ if (renderLater) {
+ cachedCollection && ctrl.render(cachedCollection);
+ }
+ });
+ }
+ };
+
+ this.register = function(comment, messageCtrl) {
+ let nextKey = latestKey.toString();
+ messages[nextKey] = {
+ message: messageCtrl
+ };
+ insertMessageNode(self.$element[0], comment, nextKey);
+ comment.$$sdcMessageNode = nextKey;
+ latestKey++;
+
+ ctrl.reRender();
+ };
+
+ this.deregister = function(comment) {
+ let key = comment.$$sdcMessageNode;
+ delete comment.$$sdcMessageNode;
+ removeMessageNode(self.$element[0], comment, key);
+ delete messages[key];
+ ctrl.reRender();
+ };
+
+ function findPreviousMessage(parent, comment) {
+ let prevNode = comment;
+ let parentLookup = [];
+ while (prevNode && prevNode !== parent) {
+ let prevKey = prevNode.$$sdcMessageNode;
+ if (prevKey && prevKey.length) {
+ return messages[prevKey];
+ }
+
+ // dive deeper into the DOM and examine its children for any sdcMessage
+ // comments that may be in an element that appears deeper in the list
+ if (prevNode.childNodes.length && parentLookup.indexOf(prevNode) == -1) {
+ parentLookup.push(prevNode);
+ prevNode = prevNode.childNodes[prevNode.childNodes.length - 1];
+ } else {
+ prevNode = prevNode.previousSibling || prevNode.parentNode;
+ }
+ }
+ }
+
+ function insertMessageNode(parent, comment, key) {
+ let messageNode = messages[key];
+ if (!ctrl.head) {
+ ctrl.head = messageNode;
+ } else {
+ let match = findPreviousMessage(parent, comment);
+ if (match) {
+ messageNode.next = match.next;
+ match.next = messageNode;
+ } else {
+ messageNode.next = ctrl.head;
+ ctrl.head = messageNode;
+ }
+ }
+ }
+
+ function removeMessageNode(parent, comment, key) {
+ let messageNode = messages[key];
+
+ let match = findPreviousMessage(parent, comment);
+ if (match) {
+ match.next = messageNode.next;
+ } else {
+ ctrl.head = messageNode.next;
+ }
+ }
+ }
+
+ isAttrTruthy = (scope, attr):any => {
+ return (angular.isString(attr) && attr.length === 0) || //empty attribute
+ this.truthy(scope.$eval(attr));
+ }
+
+ truthy = (val):any => {
+ return angular.isString(val) ? val.length : !!val;
+ }
+
+ }
+
+ SdcMessagesDirective.factory.$inject = ['$templateCache','$animate'];
+}
diff --git a/catalog-ui/app/scripts/directives/utils/sdc_messages/sdc_messages.html b/catalog-ui/app/scripts/directives/utils/sdc_messages/sdc_messages.html
new file mode 100644
index 0000000000..09b1cad4d2
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/utils/sdc_messages/sdc_messages.html
@@ -0,0 +1 @@
+<span>aaa</span>
diff --git a/catalog-ui/app/scripts/directives/utils/smart-tooltip/smart-tooltip.ts b/catalog-ui/app/scripts/directives/utils/smart-tooltip/smart-tooltip.ts
new file mode 100644
index 0000000000..49a57245e7
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/utils/smart-tooltip/smart-tooltip.ts
@@ -0,0 +1,85 @@
+/*-
+ * ============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.Directives {
+ 'use strict';
+
+ export interface ISmartTooltipScope extends ng.IScope {
+ sdcSmartToolip;
+ }
+
+ export class SmartTooltipDirective implements ng.IDirective {
+
+ constructor(private $templateCache:ng.ITemplateCacheService,
+ private $compile:ng.ICompileService) {
+ }
+
+ public replace = false;
+ public restrict = 'A';
+ public transclude = false;
+
+ public link = (scope:ISmartTooltipScope, $elem:ng.IAugmentedJQuery, $attrs:angular.IAttributes) => {
+
+ if ($elem[0].hasAttribute('style')===false){
+ $elem[0].setAttribute("style", "overflow: hidden; white-space: nowrap; text-overflow: ellipsis;");
+ } else {
+ let styles = $elem.attr('style');
+ $elem[0].setAttribute("style", styles + ";overflow: hidden; white-space: nowrap; text-overflow: ellipsis;");
+ }
+
+ $elem.bind('mouseenter', () => {
+ if($elem[0].offsetWidth < $elem[0].scrollWidth && !$elem.attr('tooltips')){
+ $attrs.$set('tooltips', 'tooltips');
+ if ($attrs['sdcSmartTooltip'] && $attrs['sdcSmartTooltip'].length>0){
+ $elem.attr('tooltip-content', $attrs['sdcSmartTooltip']);
+ } else {
+ $attrs.$set('tooltip-content', $elem.text());
+ }
+
+ //One possible problem arises when the ngIf is placed on the root element of the template.
+ //ngIf removes the node and places a comment in it's place. Then it watches over the expression and adds/removes the actual HTML element as necessary.
+ //The problem seems to be that if it is placed on the root element of the template, then a single comment is what is left from the
+ //whole template (even if only temporarily), which gets ignored (I am not sure if this is browser-specific behaviour), resulting in an empty template.
+
+ // Remove ng-if attribute and its value (if we reach here, we pass ng-if (ng-if===true), so we can remove it).
+ $elem.removeAttr('ng-if');
+ $elem.removeAttr('data-ng-if');
+
+ // Remove me (the directive from the element)
+ let template = $elem[0].outerHTML;
+ template = template.replace('sdc-smart-tooltip=""','');
+ template = template.replace('sdc-smart-tooltip="' + $elem.text() + '"','');
+ //console.log(template);
+
+ let el = this.$compile(template)(scope);
+ console.log(el);
+ $elem.replaceWith(el);
+ }
+ });
+ };
+
+ public static factory = ($templateCache:ng.ITemplateCacheService, $compile:ng.ICompileService)=> {
+ return new SmartTooltipDirective($templateCache, $compile);
+ };
+
+ }
+
+ SmartTooltipDirective.factory.$inject = ['$templateCache', '$compile'];
+}
diff --git a/catalog-ui/app/scripts/directives/utils/wizard_steps/sdc-wizard-steps.html b/catalog-ui/app/scripts/directives/utils/wizard_steps/sdc-wizard-steps.html
new file mode 100644
index 0000000000..0c9b97a58c
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/utils/wizard_steps/sdc-wizard-steps.html
@@ -0,0 +1,16 @@
+<ul class="sdc-wizard-step">
+ <li class="step" data-ng-repeat="step in steps track by $index">
+ <div class="step-wrapper">
+ <button class="step-index"
+ data-ng-click="controllerStepClicked(step.name)"
+ data-ng-class="{'selected': step.selected===true, 'valid': step.valid===true, 'disabled': !step.enabled || step.enabled===false}">
+ {{$index+1}}
+ </button>
+ <span class="step-name"
+ data-ng-class="{'selected': step.selected===true, 'valid': step.valid===true, 'disabled': !step.enabled || step.enabled===false}">{{step.name}}
+ </span>
+ </div>
+ <div class="step-seperator"></div>
+ </li>
+</ul>
+
diff --git a/catalog-ui/app/scripts/directives/utils/wizard_steps/sdc-wizard-steps.less b/catalog-ui/app/scripts/directives/utils/wizard_steps/sdc-wizard-steps.less
new file mode 100644
index 0000000000..8b777923a0
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/utils/wizard_steps/sdc-wizard-steps.less
@@ -0,0 +1,69 @@
+@circle-radius: 18px;
+@gap: 70px;
+@gap-width: 2px;
+@valid-width: 2px;
+
+ul.sdc-wizard-step {
+ padding: 0;
+ margin: 0;
+
+ li.step {
+ position: relative;
+ list-style: none;
+
+ .step-wrapper {
+ line-height: @circle-radius*2;
+ height: @circle-radius*2;
+ margin-bottom: @gap;
+
+ button.step-index {
+ ._w-sdc-wizard-step-btn(@circle-radius);
+ z-index: 99;
+ display: inline-block;
+
+ &.valid {
+ display: inline-block;
+ }
+
+ }
+
+ span.step-name {
+ .b_7;
+ line-height: @circle-radius;
+ display: inline-block;
+ word-wrap: break-word;
+ width: calc(~"100%" - @circle-radius*2 + 4);
+ vertical-align: middle;
+ padding-left: 10px;
+ white-space: normal;
+
+ &.selected {
+ .a_7;
+ font-weight: bold;
+ }
+
+ &.disabled {
+ border: none;
+ background-color: transparent;
+ }
+
+ }
+ }
+
+ .step-seperator {
+ border-right: @gap-width solid @color_n;
+ height: @gap + @circle-radius*2;
+ position: absolute;
+ top: @circle-radius*2-@circle-radius;
+ left: @circle-radius - @gap-width/2;
+ }
+
+ }
+
+ li.step:last-child {
+ .step-seperator {
+ display: none;
+ }
+ }
+
+}
diff --git a/catalog-ui/app/scripts/directives/utils/wizard_steps/sdc-wizard-steps.ts b/catalog-ui/app/scripts/directives/utils/wizard_steps/sdc-wizard-steps.ts
new file mode 100644
index 0000000000..9cad36ab78
--- /dev/null
+++ b/catalog-ui/app/scripts/directives/utils/wizard_steps/sdc-wizard-steps.ts
@@ -0,0 +1,139 @@
+/*-
+ * ============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.Directives {
+
+ 'use strict';
+
+ export interface IWizardStep {
+ name: string;
+ selected?: boolean;
+ valid?:boolean;
+ enabled?:boolean;
+ callback: Function;
+ }
+
+ export interface ISdcWizardStepScope extends ng.IScope {
+ steps:Array<IWizardStep>;
+ control:any;
+ internalControl:any;
+
+ stepClicked(stepName:string):void;
+ controllerStepClicked(stepName:string):void;
+
+ setStepValidity(stepName:string, valid:boolean):void;
+ controllerSetStepValidity(step:IWizardStep, valid:boolean):void;
+ }
+
+ export interface SdcWizardStepMethods {
+ unSelectAllSteps():void;
+ selectStep(step:IWizardStep):void;
+ }
+
+ export class SdcWizardStepDirective implements ng.IDirective {
+
+ constructor(private $templateCache:ng.ITemplateCacheService) {
+ }
+
+ scope = {
+ steps: '=',
+ control: '='
+ };
+
+ public replace = false;
+ public restrict = 'E';
+ public transclude = true;
+ public controller = SdcWizardStepDirectiveController;
+
+ template = ():string => {
+ return this.$templateCache.get('/app/scripts/directives/utils/wizard_steps/sdc-wizard-steps.html');
+ };
+
+ link = (scope:ISdcWizardStepScope, $elem:JQuery, attr:any, controller:SdcWizardStepDirectiveController) => {
+ scope.internalControl = scope.control || {};
+ scope.internalControl.stepClicked = (step:string):void => {
+ scope.controllerStepClicked(step);
+ };
+
+ scope.internalControl.setStepValidity = (step:IWizardStep, valid:boolean):void => {
+ scope.controllerSetStepValidity(step, valid);
+ };
+ }
+
+ public static factory = ($templateCache:ng.ITemplateCacheService)=> {
+ return new SdcWizardStepDirective($templateCache);
+ };
+
+ }
+
+ SdcWizardStepDirective.factory.$inject = ['$templateCache'];
+
+ export class SdcWizardStepDirectiveController {
+ static $inject = ['$element', '$scope'];
+
+ methods:SdcWizardStepMethods = <SdcWizardStepMethods>{};
+
+ constructor(public $element: JQuery,
+ public $scope: ISdcWizardStepScope) {
+
+ this.initMethods();
+ this.initScope();
+ }
+
+ private initScope = ():void => {
+
+ this.$scope.controllerStepClicked = (stepName:string):void => {
+ let selectedStep:IWizardStep = <IWizardStep>_.find(this.$scope.steps, function (item) {
+ return item.name === stepName;
+ });
+
+ if (selectedStep && selectedStep.enabled===true){
+ let result:boolean = selectedStep.callback();
+ if (result===true){
+ this.methods.unSelectAllSteps();
+ this.methods.selectStep(selectedStep);
+ }
+ }
+ };
+
+ this.$scope.controllerSetStepValidity = (step:IWizardStep, valid:boolean):void => {
+ step.valid=valid;
+ };
+
+ };
+
+ private initMethods = ():void => {
+
+ this.methods.unSelectAllSteps = ():void => {
+ this.$scope.steps.forEach(function (step) {
+ step.selected = false;
+ });
+ }
+
+ this.methods.selectStep = (step:IWizardStep):void => {
+ if (step.enabled===true){
+ step.selected=true;
+ }
+ }
+ };
+
+ }
+
+}
diff --git a/catalog-ui/app/scripts/filters/_category-name-filter.ts b/catalog-ui/app/scripts/filters/_category-name-filter.ts
new file mode 100644
index 0000000000..77bbf47684
--- /dev/null
+++ b/catalog-ui/app/scripts/filters/_category-name-filter.ts
@@ -0,0 +1,40 @@
+/*-
+ * ============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.Filters {
+
+ export class CategoryNameFilter{
+
+ constructor() {
+ let filter = <CategoryNameFilter>( (name:string) => {
+ if(name){
+ let newName:string = _.last(name.split('/'));
+ if (newName){
+ return newName;
+ }
+ return name;
+ }
+ });
+
+ return filter;
+ }
+ }
+
+}
diff --git a/catalog-ui/app/scripts/filters/capitalize-filter.ts b/catalog-ui/app/scripts/filters/capitalize-filter.ts
new file mode 100644
index 0000000000..ef0469aaa1
--- /dev/null
+++ b/catalog-ui/app/scripts/filters/capitalize-filter.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=========================================================
+ */
+module Sdc.Filters {
+
+ export class CapitalizeFilter{
+
+ constructor() {
+ let filter = <CapitalizeFilter>( (sentence:string) => {
+ if (sentence != null) {
+ let newSentence:string = "";
+ let words = sentence.split(' ');
+ for (let i=0; i < words.length; ++i){
+ let word:string = words[i].toLowerCase();
+ newSentence += word.substring(0,1).toUpperCase()+word.substring(1) + ' ';
+ }
+ return newSentence.trim();
+ }else{
+ return sentence;
+ }
+ });
+
+ return filter;
+ }
+ }
+
+}
diff --git a/catalog-ui/app/scripts/filters/catalog-status-filter.ts b/catalog-ui/app/scripts/filters/catalog-status-filter.ts
new file mode 100644
index 0000000000..5b382f6513
--- /dev/null
+++ b/catalog-ui/app/scripts/filters/catalog-status-filter.ts
@@ -0,0 +1,41 @@
+/*-
+ * ============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"/>
+/**
+ * Created by obarda on 19/08/2015.
+ */
+module Sdc.Filters {
+
+ export class CatalogStatusFilter{
+
+ constructor() {
+ let filter = <CatalogStatusFilter>( (statuses:any) => {
+ let filtered = [];
+ angular.forEach(statuses, function(status) {
+ filtered.push(status);
+ });
+ return filtered;
+ });
+
+ return filter;
+ }
+ }
+
+}
diff --git a/catalog-ui/app/scripts/filters/category-icon-filter.ts b/catalog-ui/app/scripts/filters/category-icon-filter.ts
new file mode 100644
index 0000000000..6916a13399
--- /dev/null
+++ b/catalog-ui/app/scripts/filters/category-icon-filter.ts
@@ -0,0 +1,54 @@
+/*-
+ * ============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.Filters {
+
+ export class CategoryIconFilter{
+
+ constructor() {
+ let filter = <CategoryIconFilter>( (category:string) => {
+ let map = {
+ 'Application Layer 4+/Application Servers': ['applicationServer', 'server'],
+ 'Application Layer 4+/Media Servers': ['applicationServer', 'server'],
+ 'Application Layer 4+/Web Server': ['applicationServer', 'server'],
+ 'Network Layer 4+/Common Network Resources': ['network', 'loadBalancer'],
+ 'Generic/Infrastructure': ['objectStorage', 'compute'],
+ 'Generic/Network Elements': ['port', 'network', 'router'],
+ 'Application Layer 4+/Database': ['database'],
+ 'Generic/Database': ['database'],
+ 'Network Layer 2-3/Router': ['router'],
+ 'Network Layer 2-3/Gateway': ['gateway'],
+ 'Network Layer 2-3/LAN Connectors': ['connector'],
+ 'Network Layer 2-3/WAN Connectors': ['connector'],
+ 'Application Layer 4+/Border Elements': ['borderElement'],
+ 'Application Layer 4+/Load Balancer': ['loadBalancer'],
+ 'Application Layer 4+/Call Control': ['call_controll'],
+ 'VoIP Call Control': ['call_controll'],
+ 'Mobility': ['mobility'],
+ 'Network L1-3': ['network_l_1-3'],
+ 'Network L4': ['network_l_4']
+ }
+ return map[category];
+
+ });
+ return filter;
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/filters/category-type-filter.ts b/catalog-ui/app/scripts/filters/category-type-filter.ts
new file mode 100644
index 0000000000..482e566e5a
--- /dev/null
+++ b/catalog-ui/app/scripts/filters/category-type-filter.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.Filters {
+
+ export class CategoryTypeFilter {
+
+ static $inject = ['Sdc.Services.CacheService'];
+
+ constructor(cacheService:Services.CacheService) {
+ let filter = <CategoryTypeFilter> (categories:any, selectedType:Array<string>) => {
+
+ if (!selectedType.length)
+ return categories;
+
+ let filteredCategories:any = [];
+ selectedType.forEach((type:string) => {
+ filteredCategories = filteredCategories.concat(cacheService.get(type.toLowerCase() + 'Categories'));
+ });
+
+ return _.filter(categories, function (category:any) {
+ return filteredCategories.indexOf(category) != -1;
+ });
+ };
+ return filter;
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/filters/clear-whitespaces-filter.ts b/catalog-ui/app/scripts/filters/clear-whitespaces-filter.ts
new file mode 100644
index 0000000000..5c946e1715
--- /dev/null
+++ b/catalog-ui/app/scripts/filters/clear-whitespaces-filter.ts
@@ -0,0 +1,39 @@
+/*-
+ * ============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.Filters {
+
+ export class ClearWhiteSpacesFilter {
+
+ constructor() {
+ let filter = <ClearWhiteSpacesFilter>( (text:string) => {
+ if (!angular.isString(text)) {
+ return text;
+ }
+
+ return text.replace(/ /g,''); // remove also whitespaces inside
+ });
+
+ return filter;
+ }
+ }
+}
+
+
diff --git a/catalog-ui/app/scripts/filters/entity-filter.ts b/catalog-ui/app/scripts/filters/entity-filter.ts
new file mode 100644
index 0000000000..ce60d69833
--- /dev/null
+++ b/catalog-ui/app/scripts/filters/entity-filter.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.Filters {
+
+ export class EntityFilter{
+
+ constructor() {
+
+ let filter = <EntityFilter>( (components:Array<Models.Components.Component>, filter:any) => {
+
+ let filteredComponents:Array<Models.Components.Component> = components;
+
+ // filter by type
+ // --------------------------------------------------------------------------
+ if ((filter.selectedComponentTypes && filter.selectedComponentTypes.length>0) || (filter.selectedResourceSubTypes && filter.selectedResourceSubTypes.length>0)) {
+ let filteredTypes = [];
+ angular.forEach(components, (component:Models.Components.Component):void => {
+ // Filter by component type
+ let typeLower:string = component.componentType.toLowerCase();
+ let typeFirstCapital:string = typeLower.charAt(0).toUpperCase() + typeLower.slice(1);
+ if (filter.selectedComponentTypes.indexOf(typeFirstCapital) !== -1) {
+ filteredTypes.push(component);
+ }
+
+ // Filter by resource sub type, only in case the resource checkbox was not selected (because in this case we already added all the components in above section).
+ if (component.isResource() && filter.selectedComponentTypes.indexOf("Resource") === -1 && filter.selectedResourceSubTypes.length > 0) {
+ //filteredComponents.pop(); // Remove the last inserted component.
+ let resource:Sdc.Models.Components.Resource = <Sdc.Models.Components.Resource>component;
+ if (filter.selectedResourceSubTypes.indexOf(resource.getComponentSubType()) !== -1) {
+ filteredTypes.push(component);
+ }
+ }
+ });
+ filteredComponents = filteredTypes;
+ }
+
+ // filter by categories & subcategories & groupings
+ // --------------------------------------------------------------------------
+ if (filter.selectedCategoriesModel && filter.selectedCategoriesModel.length>0) {
+ let filteredCategories = [];
+ angular.forEach(filteredComponents, (component:Models.Components.Component):void => {
+ if (component.categories && filter.selectedCategoriesModel.indexOf(component.categories[0].uniqueId) !== -1) {
+ filteredCategories.push(component);
+ } else if (component.categories && component.categories[0].subcategories && filter.selectedCategoriesModel.indexOf(component.categories[0].subcategories[0].uniqueId) !== -1) {
+ filteredCategories.push(component);
+ } else if (component.categories && component.categories[0].subcategories && component.categories[0].subcategories[0].groupings && filter.selectedCategoriesModel.indexOf(component.categories[0].subcategories[0].groupings[0].uniqueId) !== -1) {
+ filteredCategories.push(component);
+ }
+ });
+ filteredComponents = filteredCategories;
+ }
+
+ // filter by statuses
+ // --------------------------------------------------------------------------
+ if (filter.selectedStatuses && filter.selectedStatuses.length > 0) {
+ //convert array of array to string array
+ let selectedStatuses:Array<string> = [].concat.apply([],filter.selectedStatuses);
+
+ let filteredStatuses = [];
+ angular.forEach(filteredComponents, (component:Models.Components.Component):void => {
+ if (selectedStatuses.indexOf(component.lifecycleState) > -1) {
+ filteredStatuses.push(component);
+ }
+ //if status DISTRIBUTED && CERTIFIED are selected the component will added in CERTIFIED status , not need to add twice
+ if(selectedStatuses.indexOf('DISTRIBUTED') > -1 && !(selectedStatuses.indexOf('CERTIFIED') > -1)){
+ if( component.distributionStatus && component.distributionStatus.indexOf('DISTRIBUTED') > -1 && component.lifecycleState.indexOf('CERTIFIED') > -1){
+ filteredStatuses.push(component);
+ }
+ }
+ });
+ filteredComponents = filteredStatuses;
+ }
+
+ // filter by statuses and distributed
+ // --------------------------------------------------------------------------
+ if (filter.distributed != undefined && filter.distributed.length > 0) {
+ let filterDistributed: Array<any> = filter.distributed;
+ let filteredDistributed = [];
+ angular.forEach(filteredComponents, (entity) => {
+ filterDistributed.forEach((distribute) => {
+ let distributeItem = distribute.split(',');
+ distributeItem.forEach((item) => {
+ if (item !== undefined && entity.distributionStatus === item){
+ filteredDistributed.push(entity);
+ }
+ })
+ });
+ });
+ filteredComponents = filteredDistributed;
+ }
+
+ return filteredComponents;
+ });
+
+ return filter;
+ }
+ }
+
+}
diff --git a/catalog-ui/app/scripts/filters/graph-resource-name-filter.ts b/catalog-ui/app/scripts/filters/graph-resource-name-filter.ts
new file mode 100644
index 0000000000..63f0d780be
--- /dev/null
+++ b/catalog-ui/app/scripts/filters/graph-resource-name-filter.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=========================================================
+ */
+/// <reference path="../references"/>
+module Sdc.Filters {
+
+ export class GraphResourceNameFilter {
+
+
+ constructor() {
+ let filter = <GraphResourceNameFilter>( (name:string) => {
+ let context = document.createElement("canvas").getContext("2d");
+ context.font = "13px Arial";
+
+ if(67 < context.measureText(name).width) {
+ let newLen = name.length - 3;
+ let newName = name.substring(0, newLen);
+
+ while (59 < (context.measureText(newName).width)) {
+ newName = newName.substring(0, (--newLen));
+ }
+ return newName + '...';
+ }
+
+ return name;
+ });
+ return filter;
+ }
+ }
+
+}
diff --git a/catalog-ui/app/scripts/filters/product-category-name-filter.ts b/catalog-ui/app/scripts/filters/product-category-name-filter.ts
new file mode 100644
index 0000000000..afe8c7ef08
--- /dev/null
+++ b/catalog-ui/app/scripts/filters/product-category-name-filter.ts
@@ -0,0 +1,39 @@
+/*-
+ * ============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.Filters {
+
+ export class ProductCategoryNameFilter{
+
+ constructor() {
+ let filter = <CategoryNameFilter>( (name:string) => {
+ if(name){
+ let newName:string = name.split('/')[1];
+ if (newName){
+ return newName;
+ }
+ return name;
+ }
+ });
+
+ return filter;
+ }
+ }
+
+}
diff --git a/catalog-ui/app/scripts/filters/product-subcategory-name-filter.ts b/catalog-ui/app/scripts/filters/product-subcategory-name-filter.ts
new file mode 100644
index 0000000000..66d7a76c28
--- /dev/null
+++ b/catalog-ui/app/scripts/filters/product-subcategory-name-filter.ts
@@ -0,0 +1,39 @@
+/*-
+ * ============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.Filters {
+
+ export class ProductSubCategoryNameFilter{
+
+ constructor() {
+ let filter = <CategoryNameFilter>( (name:string) => {
+ if(name){
+ let newName:string = _.last(name.split('/'));
+ if (newName){
+ return newName;
+ }
+ return name;
+ }
+ });
+
+ return filter;
+ }
+ }
+
+}
diff --git a/catalog-ui/app/scripts/filters/relation-name-fllter.ts b/catalog-ui/app/scripts/filters/relation-name-fllter.ts
new file mode 100644
index 0000000000..7d97eea372
--- /dev/null
+++ b/catalog-ui/app/scripts/filters/relation-name-fllter.ts
@@ -0,0 +1,53 @@
+/*-
+ * ============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.Filters {
+
+ export class RelationNameFilter{
+
+ constructor() {
+ let filter = <RelationNameFilter>( (relationshipType:string) => {
+ let icons: Array<string> = [
+ 'AttachesTo',
+ 'BindsTo',
+ 'DependsOn',
+ 'HostedOn',
+ 'LinksTo',
+ 'RoutesTo'
+ ];
+
+ let result:string = 'ConnectedTo';
+
+ if (relationshipType) {
+ let arr = relationshipType.split('.'); // looks like tosca.relationships.AttachesTo
+ relationshipType = arr[arr.length - 1];
+ if (icons.indexOf(relationshipType) > -1) {
+ result = relationshipType;
+ }
+ }
+
+ return result;
+ });
+
+ return filter;
+ }
+ }
+
+}
diff --git a/catalog-ui/app/scripts/filters/resource-name-filter.ts b/catalog-ui/app/scripts/filters/resource-name-filter.ts
new file mode 100644
index 0000000000..a1f6162a4c
--- /dev/null
+++ b/catalog-ui/app/scripts/filters/resource-name-filter.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.Filters {
+
+ export class ResourceNameFilter{
+
+
+ constructor() {
+ let filter = <ResourceNameFilter>( (name:string) => {
+ if(name){
+ //let newName:string = _.last(name.split('.'));
+ let newName =
+ _.last(_.last(_.last(_.last(_.last(_.last(_.last(_.last(name.split('tosca.nodes.'))
+ .split('network.')).split('relationships.')).split('org.openecomp.')).split('resource.nfv.'))
+ .split('nodes.module.')).split('cp.')).split('vl.'));
+ if (newName){
+ return newName;
+ }
+ return name;
+ }
+ });
+
+ return filter;
+ }
+ }
+
+}
diff --git a/catalog-ui/app/scripts/filters/resource-type-filter.ts b/catalog-ui/app/scripts/filters/resource-type-filter.ts
new file mode 100644
index 0000000000..6aa79dae76
--- /dev/null
+++ b/catalog-ui/app/scripts/filters/resource-type-filter.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.Filters {
+
+ export class ResourceTypeFilter{
+ static '$inject' = ['Sdc.Services.CacheService'];
+ constructor(cacheService:Services.CacheService)
+ {
+ let filter = <ResourceTypeFilter> (resourceType:string) => {
+ let uiConfiguration:any = cacheService.get('UIConfiguration');
+
+ if(uiConfiguration.resourceTypes && uiConfiguration.resourceTypes[resourceType]){
+ return uiConfiguration.resourceTypes[resourceType];
+ }
+ return resourceType;
+ }
+ return filter;
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/filters/string-to-date-filter.ts b/catalog-ui/app/scripts/filters/string-to-date-filter.ts
new file mode 100644
index 0000000000..1c4919d419
--- /dev/null
+++ b/catalog-ui/app/scripts/filters/string-to-date-filter.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.Filters {
+
+ export class StringToDateFilter{
+
+ constructor() {
+ let filter = <StringToDateFilter>( (date:string) => {
+ if(date){
+ return new Date(date.replace(" UTC", '').replace(" ", 'T') + '+00:00');
+ }
+ });
+ return filter;
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/filters/tests-id-filter.ts b/catalog-ui/app/scripts/filters/tests-id-filter.ts
new file mode 100644
index 0000000000..12c5e6fd79
--- /dev/null
+++ b/catalog-ui/app/scripts/filters/tests-id-filter.ts
@@ -0,0 +1,33 @@
+/*-
+ * ============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.Filters {
+
+ export class TestsIdFilter{
+
+ constructor() {
+ let filter = <TestsIdFilter>( (testId:string) => {
+ return testId.replace(/\s/g, '_').toLowerCase();
+ });
+
+ return filter;
+ }
+ }
+
+}
diff --git a/catalog-ui/app/scripts/filters/trim-filter.ts b/catalog-ui/app/scripts/filters/trim-filter.ts
new file mode 100644
index 0000000000..fd231abc8d
--- /dev/null
+++ b/catalog-ui/app/scripts/filters/trim-filter.ts
@@ -0,0 +1,39 @@
+/*-
+ * ============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.Filters {
+
+ export class TrimFilter {
+
+ constructor() {
+ let filter = <TrimFilter>( (text:string) => {
+ if (!angular.isString(text)) {
+ return text;
+ }
+
+ return text.replace(/^\s+|\s+$/g, ''); // you could use .trim, but it's not going to work in IE<9
+ });
+
+ return filter;
+ }
+ }
+}
+
+
diff --git a/catalog-ui/app/scripts/filters/truncate-filter.ts b/catalog-ui/app/scripts/filters/truncate-filter.ts
new file mode 100644
index 0000000000..1470e5937d
--- /dev/null
+++ b/catalog-ui/app/scripts/filters/truncate-filter.ts
@@ -0,0 +1,48 @@
+/*-
+ * ============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.Filters {
+
+ export class TruncateFilter {
+ constructor() {
+ let filter = <TruncateFilter> (str:string, length:number) => {
+ if (str.length <= length) {
+ return str;
+ }
+
+ //if(str[length - 1] === ' '){
+ // return str.substring(0, length - 1) + '...';
+ //}
+
+ let char;
+ let index = length;
+ while (char !== ' ' && index !== 0) {
+ index--;
+ char = str[index];
+ }
+ if (index === 0) {
+ return (index === 0) ? str : str.substring(0, length - 3) + '...';
+ }
+ return (index === 0) ? str : str.substring(0, index) + '...';
+ };
+ return filter;
+ }
+
+ }
+}
diff --git a/catalog-ui/app/scripts/filters/underscoreless-filter.ts b/catalog-ui/app/scripts/filters/underscoreless-filter.ts
new file mode 100644
index 0000000000..6849a36f04
--- /dev/null
+++ b/catalog-ui/app/scripts/filters/underscoreless-filter.ts
@@ -0,0 +1,33 @@
+/*-
+ * ============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.Filters {
+
+ export class UnderscoreLessFilter{
+
+ constructor() {
+ let filter = <UnderscoreLessFilter>( (sentence:string) => {
+ return sentence.replace(/_/g, ' ');
+ });
+
+ return filter;
+ }
+ }
+
+}
diff --git a/catalog-ui/app/scripts/models/activity.ts b/catalog-ui/app/scripts/models/activity.ts
new file mode 100644
index 0000000000..4f8648d6b7
--- /dev/null
+++ b/catalog-ui/app/scripts/models/activity.ts
@@ -0,0 +1,48 @@
+/*-
+ * ============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 19/11/2015.
+ */
+/// <reference path="../references"/>
+module Sdc.Models {
+ 'use strict';
+
+ /*this is in uppercase because of the server response*/
+ export class Activity{
+ TIMESTAMP: string;
+ ACTION:string;
+ MODIFIER:string;
+ STATUS:string;
+ DESC:string;
+ COMMENT:string;
+ //custom data
+ public dateFormat:string;
+
+ constructor() {
+ }
+ public toJSON = ():any => {
+ this.dateFormat = undefined;
+ return this;
+ };
+
+ }
+}
+
+
diff --git a/catalog-ui/app/scripts/models/additional-information.ts b/catalog-ui/app/scripts/models/additional-information.ts
new file mode 100644
index 0000000000..bcac2e5d12
--- /dev/null
+++ b/catalog-ui/app/scripts/models/additional-information.ts
@@ -0,0 +1,44 @@
+/*-
+ * ============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.Models {
+ 'use strict';
+
+
+ export interface IAdditionalInformationModel {
+ uniqueId: string;
+ key: string;
+ value: string;
+ }
+
+
+ export class AdditionalInformationModel implements IAdditionalInformationModel {
+ uniqueId:string;
+ key:string;
+ value:string;
+
+ constructor() {
+ this.uniqueId = '';
+ this.key = '';
+ this.value = '';
+
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/models/app-config.ts b/catalog-ui/app/scripts/models/app-config.ts
new file mode 100644
index 0000000000..f0a316fc92
--- /dev/null
+++ b/catalog-ui/app/scripts/models/app-config.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.Models {
+
+ 'use strict';
+ export interface IApi {
+ baseUrl:string;
+
+ //***** NEW API *******//
+ GET_component: string;
+ PUT_component: string;
+ GET_component_validate_name: string;
+ POST_changeLifecycleState: string;
+ component_api_root:string;
+ welcome_page_video_url:string;
+ //*********//
+
+ GET_user: string;
+ GET_user_authorize: string;
+ GET_all_users: string;
+ POST_create_user;
+ DELETE_delete_user;
+ POST_edit_user_role;
+ GET_resource: string;
+ GET_resources_latestversion_notabstract:string;
+ GET_resources_certified_not_abstract: string;
+ GET_resources_certified_abstract: string;
+ PUT_resource: string;
+ GET_resource_property: string;
+ GET_resource_artifact:string;
+ GET_download_instance_artifact:string;
+ POST_instance_artifact:string;
+ GET_resource_additional_information:string;
+ GET_service_artifact:string;
+ GET_resource_interface_artifact:string;
+ GET_resource_api_artifact:string;
+ GET_resource_validate_name: string;
+ GET_resource_artifact_types: string;
+ GET_activity_log: string;
+ GET_configuration_ui: string;
+ GET_service: string;
+ PUT_product: string;
+ GET_product: string;
+ GET_ecomp_menu_items: string;
+ GET_product_validate_name: string;
+ GET_service_validate_name: string;
+ GET_service_distributions: string;
+ GET_service_distributions_components: string;
+ POST_service_distribution_deploy: string;
+ GET_element: string;
+ GET_catalog: string;
+ GET_resource_category: string;
+ GET_service_category: string;
+ resource_instance: string;
+ GET_resource_instance_property: string;
+ GET_relationship:string;
+ GET_lifecycle_state_resource:string;
+ GET_lifecycle_state_CHECKIN:string;
+ GET_lifecycle_state_CERTIFICATIONREQUEST:string;
+ GET_lifecycle_state_UNDOCHECKOUT:string;
+ root: string;
+ PUT_service: string;
+ GET_download_artifact: string;
+ GET_SDC_Version: string;
+ GET_categories: string;
+ POST_category: string;
+ POST_subcategory: string;
+ POST_change_instance_version: string;
+ GET_requirements_capabilities: string;
+ GET_onboarding: string;
+ GET_component_from_csar_uuid: string;
+ kibana:string;
+
+ //Added by Ikram -- starts
+ GET_product_category: string;
+ GET_product_category_temp: string;
+ GET_product_sub_category: string;
+ //Added by Ikram -- ends
+
+ }
+
+ export interface ILogConfig {
+ minLogLevel: string;
+ prefix: string;
+ }
+
+ export interface ICookie {
+ junctionName: string;
+ prefix: string;
+ userIdSuffix: string;
+ userFirstName: string;
+ userLastName: string;
+ userEmail: string;
+ }
+ export interface IUserTypes {
+ admin: any;
+ designer: any;
+ tester: any;
+ }
+
+ export interface IConfigStatuses {
+ inDesign: IConfigStatus;
+ readyForCertification: IConfigStatus;
+ inCertification: IConfigStatus;
+ certified: IConfigStatus;
+ distributed: IConfigStatus;
+
+ }
+
+ export interface IConfigStatus {
+ name: string;
+ values: Array<string>;
+ }
+
+ export interface IConfigRoles {
+ ADMIN: IConfigRole;
+ DESIGNER: IConfigRole;
+ TESTER: IConfigRole;
+ OPS: IConfigRole;
+ GOVERNOR: IConfigRole;
+ PRODUCT_MANAGER: IConfigRole;
+ PRODUCT_STRATEGIST: IConfigRole;
+ }
+
+ export interface IConfigRole {
+ pages: Array<string>;
+ states: IConfigState;
+ }
+
+ export interface IConfigState {
+ NOT_CERTIFIED_CHECKOUT: Array<IConfigDistribution>;
+ NOT_CERTIFIED_CHECKIN: Array<IConfigDistribution>;
+ READY_FOR_CERTIFICATION: Array<IConfigDistribution>;
+ CERTIFICATION_IN_PROGRESS: Array<IConfigDistribution>;
+ CERTIFIED: Array<IConfigDistribution>;
+ }
+
+ export interface IConfigDistribution {
+ DISTRIBUTION_NOT_APPROVED: Array<ConfigMenuItem>;
+ DISTRIBUTION_APPROVED: Array<ConfigMenuItem>;
+ DISTRIBUTED: Array<ConfigMenuItem>;
+ DISTRIBUTION_REJECTED: Array<ConfigMenuItem>;
+ }
+
+ export interface IConfirmationMessage {
+ showComment: boolean;
+ title: string;
+ message: string;
+ }
+
+ export interface IConfirmationMessages {
+ checkin: IConfirmationMessage;
+ checkout: IConfirmationMessage;
+ certify: IConfirmationMessage;
+ failCertification: IConfirmationMessage;
+ certificationRequest: IConfirmationMessage;
+ approve: IConfirmationMessage;
+ reject: IConfirmationMessage;
+ }
+
+ export interface IAlertMessage {
+ title: string;
+ message: string;
+ }
+
+ export interface IAlertMessages {
+ deleteInstance: IAlertMessage;
+ exitWithoutSaving: IConfirmationMessage;
+ }
+
+ class ConfigMenuItem {
+ text:string;
+ action:string;
+ url:string;
+ disable:boolean = false;
+ }
+
+ export interface IAppConfigurtaion {
+ environment:string;
+ api: IApi;
+ resourceTypesFilter:IResourceTypesFilter;
+ logConfig: ILogConfig;
+ cookie: ICookie;
+ imagesPath: string;
+ toscaFileExtension:string;
+ csarFileExtension:string;
+ testers: Array<ITester>
+ tutorial:any;
+ roles: Array<string>;
+ cpEndPointInstances: Array<string>;
+ openSource:boolean;
+ }
+ export interface IResourceTypesFilter {
+ resource: Array<string>;
+ }
+
+ export interface ITester {
+ email: string;
+ }
+
+ export interface IAppMenu {
+ roles: IConfigRoles;
+ confirmationMessages: IConfirmationMessages;
+ alertMessages: IAlertMessages;
+ statuses: IConfigStatuses;
+ catalogMenuItem: any;
+ categoriesDictionary:any;
+ canvas_buttons:Object;
+ component_workspace_menu_option: any;
+ LifeCycleStatuses: any;
+ DistributionStatuses: any;
+ ChangeLifecycleStateButton:any;
+ }
+}
diff --git a/catalog-ui/app/scripts/models/artifacts.ts b/catalog-ui/app/scripts/models/artifacts.ts
new file mode 100644
index 0000000000..8ee98d90d1
--- /dev/null
+++ b/catalog-ui/app/scripts/models/artifacts.ts
@@ -0,0 +1,115 @@
+/*-
+ * ============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.Models {
+ 'use strict';
+
+ //this object contains keys, each key contain ArtifactModel
+ export class ArtifactGroupModel{
+ constructor(artifacts?:Models.ArtifactGroupModel) {
+ _.forEach(artifacts, (artifact:Models.ArtifactModel, key) => {
+ this[key] = new Models.ArtifactModel(artifact);
+ });
+ }
+
+ public filteredByType (type:string): Models.ArtifactGroupModel {
+ return JSON.parse(JSON.stringify(_.pick(this, (artifact)=>{ return artifact.artifactType == type})));
+ };
+ }
+
+ export class ArtifactModel {
+
+ artifactDisplayName:string;
+ artifactGroupType:string;
+ uniqueId:string;
+ artifactName:string;
+ artifactLabel:string;
+ artifactType:string;
+ artifactUUID:string;
+ artifactVersion:string;
+ creatorFullName:string;
+ creationDate:number;
+ lastUpdateDate:number;
+ description:string;
+ mandatory:boolean;
+ serviceApi:boolean;
+ payloadData:string;
+ timeout:number;
+ esId:string;
+ "Content-MD5":string;
+ artifactChecksum:string;
+ apiUrl:string;
+ heatParameters:Array<any>;
+ generatedFromId:string;
+
+ //custom properties
+ selected:boolean;
+ originalDescription:string;
+
+ constructor(artifact?:ArtifactModel) {
+ if(artifact) {
+ this.artifactDisplayName = artifact.artifactDisplayName;
+ this.artifactGroupType = artifact.artifactGroupType;
+ this.uniqueId = artifact.uniqueId;
+ this.artifactName = artifact.artifactName;
+ this.artifactLabel = artifact.artifactLabel;
+ this.artifactType = artifact.artifactType;
+ this.artifactUUID = artifact.artifactUUID;
+ this.artifactVersion = artifact.artifactVersion;
+ this.creatorFullName = artifact.creatorFullName;
+ this.creationDate = artifact.creationDate;
+ this.lastUpdateDate = artifact.lastUpdateDate;
+ this.description = artifact.description;
+ this.mandatory = artifact.mandatory;
+ this.serviceApi = artifact.serviceApi;
+ this.payloadData = artifact.payloadData;
+ this.timeout = artifact.timeout;
+ this.esId = artifact.esId;
+ this["Content-MD5"] = artifact["Content-MD5"];
+ this.artifactChecksum = artifact.artifactChecksum;
+ this.apiUrl = artifact.apiUrl;
+ this.heatParameters = _.sortBy(artifact.heatParameters, 'name');
+ this.generatedFromId = artifact.generatedFromId;
+ this.selected = artifact.selected ? artifact.selected : false;
+ this.originalDescription = artifact.description;
+ }
+ }
+
+ public isHEAT = ():boolean => {
+ return Utils.Constants.ArtifactType.HEAT === this.artifactType.substring(0,4);
+ };
+
+ // public isEditableInInstanceLevel = ():boolean => {
+ // return true;
+ // };
+
+ public isThirdParty = ():boolean => {
+ return _.has(Utils.Constants.ArtifactType.THIRD_PARTY_RESERVED_TYPES, this.artifactType);
+ };
+
+ public toJSON = ():any => {
+ this.selected = undefined;
+ this.originalDescription = undefined;
+ return this;
+ };
+ }
+}
+
+
diff --git a/catalog-ui/app/scripts/models/aschema-property.ts b/catalog-ui/app/scripts/models/aschema-property.ts
new file mode 100644
index 0000000000..7ecc85c302
--- /dev/null
+++ b/catalog-ui/app/scripts/models/aschema-property.ts
@@ -0,0 +1,63 @@
+/*-
+ * ============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 osonsino on 16/05/2016.
+ */
+/// <reference path="../references"/>
+module Sdc.Models {
+ 'use strict';
+
+ export class SchemaPropertyGroupModel{
+ property: SchemaProperty;
+
+ constructor(schemaProperty?:Models.SchemaProperty) {
+ this.property = schemaProperty;
+ }
+ }
+
+ export class SchemaProperty {
+
+ type: string;
+ required: boolean;
+ definition: boolean;
+ description: string;
+ password: boolean;
+ //custom properties
+ simpleType: string;
+
+ constructor(schemaProperty?:SchemaProperty) {
+ if(schemaProperty) {
+ this.type = schemaProperty.type;
+ this.required = schemaProperty.required;
+ this.definition = schemaProperty.definition;
+ this.description = schemaProperty.description;
+ this.password = schemaProperty.password;
+ this.simpleType = schemaProperty.simpleType;
+ }
+ }
+
+ public toJSON = ():any => {
+ this.simpleType = undefined;
+ return this;
+ };
+ }
+}
+
+
diff --git a/catalog-ui/app/scripts/models/attributes.ts b/catalog-ui/app/scripts/models/attributes.ts
new file mode 100644
index 0000000000..ea4c7a5a23
--- /dev/null
+++ b/catalog-ui/app/scripts/models/attributes.ts
@@ -0,0 +1,139 @@
+/*-
+ * ============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.Models {
+ 'use strict';
+
+ export class AttributesGroup {
+ constructor(attributesObj?:Models.AttributesGroup) {
+ _.forEach(attributesObj, (attributes:Array<Models.AttributeModel>, instance) => {
+ this[instance] = [];
+ _.forEach(attributes, (attribute:Models.AttributeModel):void => {
+ attribute.resourceInstanceUniqueId = instance;
+ attribute.readonly = true;
+ this[instance].push(new Models.AttributeModel(attribute));
+ });
+ });
+ }
+ }
+
+ export interface IAttributeModel {
+
+ //server data
+ uniqueId:string;
+ name:string;
+ defaultValue:string;
+ description:string;
+ type:string;
+ schema:Models.SchemaAttributeGroupModel;
+ status:string;
+ value:string;
+ hidden:boolean;
+ parentUniqueId:string;
+ //custom data
+ resourceInstanceUniqueId:string;
+ readonly:boolean;
+ valueUniqueUid:string;
+ }
+
+ export class AttributeModel implements IAttributeModel {
+
+ //server data
+ uniqueId:string;
+ name:string;
+ defaultValue:string;
+ description:string;
+ type:string;
+ schema:Models.SchemaAttributeGroupModel;
+ status:string;
+ value:string;
+ hidden:boolean;
+ parentUniqueId:string;
+ //custom data
+ resourceInstanceUniqueId:string;
+ readonly:boolean;
+ valueUniqueUid:string;
+
+ constructor(attribute?:Models.AttributeModel) {
+ if (attribute) {
+ this.uniqueId = attribute.uniqueId;
+ this.name = attribute.name;
+ this.defaultValue = attribute.defaultValue;
+ this.description = attribute.description;
+ this.type = attribute.type;
+ this.status = attribute.status;
+ this.schema = attribute.schema;
+ this.value = attribute.value;
+ this.hidden = attribute.hidden;
+ this.parentUniqueId = attribute.parentUniqueId;
+ this.resourceInstanceUniqueId = attribute.resourceInstanceUniqueId;
+ this.readonly = attribute.readonly;
+ this.valueUniqueUid = attribute.valueUniqueUid;
+ }
+
+ if (!this.schema || !this.schema.property) {
+ this.schema = new Models.SchemaPropertyGroupModel(new Models.SchemaProperty());
+ } else {
+ //forcing creating new object, so editing different one than the object in the table
+ this.schema = new Models.SchemaAttributeGroupModel(new Models.SchemaAttribute(this.schema.property));
+ }
+
+ this.convertValueToView();
+ }
+
+ public convertToServerObject:Function = ():string => {
+ if (this.defaultValue && this.type === 'map') {
+ this.defaultValue = '{' + this.defaultValue + '}';
+ }
+ if (this.defaultValue && this.type === 'list') {
+ this.defaultValue = '[' + this.defaultValue + ']';
+ }
+ this.defaultValue = this.defaultValue != "" && this.defaultValue != "[]" && this.defaultValue != "{}" ? this.defaultValue : null;
+
+ return JSON.stringify(this);
+ };
+
+
+ public convertValueToView() {
+ //unwrapping value {} or [] if type is complex
+ if (this.defaultValue && (this.type === 'map' || this.type === 'list') &&
+ ['[', '{'].indexOf(this.defaultValue.charAt(0)) > -1 &&
+ [']', '}'].indexOf(this.defaultValue.slice(-1)) > -1) {
+ this.defaultValue = this.defaultValue.slice(1, -1);
+ }
+
+ //also for value - for the modal in canvas
+ if (this.value && (this.type === 'map' || this.type === 'list') &&
+ ['[', '{'].indexOf(this.value.charAt(0)) > -1 &&
+ [']', '}'].indexOf(this.value.slice(-1)) > -1) {
+ this.value = this.value.slice(1, -1);
+ }
+ }
+
+ public toJSON = ():any => {
+ if (!this.resourceInstanceUniqueId) {
+ this.value = undefined;
+ }
+ this.readonly = undefined;
+ this.resourceInstanceUniqueId = undefined;
+ return this;
+ };
+ }
+}
diff --git a/catalog-ui/app/scripts/models/capability.ts b/catalog-ui/app/scripts/models/capability.ts
new file mode 100644
index 0000000000..815be5a389
--- /dev/null
+++ b/catalog-ui/app/scripts/models/capability.ts
@@ -0,0 +1,116 @@
+/*-
+ * ============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/20/2016.
+ */
+/// <reference path="../references"/>
+module Sdc.Models {
+ 'use strict';
+
+ //this is an object contains keys, when each key has matching array.
+ // for example: key = tosca.capabilities.network.Linkable and the match array is array of capabilities objects
+ export class CapabilitiesGroup {
+ constructor(capabilityGroupObj?:Models.CapabilitiesGroup) {
+ _.forEach(capabilityGroupObj, (capabilitiesArrayObj:Array<Models.Capability>, instance) => {
+ this[instance] = [];
+ _.forEach(capabilitiesArrayObj, (capability:Models.Capability):void => {
+ this[instance].push(new Models.Capability(capability));
+ });
+ });
+ }
+
+ public findValueByKey(keySubstring:string):Array<Models.Capability> {
+ let key:string = _.find(Object.keys(this), (key)=> {
+ return _.includes(key.toLowerCase(), keySubstring);
+ });
+ return this[key];
+ }
+ }
+
+ export class Capability {
+
+ //server data
+ name:string;
+ ownerId:string;
+ ownerName:string;
+ type:string;
+ uniqueId:string;
+ capabilitySources:Array<String>;
+ minOccurrences:string;
+ maxOccurrences:string;
+ properties:Array<Models.PropertyModel>;
+ description:string;
+ validSourceTypes:Array<string>;
+ //custom
+ selected:boolean;
+ filterTerm:string;
+
+ constructor(capability?:Capability) {
+
+ if (capability) {
+ //server data
+ this.name = capability.name;
+ this.ownerId = capability.ownerId;
+ this.ownerName = capability.ownerName;
+ this.type = capability.type;
+ this.uniqueId = capability.uniqueId;
+ this.capabilitySources = capability.capabilitySources;
+ this.minOccurrences = capability.minOccurrences;
+ this.maxOccurrences = capability.maxOccurrences;
+ this.properties = capability.properties;
+ this.description = capability.description;
+ this.validSourceTypes = capability.validSourceTypes;
+ this.selected = capability.selected;
+ this.initFilterTerm();
+
+ }
+ }
+
+ public getFullTitle():string {
+ let maxOccurrences:string = this.maxOccurrences === 'UNBOUNDED' ? '∞' : this.maxOccurrences;
+ return this.ownerName + ': ' + this.name + ': [' + this.minOccurrences + ', ' + maxOccurrences + ']';
+ }
+
+ public toJSON = ():any => {
+ this.selected = undefined;
+ this.filterTerm = undefined;
+ return this;
+ };
+
+ private initFilterTerm = ():void =>{
+ this.filterTerm = this.name + " " +
+ (this.type ? (this.type.substring("tosca.capabilities.".length) + " " ) : "") +
+ (this.description||"") + " " +
+ (this.ownerName||"") + " " +
+ (this.validSourceTypes ? (this.validSourceTypes.join(',') + " ") : "") +
+ this.minOccurrences+","+this.maxOccurrences;
+ if(this.properties && this.properties.length){
+ _.forEach(this.properties,(prop:Models.PropertyModel)=>{
+ this.filterTerm += " "+ prop.name +
+ " " + (prop.description||"") +
+ " " + prop.type +
+ (prop.schema && prop.schema.property?(" " + prop.schema.property.type):"");
+ });
+ }
+ }
+ }
+}
+
+
diff --git a/catalog-ui/app/scripts/models/category.ts b/catalog-ui/app/scripts/models/category.ts
new file mode 100644
index 0000000000..730460cbc0
--- /dev/null
+++ b/catalog-ui/app/scripts/models/category.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.Models {
+ 'use strict';
+
+ export class ICategoryBase {
+
+ //server properties
+ name: string;
+ normalizedName: string;
+ uniqueId:string;
+ icons: Array<string>;
+
+ //custom properties
+ filterTerms: string;
+ isDisabled: boolean;
+ filteredGroup: Array<Models.IGroup>;
+
+ constructor(category?: ICategoryBase){
+ if (category) {
+ this.name = category.name;
+ this.normalizedName = category.normalizedName;
+ this.icons = category.icons;
+ this.filterTerms = category.filterTerms;
+ this.isDisabled = category.isDisabled;
+ this.filteredGroup = category.filteredGroup;
+ }
+ }
+ }
+
+ export class IMainCategory extends ICategoryBase {
+ subcategories:Array<ISubCategory>;
+ constructor();
+ constructor(category?: IMainCategory){
+ super(category);
+ if (category) {
+ this.subcategories = category.subcategories;
+ }
+ }
+ }
+
+ export class ISubCategory extends ICategoryBase {
+ groupings:Array<ICategoryBase>;
+ }
+
+ export interface IGroup extends ICategoryBase {
+ }
+
+}
diff --git a/catalog-ui/app/scripts/models/comments.ts b/catalog-ui/app/scripts/models/comments.ts
new file mode 100644
index 0000000000..0f7643690d
--- /dev/null
+++ b/catalog-ui/app/scripts/models/comments.ts
@@ -0,0 +1,33 @@
+/*-
+ * ============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.Models {
+ 'use strict';
+
+ export class AsdcComment{
+ public userRemarks: string;
+
+ constructor() {
+ }
+ }
+}
+
+
+
diff --git a/catalog-ui/app/scripts/models/components/component.ts b/catalog-ui/app/scripts/models/components/component.ts
new file mode 100644
index 0000000000..c0fb3a9fbb
--- /dev/null
+++ b/catalog-ui/app/scripts/models/components/component.ts
@@ -0,0 +1,828 @@
+/*-
+ * ============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 2/4/2016.
+ */
+/// <reference path="../../references"/>
+module Sdc.Models.Components {
+ 'use strict';
+ import Util = jasmine.Util;
+
+ export interface IComponent {
+
+ //---------------------------------------------- API CALLS ----------------------------------------------------//
+
+ //Component API
+ getComponent():ng.IPromise<Models.Components.Component>;
+ updateComponent():ng.IPromise<Models.Components.Component>;
+ createComponentOnServer():ng.IPromise<Models.Components.Component>;
+ changeLifecycleState(state:string, commentObj:Models.AsdcComment):ng.IPromise<Models.Components.Component>;
+ validateName(newName:string):ng.IPromise<Models.IValidate>;
+ updateRequirementsCapabilities():ng.IPromise<any>;
+
+ //Artifacts API
+ addOrUpdateArtifact(artifact:ArtifactModel):ng.IPromise<Models.ArtifactModel>;
+ updateMultipleArtifacts(artifacts:Array<Models.ArtifactModel>):ng.IPromise<any>;
+ deleteArtifact(artifactId:string, artifactLabel:string):ng.IPromise<Models.ArtifactModel>;
+ downloadInstanceArtifact(artifactId:string):ng.IPromise<Models.IFileDownload>;
+ downloadArtifact(artifactId:string):ng.IPromise<Models.IFileDownload>;
+
+ //Property API
+ addOrUpdateProperty(property:Models.PropertyModel):ng.IPromise<Models.PropertyModel>;
+ deleteProperty(propertyId:string):ng.IPromise<Models.PropertyModel>;
+ updateInstanceProperty(property:Models.PropertyModel):ng.IPromise<Models.PropertyModel>;
+
+ //Attribute API
+ deleteAttribute(attributeId:string):ng.IPromise<Models.AttributeModel>;
+ addOrUpdateAttribute(attribute:Models.AttributeModel):ng.IPromise<Models.AttributeModel>;
+ updateInstanceAttribute(attribute:Models.AttributeModel):ng.IPromise<Models.AttributeModel>;
+
+
+
+
+ //Component Instance API
+ createComponentInstance(componentInstance:Models.ComponentsInstances.ComponentInstance):ng.IPromise<Models.ComponentsInstances.ComponentInstance>;
+ deleteComponentInstance(componentInstanceId:string):ng.IPromise<Models.ComponentsInstances.ComponentInstance>;
+ addOrUpdateInstanceArtifact(artifact:Models.ArtifactModel):ng.IPromise<Models.ArtifactModel>;
+ deleteInstanceArtifact(artifactId:string, artifactLabel:string):ng.IPromise<Models.ArtifactModel>;
+ uploadInstanceEnvFile(artifact:Models.ArtifactModel): ng.IPromise<Models.ArtifactModel>;
+ changeComponentInstanceVersion(componentUid:string):ng.IPromise<Models.Components.Component>;
+ updateComponentInstance(componentInstance:Models.ComponentsInstances.ComponentInstance): ng.IPromise<Models.ComponentsInstances.ComponentInstance>;
+ updateMultipleComponentInstances(instances: Array<Models.ComponentsInstances.ComponentInstance>):ng.IPromise<Array<Models.ComponentsInstances.ComponentInstance>>;
+
+ //Inputs API
+ getComponentInstanceInputProperties(componentInstanceId: string, inputId: string):ng.IPromise<Array<Models.PropertyModel>>
+ getComponentInputs(componentId: string):ng.IPromise<Array<Models.InputModel>>;
+
+ createRelation(link:Models.RelationshipModel):ng.IPromise<Models.RelationshipModel>;
+ deleteRelation(link:Models.RelationshipModel):ng.IPromise<Models.RelationshipModel>;
+
+
+ //Modules
+ getModuleForDisplay(moduleId:string):ng.IPromise<Models.DisplayModule>;
+ updateGroupMetadata(group:Models.Module):ng.IPromise<Models.Module>;
+ //---------------------------------------------- HELP FUNCTIONS ----------------------------------------------------//
+
+ getComponentSubType():string;
+ isAlreadyCertified():boolean;
+ isProduct():boolean;
+ isService():boolean;
+ isResource():boolean;
+ isComplex():boolean;
+ getAdditionalInformation():Array<Models.AdditionalInformationModel>;
+ getAllVersionsAsSortedArray():Array<any>;
+ getStatus(sdcMenu:Models.IAppMenu):string;
+ }
+
+
+ export class Component implements IComponent {
+
+ //server data
+ public abstract:string;
+ public uniqueId:string;
+ public uuid:string;
+ public invariantUUID:string;
+ public name:string;
+ public version:string;
+ public creationDate:number;
+ public lastUpdateDate:number;
+ public description:string;
+ public lifecycleState:string;
+ public tags:Array<string>;
+ public icon:string;
+ public contactId:string;
+ public allVersions:any;
+ public creatorUserId:string;
+ public creatorFullName:string;
+ public lastUpdaterUserId:string;
+ public lastUpdaterFullName:string;
+ public componentType:string;
+ public deploymentArtifacts:Models.ArtifactGroupModel;
+ public artifacts:Models.ArtifactGroupModel;
+ public toscaArtifacts:Models.ArtifactGroupModel;
+ public distributionStatus:string;
+ public categories:Array<Models.IMainCategory>;
+ public componentInstancesProperties:Models.PropertiesGroup;
+ public componentInstancesAttributes:Models.AttributesGroup;
+ public componentInstancesRelations:Array<Models.RelationshipModel>;
+ public componentInstances:Array<Models.ComponentsInstances.ComponentInstance>;
+ public inputs:Array<Models.InputModel>;
+ public capabilities:Models.CapabilitiesGroup;
+ public requirements:Models.RequirementsGroup;
+ public additionalInformation:any;
+ public properties:Array<Models.PropertyModel>;
+ public attributes:Array<Models.AttributeModel>;
+ public highestVersion:boolean;
+ public vendorName:string;
+ public vendorRelease:string;
+ public derivedList:Array<any>;
+ public interfaces:any;
+ public normalizedName:string;
+ public systemName:string;
+ public projectCode:string;
+ public groups:Array<Models.Module>;
+ //custom properties
+ public componentService:Sdc.Services.Components.IComponentService;
+ public filterTerm:string;
+ public iconSprite:string;
+ public selectedInstance:Models.ComponentsInstances.ComponentInstance;
+ public mainCategory:string;
+ public subCategory:string;
+ public selectedCategory:string;
+ public showMenu:boolean;
+
+
+ constructor(componentService:Sdc.Services.Components.IComponentService,
+ protected $q:ng.IQService,
+ component?:Component) {
+ if (component) {
+ this.abstract = component.abstract;
+ this.uniqueId = component.uniqueId;
+ this.uuid = component.uuid;
+ this.invariantUUID = component.invariantUUID;
+ this.additionalInformation = component.additionalInformation;
+ this.artifacts = new Sdc.Models.ArtifactGroupModel(component.artifacts);
+ this.toscaArtifacts = new Sdc.Models.ArtifactGroupModel(component.toscaArtifacts);
+ this.contactId = component.contactId;
+ this.categories = component.categories;
+ this.creatorUserId = component.creatorUserId;
+ this.creationDate = component.creationDate;
+ this.creatorFullName = component.creatorFullName;
+ this.description = component.description;
+ this.icon = component.icon;
+ this.lastUpdateDate = component.lastUpdateDate;
+ this.lastUpdaterUserId = component.lastUpdaterUserId;
+ this.lastUpdaterFullName = component.lastUpdaterFullName;
+ this.lifecycleState = component.lifecycleState;
+ this.initComponentInstanceRelations(component.componentInstancesRelations);
+ this.componentInstancesProperties = new Models.PropertiesGroup(component.componentInstancesProperties);
+ this.componentInstancesAttributes = new Models.AttributesGroup(component.componentInstancesAttributes);
+ this.name = component.name;
+ this.version = component.version;
+ this.tags = component.tags;
+ this.capabilities = new Models.CapabilitiesGroup(component.capabilities);
+ this.requirements = new Models.RequirementsGroup(component.requirements);
+ this.allVersions = component.allVersions;
+ this.deploymentArtifacts = new Sdc.Models.ArtifactGroupModel(component.deploymentArtifacts);
+ this.componentType = component.componentType;
+ this.distributionStatus = component.distributionStatus;
+ this.highestVersion = component.highestVersion;
+ this.vendorName = component.vendorName;
+ this.vendorRelease = component.vendorRelease;
+ this.derivedList = component.derivedList;
+ this.interfaces = component.interfaces;
+ this.normalizedName = component.normalizedName;
+ this.systemName = component.systemName;
+ this.projectCode = component.projectCode;
+ this.inputs = component.inputs;
+ this.componentInstances = Utils.CommonUtils.initComponentInstances(component.componentInstances);
+ this.properties = Utils.CommonUtils.initProperties(component.properties, this.uniqueId);
+ this.attributes = Utils.CommonUtils.initAttributes(component.attributes, this.uniqueId);
+ this.selectedInstance = component.selectedInstance;
+ this.iconSprite = component.iconSprite;
+ this.showMenu = true;
+ this.groups = Utils.CommonUtils.initModules(component.groups);
+ }
+
+ //custom properties
+ this.componentService = componentService;
+ }
+
+ public setUniqueId = (uniqueId:string):void => {
+ this.uniqueId = uniqueId;
+ };
+
+ public setSelectedInstance = (componentInstance:Models.ComponentsInstances.ComponentInstance):void => {
+ this.selectedInstance = componentInstance;
+ };
+
+ //------------------------------------------ Init Functions ----------------------------------------------------------------//
+
+ private initComponentInstanceRelations = (componentInstanceRelationsObj:Array<Models.RelationshipModel>):void => {
+ if (componentInstanceRelationsObj) {
+ this.componentInstancesRelations = [];
+ _.forEach(componentInstanceRelationsObj, (instanceRelation:Models.RelationshipModel):void => {
+ this.componentInstancesRelations.push(new Models.RelationshipModel(instanceRelation));
+ });
+ }
+ };
+ //----------------------------------------------------------------------------------------------------------------------//
+
+ //------------------------------------------ API Calls ----------------------------------------------------------------//
+ public changeLifecycleState = (state:string, commentObj:Models.AsdcComment):ng.IPromise<Models.Components.Component> => {
+ return this.componentService.changeLifecycleState(this, state, JSON.stringify(commentObj));
+ };
+
+ public getComponent = ():ng.IPromise<Models.Components.Component> => {
+ return this.componentService.getComponent(this.uniqueId);
+ };
+
+ public createComponentOnServer = ():ng.IPromise<Models.Components.Component> => {
+ this.handleTags();
+ return this.componentService.createComponent(this);
+ };
+
+ public updateComponent = ():ng.IPromise<Models.Components.Component> => {
+ this.handleTags();
+ return this.componentService.updateComponent(this);
+ };
+
+ public validateName = (newName:string, subtype?:string):ng.IPromise<Models.IValidate> => {
+ return this.componentService.validateName(newName, subtype);
+ };
+
+ public downloadArtifact = (artifactId:string):ng.IPromise<Models.IFileDownload> => {
+ return this.componentService.downloadArtifact(this.uniqueId, artifactId);
+ };
+
+ public addOrUpdateArtifact = (artifact:ArtifactModel):ng.IPromise<Models.ArtifactModel> => {
+ let deferred = this.$q.defer();
+ let onSuccess = (artifactObj:Models.ArtifactModel):void => {
+ let newArtifact = new Models.ArtifactModel(artifactObj);
+ let artifacts = this.getArtifactsByType(artifactObj.artifactGroupType);
+ artifacts[artifactObj.artifactLabel] = newArtifact;
+ deferred.resolve(newArtifact);
+ };
+ let onError = (error:any):void => {
+ deferred.reject(error);
+ };
+ this.componentService.addOrUpdateArtifact(this.uniqueId, artifact).then(onSuccess, onError);
+ return deferred.promise;
+ };
+
+ public updateMultipleArtifacts = (artifacts:Array<Models.ArtifactModel>):ng.IPromise<any>=> {
+ let deferred = this.$q.defer();
+ let onSuccess = (response:any):void => {
+ deferred.resolve(response);
+ };
+ let onError = (error:any):void => {
+ deferred.reject(error);
+ };
+ let q = new Utils.Functions.QueueUtils(this.$q);
+
+ _.forEach(artifacts, (artifact)=> {
+ q.addBlockingUIAction(()=> this.addOrUpdateArtifact(artifact).then(onSuccess, onError));
+ });
+ return deferred.promise;
+ };
+
+
+ public deleteArtifact = (artifactId:string, artifactLabel:string):ng.IPromise<Models.ArtifactModel> => {
+ let deferred = this.$q.defer();
+ let onSuccess = (artifactObj:Models.ArtifactModel):void => {
+ let newArtifact = new Models.ArtifactModel(artifactObj);
+ let artifacts = this.getArtifactsByType(artifactObj.artifactGroupType);
+ if (newArtifact.mandatory || newArtifact.serviceApi) {
+ artifacts[newArtifact.artifactLabel] = newArtifact;
+ }
+ else {
+ delete artifacts[artifactLabel];
+ }
+ deferred.resolve(newArtifact);
+ };
+ this.componentService.deleteArtifact(this.uniqueId, artifactId, artifactLabel).then(onSuccess);
+ return deferred.promise;
+ };
+
+
+ public addOrUpdateProperty = (property:Models.PropertyModel):ng.IPromise<Models.PropertyModel> => {
+ let deferred = this.$q.defer();
+
+ let onError = (error:any):void => {
+ deferred.reject(error);
+ };
+
+ if (!property.uniqueId) {
+ let onSuccess = (property:Models.PropertyModel):void => {
+ let newProperty = new Models.PropertyModel(property);
+ this.properties.push(newProperty);
+ deferred.resolve(newProperty);
+ };
+ this.componentService.addProperty(this.uniqueId, property).then(onSuccess, onError);
+ }
+ else {
+ let onSuccess = (newProperty:Models.PropertyModel):void => {
+ // find exist instance property in parent component for update the new value ( find bu uniqueId )
+ let existProperty:Models.PropertyModel = <Models.PropertyModel>_.find(this.properties, {uniqueId: newProperty.uniqueId});
+ let propertyIndex = this.properties.indexOf(existProperty);
+ newProperty.readonly = this.uniqueId != newProperty.parentUniqueId;
+ this.properties[propertyIndex] = newProperty;
+ deferred.resolve(newProperty);
+ };
+ this.componentService.updateProperty(this.uniqueId, property).then(onSuccess, onError);
+ }
+ return deferred.promise;
+ };
+
+ public addOrUpdateAttribute = (attribute:Models.AttributeModel):ng.IPromise<Models.AttributeModel> => {
+ let deferred = this.$q.defer();
+
+ let onError = (error:any):void => {
+ deferred.reject(error);
+ };
+
+ if (!attribute.uniqueId) {
+ let onSuccess = (attribute:Models.AttributeModel):void => {
+ let newAttribute = new Models.AttributeModel(attribute);
+ this.attributes.push(newAttribute);
+ deferred.resolve(newAttribute);
+ };
+ this.componentService.addAttribute(this.uniqueId, attribute).then(onSuccess, onError);
+ }
+ else {
+ let onSuccess = (newAttribute:Models.AttributeModel):void => {
+ let existAttribute:Models.AttributeModel = <Models.AttributeModel>_.find(this.attributes, {uniqueId: newAttribute.uniqueId});
+ let attributeIndex = this.attributes.indexOf(existAttribute);
+ newAttribute.readonly = this.uniqueId != newAttribute.parentUniqueId;
+ this.attributes[attributeIndex] = newAttribute;
+ deferred.resolve(newAttribute);
+ };
+ this.componentService.updateAttribute(this.uniqueId, attribute).then(onSuccess, onError);
+ }
+ return deferred.promise;
+ };
+
+ public deleteProperty = (propertyId:string):ng.IPromise<Models.PropertyModel> => {
+ let deferred = this.$q.defer();
+ let onSuccess = ():void => {
+ console.log("Property deleted");
+ delete _.remove(this.properties, {uniqueId: propertyId})[0];
+ deferred.resolve();
+ };
+ let onFailed = ():void => {
+ console.log("Failed to delete property");
+ deferred.reject();
+ };
+ this.componentService.deleteProperty(this.uniqueId, propertyId).then(onSuccess, onFailed);
+ return deferred.promise;
+ };
+
+ public deleteAttribute = (attributeId:string):ng.IPromise<Models.AttributeModel> => {
+ let deferred = this.$q.defer();
+ let onSuccess = ():void => {
+ console.log("Attribute deleted");
+ delete _.remove(this.attributes, {uniqueId: attributeId})[0];
+ };
+ let onFailed = ():void => {
+ console.log("Failed to delete attribute");
+ };
+ this.componentService.deleteAttribute(this.uniqueId, attributeId).then(onSuccess, onFailed);
+ return deferred.promise;
+ };
+
+ public updateInstanceProperty = (property:Models.PropertyModel):ng.IPromise<Models.PropertyModel> => {
+ let deferred = this.$q.defer();
+ let onSuccess = (newProperty:Models.PropertyModel):void => {
+ // find exist instance property in parent component for update the new value ( find bu uniqueId & path)
+ let existProperty:Models.PropertyModel = <Models.PropertyModel>_.find(this.componentInstancesProperties[newProperty.resourceInstanceUniqueId], {uniqueId: newProperty.uniqueId,path: newProperty.path});
+ let index = this.componentInstancesProperties[newProperty.resourceInstanceUniqueId].indexOf(existProperty);
+ this.componentInstancesProperties[newProperty.resourceInstanceUniqueId][index] = newProperty;
+ deferred.resolve(newProperty);
+ };
+ let onFailed = (error:any):void => {
+ console.log('Failed to update property value');
+ deferred.reject(error);
+ };
+ this.componentService.updateInstanceProperty(this.uniqueId, property).then(onSuccess, onFailed);
+ return deferred.promise;
+ };
+
+ public updateInstanceAttribute = (attribute:Models.AttributeModel):ng.IPromise<Models.AttributeModel> => {
+ let deferred = this.$q.defer();
+ let onSuccess = (newAttribute:Models.AttributeModel):void => {
+ let existAttribute:Models.AttributeModel = <Models.AttributeModel>_.find(this.componentInstancesAttributes[newAttribute.resourceInstanceUniqueId], {uniqueId: newAttribute.uniqueId});
+ let index = this.componentInstancesAttributes[newAttribute.resourceInstanceUniqueId].indexOf(existAttribute);
+ this.componentInstancesAttributes[newAttribute.resourceInstanceUniqueId][index] = newAttribute;
+ deferred.resolve(newAttribute);
+ };
+ let onFailed = (error:any):void => {
+ console.log('Failed to update attribute value');
+ deferred.reject(error);
+ };
+ this.componentService.updateInstanceAttribute(this.uniqueId, attribute).then(onSuccess, onFailed);
+ return deferred.promise;
+ };
+
+ public downloadInstanceArtifact = (artifactId:string):ng.IPromise<Models.IFileDownload> => {
+ return this.componentService.downloadInstanceArtifact(this.uniqueId, this.selectedInstance.uniqueId, artifactId);
+ };
+
+ public deleteInstanceArtifact = (artifactId:string, artifactLabel:string):ng.IPromise<Models.ArtifactModel> => {
+ let deferred = this.$q.defer();
+ let onSuccess = (artifactObj:Models.ArtifactModel):void => {
+ let newArtifact = new Models.ArtifactModel(artifactObj);
+ let artifacts = this.selectedInstance.deploymentArtifacts;
+ if (newArtifact.mandatory || newArtifact.serviceApi) {//?????????
+ artifacts[newArtifact.artifactLabel] = newArtifact;
+ }
+ else {
+ delete artifacts[artifactLabel];
+ }
+ deferred.resolve(newArtifact);
+ };
+ this.componentService.deleteInstanceArtifact(this.uniqueId,this.selectedInstance.uniqueId, artifactId, artifactLabel).then(onSuccess);
+ return deferred.promise;
+ };
+
+ public addOrUpdateInstanceArtifact = (artifact:ArtifactModel):ng.IPromise<Models.ArtifactModel> => {
+ let deferred = this.$q.defer();
+ let onSuccess = (artifactObj:Models.ArtifactModel):void => {
+ this.selectedInstance.deploymentArtifacts[artifactObj.artifactLabel] = artifactObj;
+ deferred.resolve(artifactObj);
+ };
+ let onError = (error:any):void => {
+ deferred.reject(error);
+ };
+ if(artifact.uniqueId){
+ this.componentService.updateInstanceArtifact(this.uniqueId, this.selectedInstance.uniqueId, artifact).then(onSuccess, onError);
+ }else{
+ this.componentService.addInstanceArtifact(this.uniqueId, this.selectedInstance.uniqueId, artifact).then(onSuccess, onError);
+ }
+ return deferred.promise;
+ };
+
+ public uploadInstanceEnvFile = (artifact:Models.ArtifactModel):ng.IPromise<Models.ArtifactModel> => {
+ let deferred = this.$q.defer();
+ let onSuccess = (artifactObj:Models.ArtifactModel):void => {
+ this.selectedInstance.deploymentArtifacts[artifactObj.artifactLabel] = artifactObj;
+ deferred.resolve(artifactObj);
+ };
+ let onError = (error:any):void => {
+ deferred.reject(error);
+ };
+ this.componentService.uploadInstanceEnvFile(this.uniqueId, this.selectedInstance.uniqueId, artifact).then(onSuccess, onError);
+ return deferred.promise;
+ };
+
+ //this function will update the instance version than the function call getComponent to update the current component and return the new instance version
+ public changeComponentInstanceVersion = (componentUid:string):ng.IPromise<Models.Components.Component> => {
+ let deferred = this.$q.defer();
+ let onFailed = (error:any):void => {
+ deferred.reject(error);
+ };
+ let onSuccess = (componentInstance:Models.ComponentsInstances.ComponentInstance):void => {
+ let onSuccess = (component:Models.Components.Component):void => {
+ component.setSelectedInstance(componentInstance);
+ deferred.resolve(component);
+ };
+ this.getComponent().then(onSuccess, onFailed);
+ };
+ this.componentService.changeResourceInstanceVersion(this.uniqueId, this.selectedInstance.uniqueId, componentUid).then(onSuccess, onFailed);
+ return deferred.promise;
+ };
+
+ public createComponentInstance = (componentInstance:Models.ComponentsInstances.ComponentInstance):ng.IPromise<Models.ComponentsInstances.ComponentInstance> => {
+ let deferred = this.$q.defer();
+ let onSuccess = (instance:Models.ComponentsInstances.ComponentInstance):void => {
+ let onSuccess = (component:Models.Components.Component):void => {
+ this.componentInstances = Utils.CommonUtils.initComponentInstances(component.componentInstances);
+ this.componentInstancesProperties = new Models.PropertiesGroup(component.componentInstancesProperties);
+ this.componentInstancesAttributes = new Models.AttributesGroup(component.componentInstancesAttributes);
+ deferred.resolve(instance);
+ };
+ this.getComponent().then(onSuccess);
+ };
+ let onFailed = (error:any):void => {
+ deferred.reject(error);
+ };
+ this.componentService.createComponentInstance(this.uniqueId, componentInstance).then(onSuccess, onFailed);
+ return deferred.promise;
+ };
+
+ public updateComponentInstance = (componentInstance:Models.ComponentsInstances.ComponentInstance):ng.IPromise<Models.ComponentsInstances.ComponentInstance> => {
+ let deferred = this.$q.defer();
+ let onSuccess = (updatedInstance:Models.ComponentsInstances.ComponentInstance):void => {
+ let componentInstance:Models.ComponentsInstances.ComponentInstance = _.find(this.componentInstances, (instance:Models.ComponentsInstances.ComponentInstance) => {
+ return instance.uniqueId === updatedInstance.uniqueId;
+ });
+
+ let index = this.componentInstances.indexOf(componentInstance);
+ this.componentInstances[index] = componentInstance;
+ deferred.resolve(updatedInstance);
+
+ };
+ let onFailed = (error:any):void => {
+ deferred.reject(error);
+ };
+ this.componentService.updateComponentInstance(this.uniqueId, componentInstance).then(onSuccess, onFailed);
+ return deferred.promise;
+ };
+
+ public updateMultipleComponentInstances = (instances: Array<Models.ComponentsInstances.ComponentInstance>):ng.IPromise<Array<Models.ComponentsInstances.ComponentInstance>> => {
+ let deferred = this.$q.defer();
+ let onSuccess = (updatedInstances:Array<Models.ComponentsInstances.ComponentInstance>):void => {
+ deferred.resolve(updatedInstances);
+ };
+ let onFailed = (error:any):void => {
+ deferred.reject(error);
+ };
+ this.componentService.updateMultipleComponentInstances(this.uniqueId, instances).then(onSuccess, onFailed);
+ return deferred.promise;
+ };
+
+ public deleteComponentInstance = (componentInstanceId:string):ng.IPromise<Models.ComponentsInstances.ComponentInstance> => {
+ let deferred = this.$q.defer();
+ let onSuccess = ():void => {
+ let onSuccess = (component:Models.Components.Component):void => {
+ this.componentInstances = Utils.CommonUtils.initComponentInstances(component.componentInstances);
+ this.componentInstancesProperties = new Models.PropertiesGroup(component.componentInstancesProperties);
+ this.componentInstancesAttributes = new Models.AttributesGroup(component.componentInstancesAttributes);
+ this.initComponentInstanceRelations(component.componentInstancesRelations);
+ deferred.resolve();
+ };
+ this.getComponent().then(onSuccess);
+ };
+ let onFailed = (error:any):void => {
+ deferred.reject(error);
+ };
+ this.componentService.deleteComponentInstance(this.uniqueId, componentInstanceId).then(onSuccess, onFailed);
+ return deferred.promise;
+ };
+
+
+ public createRelation = (relation:Models.RelationshipModel):ng.IPromise<Models.RelationshipModel> => {
+ let deferred = this.$q.defer();
+ let onSuccess = (relation:Models.RelationshipModel):void => {
+ console.info('Link created successfully', relation);
+ if (!this.componentInstancesRelations) {
+ this.componentInstancesRelations = [];
+ }
+ this.componentInstancesRelations.push(new Models.RelationshipModel(relation));
+ deferred.resolve(relation);
+ };
+ let onFailed = (error:any):void => {
+ console.info('Failed to create relation', error);
+ deferred.reject(error);
+ };
+ this.componentService.createRelation(this.uniqueId, relation).then(onSuccess, onFailed);
+ return deferred.promise;
+ };
+
+ public deleteRelation = (relation:Models.RelationshipModel):ng.IPromise<Models.RelationshipModel> => {
+ let deferred = this.$q.defer();
+ let onSuccess = (responseRelation:Models.RelationshipModel):void => {
+ console.log("Link Deleted In Server");
+ let relationToDelete = _.find(this.componentInstancesRelations, (item) => {
+ return item.fromNode === relation.fromNode && item.toNode === relation.toNode && _.some(item.relationships, (relationship)=> {
+ return angular.equals(relation.relationships[0], relationship);
+ });
+ });
+ let index = this.componentInstancesRelations.indexOf(relationToDelete);
+ if (relationToDelete != undefined && index > -1) {
+ if (relationToDelete.relationships.length == 1) {
+ this.componentInstancesRelations.splice(index, 1);
+ } else {
+ this.componentInstancesRelations[index].relationships =
+ _.reject(this.componentInstancesRelations[index].relationships, relation.relationships[0]);
+ }
+ } else {
+ console.error("Error while deleting relation - the return delete relation from server was not found in UI")
+ }
+ deferred.resolve(relation);
+ };
+ let onFailed = (error:any):void => {
+ console.error("Failed To Delete Link");
+ deferred.reject(error);
+ };
+ this.componentService.deleteRelation(this.uniqueId, relation).then(onSuccess, onFailed);
+ return deferred.promise;
+ };
+
+ public updateRequirementsCapabilities = ():ng.IPromise<any> => {
+ let deferred = this.$q.defer();
+ let onSuccess = (response:any):void => {
+ this.capabilities = response.capabilities;
+ this.requirements = response.requirements;
+ deferred.resolve(response);
+ };
+ let onFailed = (error:any):void => {
+ deferred.reject(error);
+ };
+ this.componentService.getRequirementsCapabilities(this.uniqueId).then(onSuccess, onFailed);
+ return deferred.promise;
+ };
+
+ public getModuleForDisplay = (moduleId:string):ng.IPromise<Models.DisplayModule> => {
+
+ let deferred = this.$q.defer();
+ let onSuccess = (response:Models.DisplayModule):void => {
+ deferred.resolve(response);
+ };
+ let onFailed = (error:any):void => {
+ deferred.reject(error);
+ };
+ this.componentService.getModuleForDisplay(this.uniqueId, moduleId).then(onSuccess, onFailed);
+ return deferred.promise;
+ };
+
+ // this function get all instances filtered by inputs and properties (optional) - if no search string insert - this function will
+ // get all the instances of the component (in service only VF instances)
+ public getComponentInstancesFilteredByInputsAndProperties = (searchText?:string):ng.IPromise<Array<Models.ComponentsInstances.ComponentInstance>> => {
+
+ let deferred = this.$q.defer();
+ let onSuccess = (response: Array<Models.ComponentsInstances.ComponentInstance>):void => {
+ deferred.resolve(response);
+ };
+ let onFailed = (error:any): void => {
+ deferred.reject(error);
+ };
+ this.componentService.getComponentInstancesFilteredByInputsAndProperties(this.uniqueId, searchText).then(onSuccess, onFailed);
+ return deferred.promise;
+ };
+
+
+ // get inputs for instance - Pagination function
+ public getComponentInputs = ():ng.IPromise<Array<Models.InputModel>> => {
+
+ let deferred = this.$q.defer();
+ let onSuccess = (inputsRes: Array<Models.InputModel>):void => {
+ this.inputs = inputsRes;
+ deferred.resolve(inputsRes);
+ };
+ let onFailed = (error:any): void => {
+ deferred.reject(error);
+ };
+ this.componentService.getComponentInputs(this.uniqueId).then(onSuccess, onFailed);
+ return deferred.promise;
+ };
+
+
+ // get inputs instance - Pagination function
+ public getComponentInstanceInputs = (componentInstanceId: string, originComponentUid: string):ng.IPromise<Array<Models.InputModel>> => {
+
+ let deferred = this.$q.defer();
+ let onSuccess = (response: Array<Models.InputModel>):void => {
+ deferred.resolve(response);
+ };
+ let onFailed = (error:any): void => {
+ deferred.reject(error);
+ };
+ this.componentService.getComponentInstanceInputs(this.uniqueId, componentInstanceId, originComponentUid).then(onSuccess, onFailed);
+ return deferred.promise;
+ };
+
+ // get inputs inatnce - Pagination function
+ public getComponentInstanceInputProperties = (componentInstanceId: string, inputId: string):ng.IPromise<Array<Models.PropertyModel>> => {
+
+ let deferred = this.$q.defer();
+ let onSuccess = (response: Array<Models.PropertyModel>):void => {
+ deferred.resolve(response);
+ };
+ let onFailed = (error:any): void => {
+ deferred.reject(error);
+ };
+ this.componentService.getComponentInstanceInputProperties(this.uniqueId, componentInstanceId, inputId).then(onSuccess, onFailed);
+ return deferred.promise;
+ };
+
+ public updateGroupMetadata = (module: Models.Module):ng.IPromise<Models.Module> => {
+
+ let deferred = this.$q.defer();
+
+ let onSuccess = (updatedModule:Models.Module):void => {
+ let groupIndex: number = _.indexOf(this.groups, _.find(this.groups, (module: Models.Module) => {
+ return module.uniqueId === updatedModule.uniqueId;
+ }));
+
+ if(groupIndex !== -1) {
+ this.groups[groupIndex] = updatedModule;
+ }
+ deferred.resolve(updatedModule);
+ };
+ let onFailed = (error:any):void => {
+ deferred.reject(error);
+ };
+
+ this.componentService.updateGroupMetadata(this.uniqueId, module).then(onSuccess, onFailed);
+
+ return deferred.promise;
+ };
+
+ //------------------------------------------ Help Functions ----------------------------------------------------------------//
+
+ public isProduct = ():boolean => {
+ return this instanceof Product;
+ };
+
+ public isService = ():boolean => {
+ return this instanceof Service;
+ };
+
+ public isResource = ():boolean => {
+ return this instanceof Resource;
+ };
+
+ public getComponentSubType = ():string => {
+ return this.componentType;
+ };
+
+ public isAlreadyCertified = ():boolean => {
+ return parseInt(this.version) >= 1;
+ };
+
+ public isComplex = ():boolean => {
+ return true;
+ };
+
+ //sort string version value from hash to sorted version (i.e 1.9 before 1.11)
+ private sortVersions = (v1:string, v2:string):number => {
+ let ver1 = v1.split('.');
+ let ver2 = v2.split('.');
+ let diff = parseInt(_.first(ver1)) - parseInt(_.first(ver2));
+ if (!diff){
+ return parseInt(_.last(ver1)) - parseInt(_.last(ver2));
+ }
+ return diff;
+ };
+
+ public getAllVersionsAsSortedArray = ():Array<any> => {
+ let res = [];
+ if(this.allVersions){
+ let keys = Object.keys(this.allVersions).sort(this.sortVersions);
+ _.forEach(keys, (key)=> {
+ res.push({
+ versionNumber: key,
+ versionId: this.allVersions[key]
+ })
+ });
+ }
+ return res;
+ };
+
+ public isLatestVersion = ():boolean => {
+ if (this.allVersions){
+ return this.version === _.last(Object.keys(this.allVersions).sort(this.sortVersions));
+ }else{
+ return true;
+ }
+
+ };
+
+ public getAdditionalInformation = ():Array<Models.AdditionalInformationModel> => {
+ let additionalInformationObject:any = _.find(this.additionalInformation, (obj:any):boolean => {
+ return obj.parentUniqueId == this.uniqueId;
+ });
+ if (additionalInformationObject) {
+ return additionalInformationObject.parameters;
+ }
+ return [];
+ };
+
+ public handleTags = ():void => {
+ let isContainTag = _.find(this.tags, (tag)=> {
+ return tag === this.name;
+ });
+ if (!isContainTag) {
+ this.tags.push(this.name);
+ }
+ };
+
+ public getArtifactsByType = (artifactGroupType:string):Models.ArtifactGroupModel => {
+ switch (artifactGroupType) {
+ case Utils.Constants.ArtifactGroupType.DEPLOYMENT:
+ return this.deploymentArtifacts;
+ case Utils.Constants.ArtifactGroupType.INFORMATION:
+ return this.artifacts;
+ }
+ };
+
+ public getStatus =(sdcMenu:Models.IAppMenu):string =>{
+ let status:string = sdcMenu.LifeCycleStatuses[this.lifecycleState].text;
+ if(this.lifecycleState == "CERTIFIED" && sdcMenu.DistributionStatuses[this.distributionStatus]) {
+ status = sdcMenu.DistributionStatuses[this.distributionStatus].text;
+ }
+ return status;
+ };
+
+ public toJSON = ():any => {
+ this.componentService = undefined;
+ this.filterTerm = undefined;
+ this.iconSprite = undefined;
+ this.mainCategory = undefined;
+ this.subCategory = undefined;
+ this.selectedInstance = undefined;
+ this.showMenu = undefined;
+ this.$q = undefined;
+ this.selectedCategory = undefined;
+ return this;
+ };
+ }
+}
+
diff --git a/catalog-ui/app/scripts/models/components/displayComponent.ts b/catalog-ui/app/scripts/models/components/displayComponent.ts
new file mode 100644
index 0000000000..578f392470
--- /dev/null
+++ b/catalog-ui/app/scripts/models/components/displayComponent.ts
@@ -0,0 +1,98 @@
+/*-
+ * ============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/5/2016.
+ */
+/// <reference path="../../references"/>
+module Sdc.Models {
+ 'use strict';
+
+ export class DisplayComponent {
+
+ uniqueId:string;
+ displayName:string;
+ version:string;
+ mainCategory:string;
+ subCategory:string;
+ iconClass:string;
+ componentSubType:string;
+ searchFilterTerms:string;
+ certifiedIconClass:string;
+ icon:string;
+ isRequirmentAndCapabilitiesLoaded:boolean;
+
+ constructor(public component:Models.Components.Component) {
+ this.icon = component.icon;
+ this.version = component.version;
+ this.uniqueId = component.uniqueId;
+ this.isRequirmentAndCapabilitiesLoaded = false;
+
+ if (component.categories && component.categories[0] && component.categories[0].subcategories && component.categories[0].subcategories[0]) {
+ this.mainCategory = component.categories[0].name;
+ this.subCategory = component.categories[0].subcategories[0].name;
+ } else {
+ this.mainCategory = 'Generic';
+ this.subCategory = 'Generic';
+ }
+ if (component instanceof Models.Components.Resource) {
+ this.componentSubType = (<Models.Components.Resource>component).resourceType;
+ } else {
+ this.componentSubType = component.componentType;
+ }
+
+ this.initDisplayName(component.name);
+ this.searchFilterTerms = (this.displayName + ' ' + component.description + ' ' + component.tags.join(' ')).toLowerCase() + ' ' + component.version;
+ this.initIconSprite(component.icon);
+ this.certifiedIconClass = component.lifecycleState != 'CERTIFIED' ? 'non-certified' : '';
+ if(component.icon === 'vl' || component.icon === 'cp') {
+ this.certifiedIconClass = this.certifiedIconClass + " " + 'smaller-icon';
+ }
+ }
+
+ public initDisplayName = (name:string):void => {
+ let newName =
+ _.last(_.last(_.last(_.last(_.last(_.last(_.last(_.last(name.split('tosca.nodes.'))
+ .split('network.')).split('relationships.')).split('org.openecomp.')).split('resource.nfv.'))
+ .split('nodes.module.')).split('cp.')).split('vl.'));
+ if (newName){
+ this.displayName = newName;
+ } else {
+ this.displayName = name;
+ }
+ };
+
+ public initIconSprite = (icon:string ):void => {
+ switch (this.componentSubType) {
+ case Utils.Constants.ComponentType.SERVICE:
+ this.iconClass = "sprite-services-icons " + icon;
+ break;
+ case Utils.Constants.ComponentType.PRODUCT:
+ this.iconClass = "sprite-product-icons " + icon;
+ break;
+ default:
+ this.iconClass = "sprite-resource-icons " + icon;
+ }
+ }
+
+ public getComponentSubType = ():string => {
+ return this.componentSubType;
+ };
+ }
+}
diff --git a/catalog-ui/app/scripts/models/components/product.ts b/catalog-ui/app/scripts/models/components/product.ts
new file mode 100644
index 0000000000..6ba3404afb
--- /dev/null
+++ b/catalog-ui/app/scripts/models/components/product.ts
@@ -0,0 +1,109 @@
+/*-
+ * ============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 2/4/2016.
+ */
+/// <reference path="../../references"/>
+module Sdc.Models.Components {
+ 'use strict';
+
+ export class Product extends Component{
+
+ public contacts:Array<string>;
+ public componentService: Services.Components.IProductService;
+ public fullName: string;
+
+ constructor(componentService: Services.Components.IProductService, $q:ng.IQService, component?:Product) {
+ super(componentService, $q, component);
+
+ if(component) {
+ this.fullName = component.fullName;
+ this.filterTerm = this.name + ' ' + this.description + ' ' + (this.tags ? this.tags.toString() : '') + ' ' + this.version;
+ this.contacts = component.contacts;
+ }
+ this.componentService = componentService;
+ this.iconSprite = "sprite-product-icons";
+ }
+
+ public deleteGroup = (uniqueId: string): void => {
+ _.forEach(this.categories, (category: Models.IMainCategory) => {
+ _.forEach(category.subcategories, (subcategory:Models.ISubCategory) => {
+ subcategory.groupings = _.reject (subcategory.groupings, (group:Models.IGroup) => {
+ return group.uniqueId === uniqueId;
+ });
+ if(subcategory.groupings.length == 0){ // if there is no groups, delete the subcategory
+ category.subcategories = _.reject (category.subcategories, (subcategoryObj:Models.ISubCategory) => {
+ return subcategoryObj.uniqueId === subcategory.uniqueId;
+ });
+ if(category.subcategories.length == 0){ // if there is no subcategory, delete the category
+ this.categories = _.reject (this.categories , (categoryObj:Models.IMainCategory) => {
+ return categoryObj.uniqueId === category.uniqueId;
+ });
+ }
+ }
+ });
+ });
+ };
+
+ private getCategoryObjectById = (categoriesArray:Array<Models.ICategoryBase>, categoryUniqueId:string):Models.ICategoryBase => {
+ let categorySelected = _.find(categoriesArray, (category) => {
+ return category.uniqueId === categoryUniqueId;
+ });
+ return categorySelected;
+ };
+
+ public addGroup = (category: Models.IMainCategory, subcategory: Models.ISubCategory, group: Models.IGroup): void => {
+ if(!this.categories){
+ this.categories = new Array<Models.IMainCategory>();
+ }
+ let existingCategory:Models.IMainCategory = <Models.IMainCategory>this.getCategoryObjectById(this.categories, category.uniqueId);
+ let newGroup = angular.copy(group);
+ newGroup.filterTerms = undefined;
+ newGroup.isDisabled = undefined;
+ if(!existingCategory){
+ let newCategory: Models.IMainCategory = angular.copy(category);
+ newCategory.filteredGroup = undefined;
+ newCategory.subcategories = [];
+ let newSubcategory:Models.ISubCategory = angular.copy(subcategory);
+ newSubcategory.groupings = [];
+ newSubcategory.groupings.push(newGroup);
+ newCategory.subcategories.push(newSubcategory);
+ this.categories.push(newCategory);
+ }
+ else{
+ let existingSubcategory:Models.ISubCategory = <Models.ISubCategory> this.getCategoryObjectById(existingCategory.subcategories, subcategory.uniqueId);
+ if(!existingSubcategory){
+ let newSubcategory:Models.ISubCategory = angular.copy(subcategory);
+ newSubcategory.groupings = [];
+ newSubcategory.groupings.push(newGroup);
+ existingCategory.subcategories.push(newSubcategory);
+
+ } else {
+ let existingGroup:Models.IGroup = <Models.IGroup> this.getCategoryObjectById(existingSubcategory.groupings, group.uniqueId);
+ if(!existingGroup){
+ existingSubcategory.groupings.push(newGroup);
+ }
+ }
+ }
+ };
+
+ }
+}
+
diff --git a/catalog-ui/app/scripts/models/components/resource.ts b/catalog-ui/app/scripts/models/components/resource.ts
new file mode 100644
index 0000000000..243ef3463c
--- /dev/null
+++ b/catalog-ui/app/scripts/models/components/resource.ts
@@ -0,0 +1,185 @@
+/*-
+ * ============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 2/3/2016.
+ */
+/// <reference path="../../references"/>
+module Sdc.Models.Components {
+ 'use strict';
+
+ export class Resource extends Component {
+
+ public interfaces: any;
+ public derivedFrom:Array<string>;
+ public componentService: Services.Components.IResourceService;
+ public resourceType:string;
+ public payloadData:string;
+ public payloadName:string;
+ public importedFile: Sdc.Directives.FileUploadModel;
+
+ // Onboarding parameters
+ public csarUUID:string;
+ public csarVersion:string;
+ public csarPackageType:string;
+ public packageId:string;
+
+ constructor(componentService: Services.Components.IResourceService, $q: ng.IQService, component?:Resource) {
+ super(componentService, $q, component);
+ if(component) {
+
+ this.interfaces = component.interfaces;
+ this.derivedFrom = component.derivedFrom;
+ this.payloadData = component.payloadData ? component.payloadData : undefined;
+ this.payloadName = component.payloadName ? component.payloadName : undefined;
+ this.resourceType = component.resourceType;
+ this.csarUUID = component.csarUUID;
+ this.csarVersion = component.csarVersion;
+ this.filterTerm = this.name + ' ' + this.description + ' ' + (this.tags ? this.tags.toString() : '') + ' ' + this.version + ' ' + this.resourceType;
+
+ if (component.categories && component.categories[0] && component.categories[0].subcategories && component.categories[0].subcategories[0]) {
+ component.mainCategory = component.categories[0].name;
+ component.subCategory = component.categories[0].subcategories[0].name;
+ this.selectedCategory = component.mainCategory + "_#_" + component.subCategory;
+ this.importedFile = component.importedFile;
+ }
+ } else {
+ this.resourceType = Utils.Constants.ResourceType.VF;
+ }
+
+ this.componentService = componentService;
+ this.iconSprite = "sprite-resource-icons";
+ }
+
+ public getComponentSubType = ():string => {
+ return this.resourceType;
+ };
+
+ public isComplex = ():boolean => {
+ return this.resourceType === Utils.Constants.ResourceType.VF;
+ };
+
+ public isVl = ():boolean => {
+ return Utils.Constants.ResourceType.VL == this.resourceType;
+ };
+
+ public isCsarComponent = ():boolean => {
+ return !!this.csarUUID;
+ };
+
+ public createComponentOnServer = ():ng.IPromise<Models.Components.Component> => {
+ let deferred = this.$q.defer();
+ let onSuccess = (component:Models.Components.Resource):void => {
+ this.payloadData = undefined;
+ this.payloadName = undefined;
+ deferred.resolve(component);
+ };
+ let onError = (error:any):void => {
+ deferred.reject(error);
+ };
+
+ this.handleTags();
+ if(this.importedFile){
+ this.payloadData = this.importedFile.base64;
+ this.payloadName = this.importedFile.filename;
+ }
+ this.componentService.createComponent(this).then(onSuccess, onError);
+ return deferred.promise;
+ };
+
+ /* we need to change the name of the input to vfInstanceName + input name before sending to server in order to create the inputs on the service
+ * we also need to remove already selected inputs (the inputs that already create on server, and disabled in the view - but they are selected so they are still in the view model
+ */
+ public createInputsFormInstances = (instanceInputsPropertiesMap:Models.InstanceInputsPropertiesMapData):ng.IPromise<Array<Models.InputModel>> => {
+ let deferred = this.$q.defer();
+ /*
+ let instanceInputsPropertiesMapToCreate: Models.InstanceInputsPropertiesMapData = new Models.InstanceInputsPropertiesMapData();
+ _.forEach(instanceInputsPropertiesMap, (properties:Array<Models.PropertyModel>, instanceId:string) => {
+
+ if(properties && properties.length > 0) {
+ let componentInstance:Models.ComponentsInstances.ComponentInstance = _.find(this.componentInstances, (instace:Models.ComponentsInstances.ComponentInstance) => {
+ return instace.uniqueId === instanceId;
+ });
+
+ instanceInputsPropertiesMapToCreate[instanceId] = new Array<Models.PropertyModel>();
+ _.forEach(properties, (property:Models.PropertyModel) => {
+
+ if(!property.isAlreadySelected) {
+ let newInput = new Models.PropertyModel(property);
+ newInput.name = componentInstance.normalizedName + '_' + property.name;
+ instanceInputsPropertiesMapToCreate[instanceId].push(newInput);
+ }
+ });
+ if( instanceInputsPropertiesMapToCreate[instanceId].length === 0) {
+ delete instanceInputsPropertiesMapToCreate[instanceId];
+ }
+ } else {
+ delete instanceInputsPropertiesMapToCreate[instanceId];
+ }
+ });
+
+ if(Object.keys(instanceInputsPropertiesMapToCreate).length > 0) {
+ let deferred = this.$q.defer();
+ let onSuccess = (propertiesCreated: Array<Models.PropertyModel>):void => {
+ this.inputs = propertiesCreated.concat(this.inputs);
+ deferred.resolve(propertiesCreated);
+ };
+ let onFailed = (error:any): void => {
+ deferred.reject(error);
+ };
+ this.componentService.createInputsFromInstancesInputsProperties(this.uniqueId, new Models.InstanceInputsPropertiesMap(instanceInputsPropertiesMapToCreate)).then(onSuccess, onFailed);
+ }
+ */
+ return deferred.promise;
+ };
+
+ // we need to change the name of the input to vfInstanceName + input name before sending to server in order to create the inputs on the service
+ public getResourceInputInputs = (inputId:string):ng.IPromise<Array<Models.InputModel>> => {
+ let deferred = this.$q.defer();
+ let onSuccess = (inputInputs: Array<Models.InputModel>):void => {
+ let input: Models.InputModel = _.find(this.inputs, (input:Models.InputModel) => {
+ return input.uniqueId === inputId;
+ });
+ input.inputs = inputInputs;
+ deferred.resolve(inputInputs);
+ };
+ let onFailed = (error:any): void => {
+ deferred.reject(error);
+ };
+ this.componentService.getComponentInputInputs(this.uniqueId, inputId).then(onSuccess, onFailed);
+ return deferred.promise;
+ };
+
+ public toJSON = ():any => {
+ this.componentService = undefined;
+ this.filterTerm = undefined;
+ this.iconSprite = undefined;
+ this.mainCategory = undefined;
+ this.subCategory = undefined;
+ this.selectedInstance = undefined;
+ this.showMenu = undefined;
+ this.$q = undefined;
+ this.selectedCategory = undefined;
+ this.importedFile = undefined;
+ return this;
+ };
+ }
+}
+
+
diff --git a/catalog-ui/app/scripts/models/components/service.ts b/catalog-ui/app/scripts/models/components/service.ts
new file mode 100644
index 0000000000..b1730aae94
--- /dev/null
+++ b/catalog-ui/app/scripts/models/components/service.ts
@@ -0,0 +1,147 @@
+/*-
+ * ============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 2/4/2016.
+ */
+/// <reference path="../../references"/>
+module Sdc.Models.Components {
+ 'use strict';
+
+
+ export class Service extends Component {
+
+ public serviceApiArtifacts:Models.ArtifactGroupModel;
+ public componentService:Services.Components.IServiceService;
+
+ constructor(componentService:Services.Components.IServiceService, $q:ng.IQService, component?:Service) {
+ super(componentService, $q, component);
+ if (component) {
+ this.serviceApiArtifacts = new Models.ArtifactGroupModel(component.serviceApiArtifacts);
+ this.filterTerm = this.name + ' ' + this.description + ' ' + (this.tags ? this.tags.toString() : '') + ' ' + this.version;
+ if (component.categories && component.categories[0]) {
+ this.mainCategory = component.categories[0].name;
+ this.selectedCategory = this.mainCategory;
+ }
+ }
+ this.componentService = componentService;
+ this.iconSprite = "sprite-services-icons";
+ }
+
+ public getDistributionsList = ():ng.IPromise<Array<Models.Distribution>> => {
+ return this.componentService.getDistributionsList(this.uuid);
+ };
+
+ public getDistributionsComponent = (distributionId:string):ng.IPromise<Array<Models.DistributionComponent>> => {
+ return this.componentService.getDistributionComponents(distributionId);
+ };
+
+ public markAsDeployed = (distributionId:string):ng.IPromise<any> => {
+ return this.componentService.markAsDeployed(this.uniqueId, distributionId);
+ };
+
+ /* we need to change the name of the input to vfInstanceName + input name before sending to server in order to create the inputs on the service
+ * we also need to remove already selected inputs (the inputs that already create on server, and disabled in the view - but they are selected so they are still in the view model
+ */
+ public createInputsFormInstances = (instancesInputsMap:Models.InstancesInputsMapData):ng.IPromise<Array<Models.InputModel>> => {
+ let deferred = this.$q.defer();
+
+ let instancesInputsMapToCreate: Models.InstancesInputsMapData = new Models.InstancesInputsMapData();
+ _.forEach(instancesInputsMap, (inputs:Array<Models.InputModel>, instanceId:string) => {
+
+ if(inputs && inputs.length > 0) {
+ let componentInstance:Models.ComponentsInstances.ComponentInstance = _.find(this.componentInstances, (instace:Models.ComponentsInstances.ComponentInstance) => {
+ return instace.uniqueId === instanceId;
+ });
+ instancesInputsMapToCreate[instanceId] = new Array<Models.InputModel>();
+ _.forEach(inputs, (input:Models.InputModel) => {
+
+ if(!input.isAlreadySelected) {
+ let newInput = new Models.InputModel(input);
+ newInput.name = componentInstance.normalizedName + '_' + input.name;
+ instancesInputsMapToCreate[instanceId].push(newInput);
+ }
+ });
+ if( instancesInputsMapToCreate[instanceId].length === 0) {
+ delete instancesInputsMapToCreate[instanceId];
+ }
+ } else {
+ delete instancesInputsMapToCreate[instanceId];
+ }
+ });
+
+ if(Object.keys(instancesInputsMapToCreate).length > 0) {
+ let deferred = this.$q.defer();
+ let onSuccess = (inputsCreated: Array<Models.InputModel>):void => {
+ this.inputs = inputsCreated.concat(this.inputs);
+ deferred.resolve(inputsCreated);
+ };
+ let onFailed = (error:any): void => {
+ deferred.reject(error);
+ };
+ this.componentService.createInputsFromInstancesInputs(this.uniqueId, new Models.InstancesInputsMap(instancesInputsMapToCreate)).then(onSuccess, onFailed);
+ }
+ return deferred.promise;
+ };
+
+ // we need to change the name of the input to vfInstanceName + input name before sending to server in order to create the inputs on the service
+ public getServiceInputInputs = (inputId:string):ng.IPromise<Array<Models.InputModel>> => {
+ let deferred = this.$q.defer();
+ let onSuccess = (inputInputs: Array<Models.InputModel>):void => {
+ let input: Models.InputModel = _.find(this.inputs, (input:Models.InputModel) => {
+ return input.uniqueId === inputId;
+ });
+ input.inputs = inputInputs;
+ deferred.resolve(inputInputs);
+ };
+ let onFailed = (error:any): void => {
+ deferred.reject(error);
+ };
+ this.componentService.getComponentInputInputs(this.uniqueId, inputId).then(onSuccess, onFailed);
+ return deferred.promise;
+ };
+
+ public deleteServiceInput = (inputId:string):ng.IPromise<Models.InputModel> => {
+ var deferred = this.$q.defer();
+
+ var onSuccess = (input: Models.InputModel):void => {
+ deferred.resolve(input)
+ };
+
+ var onFailed = (error:any) : void => {
+ deferred.reject(error);
+ };
+
+ this.componentService.deleteComponentInput(this.uniqueId, inputId).then(onSuccess, onFailed);
+ return deferred.promise;
+ };
+
+ public getArtifactsByType = (artifactGroupType:string):Models.ArtifactGroupModel => {
+ switch (artifactGroupType) {
+ case Utils.Constants.ArtifactGroupType.DEPLOYMENT:
+ return this.deploymentArtifacts;
+ case Utils.Constants.ArtifactGroupType.INFORMATION:
+ return this.artifacts;
+ case Utils.Constants.ArtifactGroupType.SERVICE_API:
+ return this.serviceApiArtifacts;
+ }
+ };
+ }
+}
+
diff --git a/catalog-ui/app/scripts/models/componentsInstances/componentInstance.ts b/catalog-ui/app/scripts/models/componentsInstances/componentInstance.ts
new file mode 100644
index 0000000000..af2f338998
--- /dev/null
+++ b/catalog-ui/app/scripts/models/componentsInstances/componentInstance.ts
@@ -0,0 +1,126 @@
+/*-
+ * ============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 2/4/2016.
+ */
+/// <reference path="../../references"/>
+module Sdc.Models.ComponentsInstances {
+ 'use strict';
+
+ export class ComponentInstance{
+
+ public componentUid: string;
+ public componentName:string;
+ public posX: number;
+ public posY: number;
+ public componentVersion:string;
+ public description: string;
+ public icon: string;
+ public name: string;
+ public normalizedName:string;
+ public originType: string;
+ public deploymentArtifacts: Models.ArtifactGroupModel;
+ public propertyValueCounter: number;
+ public uniqueId: string;
+ public creationTime: number;
+ public modificationTime: number;
+ public capabilities: Models.CapabilitiesGroup;
+ public requirements: Models.RequirementsGroup;
+
+ //custom properties
+ public certified: boolean;
+ public iconSprite:string;
+ public inputs: Array<Models.InputModel>;
+ public properties: Array<Models.PropertyModel>;
+
+ constructor(componentInstance?: ComponentInstance) {
+
+ if(componentInstance) {
+ this.componentUid = componentInstance.componentUid;
+ this.componentName = componentInstance.componentName;
+
+ this.componentVersion = componentInstance.componentVersion;
+ this.description = componentInstance.description;
+ this.icon = componentInstance.icon;
+ this.name = componentInstance.name;
+ this.normalizedName = componentInstance.normalizedName;
+ this.originType = componentInstance.originType;
+ this.deploymentArtifacts = new Models.ArtifactGroupModel(componentInstance.deploymentArtifacts);
+ this.uniqueId = componentInstance.uniqueId;
+ this.creationTime = componentInstance.creationTime;
+ this.modificationTime = componentInstance.modificationTime;
+ this.propertyValueCounter = componentInstance.propertyValueCounter;
+ this.capabilities = new Models.CapabilitiesGroup(componentInstance.capabilities);
+ this.requirements = new Models.RequirementsGroup(componentInstance.requirements);
+ this.certified = componentInstance.certified;
+ this.updatePosition(componentInstance.posX, componentInstance.posY);
+ }
+ }
+
+ public isUcpe = ():boolean =>{
+ if(this.originType === 'VF' && this.capabilities && this.capabilities['tosca.capabilities.Container'] && this.name.toLowerCase().indexOf('ucpe') > -1){
+ return true;
+ }
+ return false;
+ };
+
+ public isVl = ():boolean =>{
+ return this.originType === 'VL';
+ };
+
+
+ public setInstanceRC = ():void=>{
+ _.forEach(this.requirements, (requirementValue:Array<any>, requirementKey)=> {
+ _.forEach(requirementValue, (requirement)=> {
+ if (!requirement.ownerName){
+ requirement['ownerId'] = this.uniqueId;
+ requirement['ownerName'] = this.name;
+ }
+ });
+ });
+ _.forEach(this.capabilities, (capabilityValue:Array<any>, capabilityKey)=> {
+ _.forEach(capabilityValue, (capability)=> {
+ if (!capability.ownerName){
+ capability['ownerId'] = this.uniqueId;
+ capability['ownerName'] = this.name;
+ }
+ });
+ });
+ };
+
+ public updatePosition (posX:number, posY:number) {
+ this.posX = posX;
+ this.posY = posY;
+ }
+
+ public toJSON = ():any => {
+
+ var serverInstance = angular.copy(this);
+ serverInstance.certified = undefined;
+ serverInstance.iconSprite = undefined;
+ serverInstance.inputs = undefined;
+ serverInstance.properties = undefined;
+ serverInstance.requirements = undefined;
+ serverInstance.capabilities = undefined;
+ return serverInstance;
+ };
+ }
+
+}
diff --git a/catalog-ui/app/scripts/models/componentsInstances/productInstance.ts b/catalog-ui/app/scripts/models/componentsInstances/productInstance.ts
new file mode 100644
index 0000000000..71ef9bb7d3
--- /dev/null
+++ b/catalog-ui/app/scripts/models/componentsInstances/productInstance.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=========================================================
+ */
+/**
+ * Created by obarda on 2/4/2016.
+ */
+/// <reference path="../../references"/>
+module Sdc.Models.ComponentsInstances {
+ 'use strict';
+
+ export class ProductInstance extends ComponentInstance{
+
+ constructor(componentInstance?: ProductInstance) {
+ super(componentInstance);
+ this.iconSprite = "sprite-product-icons";
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/models/componentsInstances/resourceInstance.ts b/catalog-ui/app/scripts/models/componentsInstances/resourceInstance.ts
new file mode 100644
index 0000000000..67df05ded9
--- /dev/null
+++ b/catalog-ui/app/scripts/models/componentsInstances/resourceInstance.ts
@@ -0,0 +1,36 @@
+/*-
+ * ============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 2/4/2016.
+ */
+/// <reference path="../../references"/>
+module Sdc.Models.ComponentsInstances {
+ 'use strict';
+
+ export class ResourceInstance extends ComponentInstance{
+
+ constructor(componentInstance?: ResourceInstance) {
+ super(componentInstance);
+
+ this.iconSprite = "sprite-resource-icons";
+ }
+ }
+}
+
diff --git a/catalog-ui/app/scripts/models/componentsInstances/serviceInstance.ts b/catalog-ui/app/scripts/models/componentsInstances/serviceInstance.ts
new file mode 100644
index 0000000000..0d78feafd3
--- /dev/null
+++ b/catalog-ui/app/scripts/models/componentsInstances/serviceInstance.ts
@@ -0,0 +1,35 @@
+/*-
+ * ============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 2/4/2016.
+ */
+/// <reference path="../../references"/>
+module Sdc.Models.ComponentsInstances {
+ 'use strict';
+
+ export class ServiceInstance extends ComponentInstance{
+
+ constructor(componentInstance?: ServiceInstance) {
+ super(componentInstance);
+ this.iconSprite = "sprite-services-icons";
+ }
+ }
+}
+
diff --git a/catalog-ui/app/scripts/models/csar-component.ts b/catalog-ui/app/scripts/models/csar-component.ts
new file mode 100644
index 0000000000..da649c1efd
--- /dev/null
+++ b/catalog-ui/app/scripts/models/csar-component.ts
@@ -0,0 +1,36 @@
+/*-
+ * ============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.Models {
+ 'use strict';
+
+ export interface ICsarComponent {
+ displayName:string;
+ description:string;
+ vspName:string;
+ version:string;
+ packageId:string;
+ category:string;
+ subCategory:string
+ vendorName:string;
+ packageType:string;
+ vendorRelease:string;
+ }
+
+}
diff --git a/catalog-ui/app/scripts/models/data-type-properties.ts b/catalog-ui/app/scripts/models/data-type-properties.ts
new file mode 100644
index 0000000000..973978d9b2
--- /dev/null
+++ b/catalog-ui/app/scripts/models/data-type-properties.ts
@@ -0,0 +1,65 @@
+/*-
+ * ============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/25/2016.
+ */
+/// <reference path="../references"/>
+module Sdc.Models {
+ 'use strict';
+
+ export class DataTypePropertyModel {
+
+ //server data
+ uniqueId:string;
+ type:string;
+ required:boolean;
+ definition:boolean;
+ description:string;
+ password:boolean;
+ name:string;
+ parentUniqueId:string;
+ defaultValue:string;
+ constraints:Array<any>;
+ //custom
+ simpleType:string;
+
+ constructor(dataTypeProperty:DataTypePropertyModel) {
+ if (dataTypeProperty) {
+ this.uniqueId = dataTypeProperty.uniqueId;
+ this.type = dataTypeProperty.type;
+ this.required = dataTypeProperty.required;
+ this.definition = dataTypeProperty.definition;
+ this.description = dataTypeProperty.description;
+ this.password = dataTypeProperty.password;
+ this.name = dataTypeProperty.name;
+ this.parentUniqueId = dataTypeProperty.parentUniqueId;
+ this.defaultValue = dataTypeProperty.defaultValue;
+ this.constraints = dataTypeProperty.constraints;
+ this.simpleType = dataTypeProperty.simpleType;
+ }
+ }
+
+ public toJSON = ():any => {
+ this.simpleType = undefined;
+ return this;
+ };
+ }
+
+}
diff --git a/catalog-ui/app/scripts/models/data-types-map.ts b/catalog-ui/app/scripts/models/data-types-map.ts
new file mode 100644
index 0000000000..d1bee48e41
--- /dev/null
+++ b/catalog-ui/app/scripts/models/data-types-map.ts
@@ -0,0 +1,39 @@
+/*-
+ * ============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/25/2016.
+ */
+/// <reference path="../references"/>
+module Sdc.Models {
+ 'use strict';
+
+ export class DataTypesMapData {
+ [dataTypeId:string]: Array<DataTypeModel>;
+ }
+
+ export class DataTypesMap {
+ dataTypesMap:DataTypesMapData;
+
+ constructor(dataTypesMap:DataTypesMapData) {
+ this.dataTypesMap = dataTypesMap;
+ }
+ }
+
+}
diff --git a/catalog-ui/app/scripts/models/data-types.ts b/catalog-ui/app/scripts/models/data-types.ts
new file mode 100644
index 0000000000..d7de238f3b
--- /dev/null
+++ b/catalog-ui/app/scripts/models/data-types.ts
@@ -0,0 +1,55 @@
+/*-
+ * ============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/25/2016.
+ */
+/// <reference path="../references"/>
+module Sdc.Models {
+ 'use strict';
+
+ export class DataTypeModel {
+
+ //server data
+ name:string;
+ uniqueId:string;
+ derivedFromName:string;
+ creationTime:string;
+ modificationTime:string;
+ properties:Array<Models.DataTypePropertyModel>;
+
+ constructor(dataType:DataTypeModel) {
+ if (dataType) {
+ this.uniqueId = dataType.uniqueId;
+ this.name = dataType.name;
+ this.derivedFromName = dataType.derivedFromName;
+ this.creationTime = dataType.creationTime;
+ this.modificationTime = dataType.modificationTime;
+ this.properties = dataType.properties;
+ }
+ }
+
+ public toJSON = ():any => {
+
+ return this;
+ };
+ }
+
+}
+
diff --git a/catalog-ui/app/scripts/models/distribution.ts b/catalog-ui/app/scripts/models/distribution.ts
new file mode 100644
index 0000000000..1c3a9568dd
--- /dev/null
+++ b/catalog-ui/app/scripts/models/distribution.ts
@@ -0,0 +1,66 @@
+/*-
+ * ============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.Models {
+ 'use strict';
+
+ export class DistributionStatuses{
+ public omfComponentID: string;
+ public url: string;
+ public timestamp:string;
+ public status: string;
+
+ constructor() {
+ }
+ }
+
+
+ export class DistributionComponent{
+ public omfComponentID: string;
+ public url: string;
+ public timestamp:string;
+ public status: string;
+
+ constructor() {
+ }
+ }
+
+ export class Distribution {
+ public distributionID:string;
+ public timestamp:string;
+ public userId:string;
+ public deployementStatus:string;
+ public distributionComponents:Array<Models.DistributionComponent>;
+ public statusCount:any;
+ //custom data
+ public dateFormat:string;
+
+ constructor() {
+ }
+ public toJSON = ():any => {
+ this.dateFormat = undefined;
+ return this;
+ };
+
+ }
+
+}
+
+
diff --git a/catalog-ui/app/scripts/models/file-download.ts b/catalog-ui/app/scripts/models/file-download.ts
new file mode 100644
index 0000000000..8a74ed57c1
--- /dev/null
+++ b/catalog-ui/app/scripts/models/file-download.ts
@@ -0,0 +1,28 @@
+/*-
+ * ============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.Models {
+ 'use strict';
+
+ export interface IFileDownload{
+ artifactName: string;
+ base64Contents:string;
+ }
+}
diff --git a/catalog-ui/app/scripts/models/graph/d2-node.ts b/catalog-ui/app/scripts/models/graph/d2-node.ts
new file mode 100644
index 0000000000..16daa5470d
--- /dev/null
+++ b/catalog-ui/app/scripts/models/graph/d2-node.ts
@@ -0,0 +1,31 @@
+/*-
+ * ============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.Models {
+//
+// export interface D2Node extends go.Node {
+// //TODO:should be typesafe!
+// resource: any;
+// key:string;
+// data:any;
+// canvasPosition: {x:number;y:number};
+// }
+// }
diff --git a/catalog-ui/app/scripts/models/graph/graph-links/common-base-link.ts b/catalog-ui/app/scripts/models/graph/graph-links/common-base-link.ts
new file mode 100644
index 0000000000..7d21c5d978
--- /dev/null
+++ b/catalog-ui/app/scripts/models/graph/graph-links/common-base-link.ts
@@ -0,0 +1,53 @@
+/*-
+ * ============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 6/29/2016.
+ */
+/// <reference path="../../../references"/>
+module Sdc.Models {
+
+ export class CommonLinkBase {
+
+ img:string;
+ color:string;
+ classes: string;
+
+ //this is cytoscapejs fields
+ public source: string;
+ public target: string;
+ public type: string;
+ public isSdcElement: boolean;
+
+ constructor() {
+ this.isSdcElement = true;
+ this.type = 'sdc-link';
+
+ }
+
+ public setImage = (imgUrl: string) => {
+ this.img = imgUrl;
+ };
+
+ public setColor = (color: string) => {
+ this.color = color;
+ };
+
+ }
+}
diff --git a/catalog-ui/app/scripts/models/graph/graph-links/common-ci-link-base.ts b/catalog-ui/app/scripts/models/graph/graph-links/common-ci-link-base.ts
new file mode 100644
index 0000000000..1e7416ac3e
--- /dev/null
+++ b/catalog-ui/app/scripts/models/graph/graph-links/common-ci-link-base.ts
@@ -0,0 +1,50 @@
+/*-
+ * ============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 6/29/2016.
+ */
+/// <reference path="../../../references"/>
+module Sdc.Models {
+
+ export interface ICommonCiLinkBase {
+
+ }
+
+ export class CommonCiLinkBase extends CommonLinkBase implements ICommonCiLinkBase {
+
+ relation:RelationshipModel;
+
+
+ constructor(relation?:RelationshipModel, singleRelationship?:Models.Relationship) {
+ super();
+ if (relation) {
+ if(singleRelationship){
+ this.relation = new Models.RelationshipModel(relation, singleRelationship);
+ }else{
+ this.relation = new Models.RelationshipModel(relation);
+ }
+ this.source = relation.fromNode;
+ this.target = relation.toNode;
+ } else {
+ this.relation = new RelationshipModel();
+ }
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/models/graph/graph-links/composition-graph-links/composition-ci-link-base.ts b/catalog-ui/app/scripts/models/graph/graph-links/composition-graph-links/composition-ci-link-base.ts
new file mode 100644
index 0000000000..3587198615
--- /dev/null
+++ b/catalog-ui/app/scripts/models/graph/graph-links/composition-graph-links/composition-ci-link-base.ts
@@ -0,0 +1,46 @@
+/*-
+ * ============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.Models {
+
+ export interface ICompositionCiLinkBase extends ICommonCiLinkBase{
+ updateLinkDirection():void;
+ }
+
+ export class CompositionCiLinkBase extends CommonCiLinkBase implements ICompositionCiLinkBase {
+
+ type:string;
+ visible:boolean;
+
+ constructor(relation?:RelationshipModel, singleRelationship?:Models.Relationship) {
+ super(relation, singleRelationship);
+ this.visible = true;
+ }
+
+ public setRelation = (relation: Models.RelationshipModel) => {
+ this.relation = relation;
+ };
+
+ updateLinkDirection():void{
+ this.source = this.relation.fromNode;
+ this.target = this.relation.toNode;
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/models/graph/graph-links/composition-graph-links/composition-ci-simple-link.ts b/catalog-ui/app/scripts/models/graph/graph-links/composition-graph-links/composition-ci-simple-link.ts
new file mode 100644
index 0000000000..c2deddbfc3
--- /dev/null
+++ b/catalog-ui/app/scripts/models/graph/graph-links/composition-graph-links/composition-ci-simple-link.ts
@@ -0,0 +1,31 @@
+/*-
+ * ============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.Models {
+
+ export class CompositionCiSimpleLink extends CompositionCiLinkBase {
+
+ constructor(relation?:RelationshipModel, singleRelationship?:Models.Relationship) {
+ super(relation, singleRelationship);
+ this.color = Utils.Constants.GraphColors.BASE_LINK;
+ this.classes = 'simple-link';
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/models/graph/graph-links/composition-graph-links/composition-ci-ucpe-host-link.ts b/catalog-ui/app/scripts/models/graph/graph-links/composition-graph-links/composition-ci-ucpe-host-link.ts
new file mode 100644
index 0000000000..7a30c20eee
--- /dev/null
+++ b/catalog-ui/app/scripts/models/graph/graph-links/composition-graph-links/composition-ci-ucpe-host-link.ts
@@ -0,0 +1,33 @@
+/*-
+ * ============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/20/2016.
+ */
+/// <reference path="../../../../references"/>
+module Sdc.Models {
+ export class LinkUcpeHost extends CompositionCiLinkBase {
+
+ constructor(relation?:RelationshipModel, singleRelationship?:Models.Relationship) {
+ super(relation, singleRelationship);
+ this.visible = false;
+ this.classes = "ucpe-host-link";
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/models/graph/graph-links/composition-graph-links/composition-ci-ucpe-link.ts b/catalog-ui/app/scripts/models/graph/graph-links/composition-graph-links/composition-ci-ucpe-link.ts
new file mode 100644
index 0000000000..5d035ccc2c
--- /dev/null
+++ b/catalog-ui/app/scripts/models/graph/graph-links/composition-graph-links/composition-ci-ucpe-link.ts
@@ -0,0 +1,37 @@
+/*-
+ * ============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.Models {
+
+ export class CompositionCiUcpeLink extends CompositionCiLinkBase {
+
+ isFromUcpe: boolean;
+ constructor(relation?:RelationshipModel, from?:boolean, singleRelation?:Relationship) {
+ super(relation, singleRelation);
+ this.isFromUcpe = from;
+ this.target = relation.toNode;
+ this.source = singleRelation.requirementOwnerId;
+ this.relation.relationships = [singleRelation];
+ this.color = Utils.Constants.GraphColors.BASE_LINK;
+ }
+
+ updateLinkDirection():void {}
+ }
+}
diff --git a/catalog-ui/app/scripts/models/graph/graph-links/composition-graph-links/composition-ci-vl-link.ts b/catalog-ui/app/scripts/models/graph/graph-links/composition-graph-links/composition-ci-vl-link.ts
new file mode 100644
index 0000000000..a347db6cb5
--- /dev/null
+++ b/catalog-ui/app/scripts/models/graph/graph-links/composition-graph-links/composition-ci-vl-link.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.Models {
+
+ export class CompositionCiVLink extends CompositionCiLinkBase {
+
+ constructor(relation?:RelationshipModel, singleRelationship?:Models.Relationship) {
+ super(relation, singleRelationship);
+ this.color = Utils.Constants.GraphColors.VL_LINK;
+ this.classes ='vl-link';
+ }
+
+
+
+ }
+}
diff --git a/catalog-ui/app/scripts/models/graph/graph-links/composition-graph-links/composition-ci-vl-ucpe-link.ts b/catalog-ui/app/scripts/models/graph/graph-links/composition-graph-links/composition-ci-vl-ucpe-link.ts
new file mode 100644
index 0000000000..2ebc796cb9
--- /dev/null
+++ b/catalog-ui/app/scripts/models/graph/graph-links/composition-graph-links/composition-ci-vl-ucpe-link.ts
@@ -0,0 +1,33 @@
+/*-
+ * ============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/20/2016.
+ */
+/// <reference path="../../../../references"/>
+module Sdc.Models {
+
+ export class CompositionCiVlUcpeLink extends CompositionCiUcpeLink {
+
+ constructor(relation?:RelationshipModel, from?:boolean, singleRelation?:Relationship) {
+ super(relation, from, singleRelation);
+ this.color = Utils.Constants.GraphColors.VL_LINK;
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/models/graph/graph-links/links-factory.ts b/catalog-ui/app/scripts/models/graph/graph-links/links-factory.ts
new file mode 100644
index 0000000000..8f6cd6d321
--- /dev/null
+++ b/catalog-ui/app/scripts/models/graph/graph-links/links-factory.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=========================================================
+ */
+/**
+ * Created by obarda on 5/1/2016.
+ */
+/// <reference path="../../../references"/>
+module Sdc.Utils {
+ 'use strict';
+
+ export class LinksFactory {
+
+ constructor() {
+ }
+
+ public createGraphLink = (cy: Cy.Instance, relation:Models.RelationshipModel, singleRelation:Models.Relationship):Models.CompositionCiLinkBase => {
+
+ let newRelation:Models.CompositionCiLinkBase;
+
+ let fromNode:Models.Graph.CompositionCiNodeBase = cy.getElementById(relation.fromNode).data();
+ let toNode:Models.Graph.CompositionCiNodeBase = cy.getElementById(relation.toNode).data() ;
+
+ if ((relation.fromNode && fromNode.isUcpePart) || (relation.toNode && toNode.isUcpePart )) { //Link from or to node inside ucpe
+
+ if (singleRelation && singleRelation.relationship.type && singleRelation.relationship.type == 'tosca.relationships.HostedOn') {
+ newRelation = new Models.LinkUcpeHost(relation, singleRelation);
+ } else if (singleRelation.relationship.type && _.includes(singleRelation.relationship.type.toLowerCase(), 'link')) {
+ newRelation = new Models.CompositionCiVlUcpeLink(relation, fromNode.isUcpePart, singleRelation);
+ } else {
+ newRelation = new Models.CompositionCiUcpeLink(relation, fromNode.isUcpePart, singleRelation);
+ }
+ } else if (singleRelation.relationship.type && _.includes(singleRelation.relationship.type.toLowerCase(), 'link')) {
+ newRelation = new Models.CompositionCiVLink(relation, singleRelation);
+ } else {
+ newRelation = new Models.CompositionCiSimpleLink(relation, singleRelation);
+ }
+
+ return newRelation;
+ };
+
+ public createUcpeHostLink = (relation:Models.RelationshipModel):Models.LinkUcpeHost => {
+ return new Models.LinkUcpeHost(relation);
+ };
+
+ public createVLLink = (relation:Models.RelationshipModel):Models.CompositionCiVLink => {
+ return new Models.CompositionCiVLink(relation);
+ }
+
+
+ public createModuleGraphLinks= (relation:Models.RelationshipModel, singleRelation:Models.Relationship):Models.ModuleCiLinkBase => {
+
+ let newRelation:Models.ModuleCiLinkBase;
+
+ if (_.includes(singleRelation.relationship.type.toLowerCase(), 'link')) {
+ newRelation = new Models.ModuleCiVlLink(relation, singleRelation);
+ } else {
+ newRelation = new Models.ModuleCiLinkBase(relation, singleRelation);
+ }
+
+ return newRelation;
+ };
+
+ }
+}
diff --git a/catalog-ui/app/scripts/models/graph/graph-links/module-graph-links/module-ci-link-base.ts b/catalog-ui/app/scripts/models/graph/graph-links/module-graph-links/module-ci-link-base.ts
new file mode 100644
index 0000000000..b85e7673f5
--- /dev/null
+++ b/catalog-ui/app/scripts/models/graph/graph-links/module-graph-links/module-ci-link-base.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=========================================================
+ */
+/**
+ * Created by obarda on 6/29/2016.
+ */
+/// <reference path="../../../../references"/>
+module Sdc.Models {
+
+ export interface IModuleCiLinkBase extends ICommonCiLinkBase{
+
+ }
+
+ export class ModuleCiLinkBase extends CommonCiLinkBase implements IModuleCiLinkBase {
+
+ constructor(relation?:RelationshipModel, singleRelationship?:Models.Relationship) {
+ super(relation, singleRelationship);
+ this.color = Utils.Constants.GraphColors.BASE_LINK;
+ }
+
+ }
+}
diff --git a/catalog-ui/app/scripts/models/graph/graph-links/module-graph-links/module-ci-vl-link.ts b/catalog-ui/app/scripts/models/graph/graph-links/module-graph-links/module-ci-vl-link.ts
new file mode 100644
index 0000000000..a421610792
--- /dev/null
+++ b/catalog-ui/app/scripts/models/graph/graph-links/module-graph-links/module-ci-vl-link.ts
@@ -0,0 +1,37 @@
+/*-
+ * ============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 6/29/2016.
+ */
+/// <reference path="../../../../references"/>
+module Sdc.Models {
+
+ export interface IModuleCiVlLink extends ICommonCiLinkBase{
+
+ }
+
+ export class ModuleCiVlLink extends CommonCiLinkBase implements IModuleCiVlLink {
+
+ constructor(relation?:RelationshipModel, singleRelationship?:Models.Relationship) {
+ super(relation, singleRelationship);
+ this.color = Utils.Constants.GraphColors.VL_LINK;
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/models/graph/graphTooltip.ts b/catalog-ui/app/scripts/models/graph/graphTooltip.ts
new file mode 100644
index 0000000000..08a85e1126
--- /dev/null
+++ b/catalog-ui/app/scripts/models/graph/graphTooltip.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.Models {
+ 'use strict';
+
+ export class GraphTooltip{
+ position: Cy.Position;
+ isShow:boolean;
+ text:string;
+
+ constructor();
+ constructor(position: Cy.Position, isShow:boolean, text: string);
+ constructor(position?: Cy.Position, isShow?:boolean, text?: string) {
+ this.position = position;
+ this.isShow = isShow;
+ this.text = text;
+ }
+ }
+}
+
diff --git a/catalog-ui/app/scripts/models/graph/link-menu.ts b/catalog-ui/app/scripts/models/graph/link-menu.ts
new file mode 100644
index 0000000000..606c392982
--- /dev/null
+++ b/catalog-ui/app/scripts/models/graph/link-menu.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.Models {
+ 'use strict';
+
+ export class LinkMenu {
+ position:Sdc.Models.Graph.Point;
+ isShow:boolean;
+ link:Cy.CollectionFirstEdge;
+
+ constructor();
+ constructor(point:Sdc.Models.Graph.Point, isShow:boolean, link:Cy.CollectionFirstEdge);
+ constructor(point?:Sdc.Models.Graph.Point, isShow?:boolean, link?:Cy.CollectionFirstEdge) {
+ this.position = point ? point: new Sdc.Models.Graph.Point();
+ this.isShow = isShow ? isShow : false;
+ this.link = link ? link : null;
+ }
+ }
+}
+
diff --git a/catalog-ui/app/scripts/models/graph/match-relation.ts b/catalog-ui/app/scripts/models/graph/match-relation.ts
new file mode 100644
index 0000000000..8d864c675b
--- /dev/null
+++ b/catalog-ui/app/scripts/models/graph/match-relation.ts
@@ -0,0 +1,109 @@
+/*-
+ * ============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.Models {
+ 'use strict';
+
+ export class MatchBase {
+ requirement:Models.Requirement;
+ isFromTo:boolean;
+ fromNode:string;
+ toNode:string;
+
+ constructor(requirement:Models.Requirement, isFromTo:boolean, fromNode:string, toNode:string) {
+ this.requirement = requirement;
+ this.isFromTo = isFromTo;
+ this.fromNode = fromNode;
+ this.toNode = toNode;
+ }
+
+ public getDisplayText = (menuSide:string):string => {return '';};
+
+ public isOwner = (id:string):boolean => { return false; }
+
+ }
+
+ export class MatchReqToReq extends MatchBase {
+
+ secondRequirement:Models.Requirement;
+
+ constructor(requirement:Models.Requirement, secondRequirement:Models.Requirement, isFromTo:boolean, fromNode:string, toNode:string) {
+ super(requirement, isFromTo, fromNode, toNode);
+ this.secondRequirement = secondRequirement;
+ }
+
+ public getDisplayText = (menuSide:string):string => {
+ if ('left' == menuSide) {
+ return this.requirement.getFullTitle();
+ }
+ return this.secondRequirement.getFullTitle();
+ };
+
+ public isOwner = (id:string):boolean => {
+ return this.secondRequirement.ownerId === id || this.requirement.ownerId === id;
+ }
+ }
+
+ export class MatchReqToCapability extends MatchBase {
+
+ capability:Models.Capability;
+
+ constructor(requirement:Models.Requirement, capability:Models.Capability, isFromTo:boolean, fromNode:string, toNode:string) {
+ super(requirement, isFromTo, fromNode, toNode);
+ this.capability = capability;
+ }
+
+ public matchToRelation = ():Models.Relationship => {
+ let relationship:Models.Relationship = new Models.Relationship();
+ relationship.capability = this.capability.name;
+ relationship.capabilityOwnerId = this.capability.ownerId;
+ relationship.capabilityUid = this.capability.uniqueId;
+ relationship.relationship = new Models.RelationType(this.capability.type);
+ relationship.requirement = this.requirement.name;
+ relationship.requirementOwnerId = this.requirement.ownerId;
+ relationship.requirementUid = this.requirement.uniqueId;
+ return relationship;
+ };
+
+
+ public getDisplayText = (menuSide:string):string => {
+ if (this.isFromTo && 'left' == menuSide || !this.isFromTo && 'right' == menuSide) {
+ return this.requirement.getFullTitle();
+ }
+ return this.capability.getFullTitle();
+
+ };
+
+ public isOwner = (id:string):boolean => {
+ return this.capability.ownerId === id || this.requirement.ownerId === id;
+ };
+
+
+ public matchToRelationModel = ():Models.RelationshipModel => {
+ let relationshipModel:Models.RelationshipModel = new Models.RelationshipModel();
+ let relationship:Models.Relationship = this.matchToRelation();
+ relationshipModel.setRelationshipModelParams(this.fromNode, this.toNode, [relationship]);
+ return relationshipModel;
+ };
+ }
+
+}
+
+
diff --git a/catalog-ui/app/scripts/models/graph/nodes/base-common-node.ts b/catalog-ui/app/scripts/models/graph/nodes/base-common-node.ts
new file mode 100644
index 0000000000..e1957e61aa
--- /dev/null
+++ b/catalog-ui/app/scripts/models/graph/nodes/base-common-node.ts
@@ -0,0 +1,73 @@
+/*-
+ * ============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 6/29/2016.
+ */
+/// <reference path="../../../references"/>
+
+module Sdc.Models.Graph {
+ 'use strict';
+
+ export abstract class CommonNodeBase {
+
+ public displayName:string;
+ public name:string;
+ public img:string;
+ public certified:boolean;
+ public isGroup:boolean;
+ public imagesPath: string;
+ public isDraggable: boolean; //we need to to manage manually the dragging on the graph inside groups (ucpe-cp is not draggable)
+
+ //cytoscape fields
+ public id:string;
+ public type:string; //type is for the edge edition extension, by type we put the green plus icon in position
+ public isSdcElement:boolean; //this fields is in order to filter sdc elements from all extensions elements
+ public classes: string;
+ public parent: string;
+ public allowConnection: boolean; //this is for egeEdition extension in order to decide if connection to a node is available
+
+ constructor() {
+
+ this.imagesPath = Services.AngularJSBridge.getAngularConfig().imagesPath;
+ this.type = "basic-node";
+ this.isSdcElement = true;
+ this.isDraggable = true;
+ this.allowConnection = true;
+ }
+
+ public updateNameForDisplay =() => {
+ let context = document.createElement("canvas").getContext("2d");
+ context.font = "13px Arial";
+
+ if (63 < context.measureText(this.name).width) {
+ let newLen = this.name.length - 3;
+ let newName = this.name.substring(0, newLen);
+
+ while (60 < (context.measureText(newName).width)) {
+ newName = newName.substring(0, (--newLen));
+ }
+ this.displayName = newName + '...';
+ return;
+ }
+
+ this.displayName = this.name;
+ };
+ }
+}
diff --git a/catalog-ui/app/scripts/models/graph/nodes/common-ci-node-base.ts b/catalog-ui/app/scripts/models/graph/nodes/common-ci-node-base.ts
new file mode 100644
index 0000000000..1597650654
--- /dev/null
+++ b/catalog-ui/app/scripts/models/graph/nodes/common-ci-node-base.ts
@@ -0,0 +1,46 @@
+/*-
+ * ============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.Models.Graph {
+
+ export abstract class CommonCINodeBase extends CommonNodeBase {
+
+ public certified:boolean;
+ public template:string;
+ public componentInstance:Models.ComponentsInstances.ComponentInstance;
+ public group:string;
+
+ constructor(instance:Models.ComponentsInstances.ComponentInstance) {
+ super();
+ this.componentInstance = instance;
+ this.id = this.componentInstance.uniqueId;
+ this.name = this.componentInstance.name;
+ this.img = '';
+ this.certified = this.isCertified(this.componentInstance.componentVersion);
+ this.displayName = instance.name;
+ }
+
+ private isCertified(version:string):boolean {
+ return 0 === (parseFloat(version)) % 1;
+ }
+
+ }
+}
+
diff --git a/catalog-ui/app/scripts/models/graph/nodes/composition-graph-nodes/composition-ci-node-base.ts b/catalog-ui/app/scripts/models/graph/nodes/composition-graph-nodes/composition-ci-node-base.ts
new file mode 100644
index 0000000000..5f4c0df3c2
--- /dev/null
+++ b/catalog-ui/app/scripts/models/graph/nodes/composition-graph-nodes/composition-ci-node-base.ts
@@ -0,0 +1,72 @@
+/*-
+ * ============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.Models.Graph {
+
+ export interface ICompositionCiNodeBase {
+
+ }
+
+
+ export abstract class CompositionCiNodeBase extends CommonCINodeBase implements ICompositionCiNodeBase {
+
+ public textPosition: string; //need to move to cp UCPE
+ public isUcpe: boolean;
+ public isInsideGroup: boolean;
+ public isUcpePart: boolean;
+
+ constructor(instance: Models.ComponentsInstances.ComponentInstance,
+ public imageCreator: Utils.ImageCreatorService) {
+ super(instance);
+ this.init();
+ }
+
+ private init() {
+
+ this.displayName = this.getDisplayName();
+ this.isUcpe = false;
+ this.isGroup = false;
+ this.isUcpePart = false;
+ this.isInsideGroup = false;
+
+ }
+
+ public initImage(node: Cy.Collection): string {
+
+ this.imageCreator.getImageBase64(this.imagesPath + Utils.Constants.ImagesUrl.RESOURCE_ICONS + this.componentInstance.icon + '.png',
+ this.imagesPath + Utils.Constants.ImagesUrl.RESOURCE_ICONS + 'uncertified.png')
+ .then(imageBase64 => {
+ this.img = imageBase64;
+ node.style({'background-image': this.img});
+ });
+
+ return this.img;
+ }
+
+ protected getDisplayName(): string {
+
+ let graphResourceName = Services.AngularJSBridge.getFilter('graphResourceName');
+ let resourceName = Services.AngularJSBridge.getFilter('resourceName');
+ return graphResourceName(resourceName(this.componentInstance.name));
+ }
+
+ }
+}
diff --git a/catalog-ui/app/scripts/models/graph/nodes/composition-graph-nodes/composition-ci-node-cp.ts b/catalog-ui/app/scripts/models/graph/nodes/composition-graph-nodes/composition-ci-node-cp.ts
new file mode 100644
index 0000000000..6286c8245d
--- /dev/null
+++ b/catalog-ui/app/scripts/models/graph/nodes/composition-graph-nodes/composition-ci-node-cp.ts
@@ -0,0 +1,48 @@
+/*-
+ * ============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.Models.Graph {
+
+ export class CompositionCiNodeCp extends CompositionCiNodeBase {
+
+ constructor(instance:Models.ComponentsInstances.ComponentInstance,
+ imageCreator: Utils.ImageCreatorService) {
+ super(instance, imageCreator);
+ this.initCp();
+ }
+
+ private initCp():void {
+ let sdcConfig = Services.AngularJSBridge.getAngularConfig();
+ this.img = sdcConfig.imagesPath + Utils.Constants.ImagesUrl.RESOURCE_ICONS + this.componentInstance.icon + '.png';
+ this.type = "basic-small-node";
+ //if the cp from type cpEndPointInstances create with another template
+ if(sdcConfig.cpEndPointInstances.indexOf(this.componentInstance.icon) > -1){
+ this.classes = 'cp-end-point-node';
+ }else {
+ this.classes = 'cp-node';
+ }
+ if(!this.certified) {
+ this.classes = this.classes + ' not-certified';
+ }
+
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/models/graph/nodes/composition-graph-nodes/composition-ci-node-service.ts b/catalog-ui/app/scripts/models/graph/nodes/composition-graph-nodes/composition-ci-node-service.ts
new file mode 100644
index 0000000000..41bf0cef98
--- /dev/null
+++ b/catalog-ui/app/scripts/models/graph/nodes/composition-graph-nodes/composition-ci-node-service.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.Models.Graph {
+
+ export class CompositionCiNodeService extends CompositionCiNodeBase {
+
+ constructor(instance:Models.ComponentsInstances.ComponentInstance,
+ imageCreator: Utils.ImageCreatorService) {
+ super(instance, imageCreator);
+ this.initService();
+ }
+
+ private initService():void {
+
+ this.img = this.imagesPath + Utils.Constants.ImagesUrl.SERVICE_ICONS + this.componentInstance.icon + '.png';
+ this.classes = 'service-node'
+ if(!this.certified) {
+ this.classes = this.classes + ' not-certified';
+ }
+
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/models/graph/nodes/composition-graph-nodes/composition-ci-node-ucpe-cp.ts b/catalog-ui/app/scripts/models/graph/nodes/composition-graph-nodes/composition-ci-node-ucpe-cp.ts
new file mode 100644
index 0000000000..9123ff7224
--- /dev/null
+++ b/catalog-ui/app/scripts/models/graph/nodes/composition-graph-nodes/composition-ci-node-ucpe-cp.ts
@@ -0,0 +1,39 @@
+/*-
+ * ============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.Models.Graph {
+
+ export class CompositionCiNodeUcpeCp extends CompositionCiNodeCp {
+
+ constructor(instance:Models.ComponentsInstances.ComponentInstance,
+ imageCreator: Utils.ImageCreatorService) {
+ super(instance, imageCreator);
+ this.isUcpePart = true;
+ this.classes = 'ucpe-cp'; // the css class for the node
+ this.parent = instance.uniqueId;
+ this.type = 'ucpe-cp-node'; //the type is for the handle (plus icon) extension
+ this.isDraggable = false;
+ }
+
+
+
+ }
+}
diff --git a/catalog-ui/app/scripts/models/graph/nodes/composition-graph-nodes/composition-ci-node-ucpe.ts b/catalog-ui/app/scripts/models/graph/nodes/composition-graph-nodes/composition-ci-node-ucpe.ts
new file mode 100644
index 0000000000..bc91e004f4
--- /dev/null
+++ b/catalog-ui/app/scripts/models/graph/nodes/composition-graph-nodes/composition-ci-node-ucpe.ts
@@ -0,0 +1,50 @@
+/*-
+ * ============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.Models.Graph {
+
+ export class NodeUcpe extends CompositionCiNodeBase {
+ constructor(instance:Models.ComponentsInstances.ComponentInstance,
+ imageCreator:Utils.ImageCreatorService) {
+ super(instance, imageCreator);
+ this.initUcpe();
+ }
+
+ private initUcpe():void {
+ this.isUcpe = true;
+ this.isGroup = true;
+ this.isUcpePart = true;
+ this.classes = 'ucpe-node';
+ this.type = 'ucpe-node';
+ this.allowConnection = false;
+
+ if (!this.certified) {
+ this.classes = this.classes + ' not-certified-ucpe';
+ }
+ }
+
+ }
+}
+
+
+
+
+
diff --git a/catalog-ui/app/scripts/models/graph/nodes/composition-graph-nodes/composition-ci-node-vf.ts b/catalog-ui/app/scripts/models/graph/nodes/composition-graph-nodes/composition-ci-node-vf.ts
new file mode 100644
index 0000000000..d090960046
--- /dev/null
+++ b/catalog-ui/app/scripts/models/graph/nodes/composition-graph-nodes/composition-ci-node-vf.ts
@@ -0,0 +1,41 @@
+/*-
+ * ============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.Models.Graph {
+
+ export class CompositionCiNodeVf extends CompositionCiNodeBase {
+
+ constructor(instance: Models.ComponentsInstances.ComponentInstance,
+ imageCreator: Utils.ImageCreatorService) {
+ super(instance, imageCreator);
+ this.initVf();
+ }
+
+ private initVf(): void {
+ this.img = this.imagesPath + Utils.Constants.ImagesUrl.RESOURCE_ICONS + this.componentInstance.icon + '.png';
+ this.classes = 'vf-node';
+ if(!this.certified) {
+ this.classes = this.classes + ' not-certified';
+ }
+ }
+
+ }
+}
diff --git a/catalog-ui/app/scripts/models/graph/nodes/composition-graph-nodes/composition-ci-node-vfc.ts b/catalog-ui/app/scripts/models/graph/nodes/composition-graph-nodes/composition-ci-node-vfc.ts
new file mode 100644
index 0000000000..04f45c87fb
--- /dev/null
+++ b/catalog-ui/app/scripts/models/graph/nodes/composition-graph-nodes/composition-ci-node-vfc.ts
@@ -0,0 +1,33 @@
+/*-
+ * ============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.Models.Graph {
+ export class CompositionCiNodeVfc extends CompositionCiNodeBase {
+ constructor(instance:Models.ComponentsInstances.ComponentInstance, imageCreator: Utils.ImageCreatorService) {
+ super(instance, imageCreator);
+ this.initVfc();
+ }
+
+ private initVfc():void {
+ this.img = this.imagesPath + Utils.Constants.ImagesUrl.RESOURCE_ICONS+ this.componentInstance.icon + '.png';
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/models/graph/nodes/composition-graph-nodes/composition-ci-node-vl.ts b/catalog-ui/app/scripts/models/graph/nodes/composition-graph-nodes/composition-ci-node-vl.ts
new file mode 100644
index 0000000000..ed9a0d9d87
--- /dev/null
+++ b/catalog-ui/app/scripts/models/graph/nodes/composition-graph-nodes/composition-ci-node-vl.ts
@@ -0,0 +1,54 @@
+/*-
+ * ============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.Models.Graph {
+
+ export class CompositionCiNodeVl extends CompositionCiNodeBase {
+ private toolTipText:string;
+
+ constructor(instance:Models.ComponentsInstances.ComponentInstance, imageCreator: Utils.ImageCreatorService) {
+ super(instance, imageCreator);
+ this.initVl();
+
+ }
+
+ private initVl():void {
+ this.type = "basic-small-node";
+ this.toolTipText = 'Point to point';
+ let key:string = _.find(Object.keys(this.componentInstance.capabilities), (key)=> {
+ return _.includes(key.toLowerCase(), 'linkable');
+ });
+ let linkable = this.componentInstance.capabilities[key];
+ if (linkable) {
+ if ('UNBOUNDED' == linkable[0].maxOccurrences) {
+ this.toolTipText = 'Multi point';
+ }
+ }
+ this.img = this.imagesPath + Utils.Constants.ImagesUrl.RESOURCE_ICONS + 'vl.png';
+
+ this.classes = 'vl-node';
+ if(!this.certified) {
+ this.classes = this.classes + ' not-certified';
+ }
+ }
+
+ }
+}
diff --git a/catalog-ui/app/scripts/models/graph/nodes/modules-graph-nodes/module-node-base.ts b/catalog-ui/app/scripts/models/graph/nodes/modules-graph-nodes/module-node-base.ts
new file mode 100644
index 0000000000..cd6ab3ba85
--- /dev/null
+++ b/catalog-ui/app/scripts/models/graph/nodes/modules-graph-nodes/module-node-base.ts
@@ -0,0 +1,52 @@
+/*-
+ * ============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 6/29/2016.
+ */
+/// <reference path="../../../../references"/>
+
+module Sdc.Models.Graph {
+ 'use strict';
+
+ export interface IModuleNodeBase {
+ }
+
+ export class ModuleNodeBase extends CommonNodeBase implements IModuleNodeBase {
+
+ module:Module;
+
+ constructor(module:Module) {
+ super();
+ this.module = module;
+ this.init();
+ }
+
+ private init() {
+
+ this.id = this.module.uniqueId;
+ this.name = this.module.name;
+ this.displayName = this.module.name;
+ this.isGroup = true;
+ this.img = Utils.Constants.IMAGE_PATH + Utils.Constants.ImagesUrl.MODULE_ICON;
+ this.classes = "module-node";
+
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/models/graph/nodes/nodes-factory.ts b/catalog-ui/app/scripts/models/graph/nodes/nodes-factory.ts
new file mode 100644
index 0000000000..b19b1a7261
--- /dev/null
+++ b/catalog-ui/app/scripts/models/graph/nodes/nodes-factory.ts
@@ -0,0 +1,63 @@
+/*-
+ * ============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.Utils {
+ 'use strict';
+
+ export class NodesFactory {
+
+ constructor(
+ private imageCreator:ImageCreatorService) {
+ }
+
+ public createNode = (instance:Models.ComponentsInstances.ComponentInstance):Models.Graph.CompositionCiNodeBase => {
+
+ if (instance.isUcpe()) {
+ return new Models.Graph.NodeUcpe(instance, this.imageCreator);
+ }
+ if (instance.originType === Utils.Constants.ComponentType.SERVICE) {
+ return new Models.Graph.CompositionCiNodeService(instance, this.imageCreator);
+ }
+ if (instance.originType === Utils.Constants.ResourceType.CP) {
+ return new Models.Graph.CompositionCiNodeCp(instance, this.imageCreator);
+ }
+ if (instance.originType === Utils.Constants.ResourceType.VL) {
+ return new Models.Graph.CompositionCiNodeVl(instance, this.imageCreator);
+ }
+
+ return new Models.Graph.CompositionCiNodeVf(instance, this.imageCreator);
+ };
+
+ public createModuleNode = (module:Models.Module):Models.Graph.ModuleNodeBase => {
+
+ return new Models.Graph.ModuleNodeBase(module);
+ };
+
+ public createUcpeCpNode = (instance:Models.ComponentsInstances.ComponentInstance):Models.Graph.CompositionCiNodeCp => {
+
+
+ return new Models.Graph.CompositionCiNodeUcpeCp(instance, this.imageCreator);
+ }
+ }
+
+ NodesFactory.$inject = [
+ 'ImageCreatorService'
+ ];
+}
diff --git a/catalog-ui/app/scripts/models/graph/point.ts b/catalog-ui/app/scripts/models/graph/point.ts
new file mode 100644
index 0000000000..0efd4c6040
--- /dev/null
+++ b/catalog-ui/app/scripts/models/graph/point.ts
@@ -0,0 +1,26 @@
+/**
+ * Created by obarda on 11/7/2016.
+ */
+/// <reference path="../../references"/>
+
+module Sdc.Models.Graph {
+
+
+ export class Point {
+ /**
+ * The two-argument constructor produces the Point(x, y).
+ * @param {number} x
+ * @param {number} y
+ */
+ constructor(x?:number, y?:number) {
+ this.x = x || 0;
+ this.y = y || 0;
+ }
+
+ /**Gets or sets the x value of the Point.*/
+ x:number;
+
+ /**Gets or sets the y value of the Point.*/
+ y:number;
+ }
+} \ No newline at end of file
diff --git a/catalog-ui/app/scripts/models/graph/relationMenuObjects.ts b/catalog-ui/app/scripts/models/graph/relationMenuObjects.ts
new file mode 100644
index 0000000000..266ed76cfa
--- /dev/null
+++ b/catalog-ui/app/scripts/models/graph/relationMenuObjects.ts
@@ -0,0 +1,138 @@
+/*-
+ * ============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.Models {
+ 'use strict';
+
+
+ export class RelationMenuDirectiveObj {
+
+ fromNode:Models.Graph.CompositionCiNodeBase;
+ toNode:Models.Graph.CompositionCiNodeBase;
+ // modelLinks:Array<Models.CompositionCiLinkBase>;
+ mp2mpVL:Models.Components.Component;
+ p2pVL:Models.Components.Component;
+ menuPosition: Cy.Position;
+ rightSideLink:GraphLinkMenuSide;
+ leftSideLink:GraphLinkMenuSide;
+ selectionText:string;
+ vlType:string;
+
+ constructor(fromNode:Models.Graph.CompositionCiNodeBase, toNode:Models.Graph.CompositionCiNodeBase, mp2mpVL:Models.Components.Component, p2pVL:Models.Components.Component, menuPosition:Cy.Position, possibleRelations:Array<Models.MatchBase>) {
+ this.fromNode = fromNode;
+ this.toNode = toNode;
+ // this.modelLinks = modelLinks;
+ this.mp2mpVL = mp2mpVL;
+ this.p2pVL = p2pVL;
+ this.menuPosition = menuPosition;
+ this.leftSideLink = new GraphLinkMenuSide(this.fromNode.componentInstance);
+ this.rightSideLink = new GraphLinkMenuSide(this.toNode.componentInstance);
+ this.selectionText = '';
+ this.vlType = null;
+
+ possibleRelations.forEach((match:any) => {
+
+ let reqObjKey: string = match.requirement.ownerName + match.requirement.uniqueId;
+ let capObjKey: string = match.secondRequirement ? match.secondRequirement.ownerName + match.secondRequirement.uniqueId
+ : match.capability.ownerName + match.capability.uniqueId;
+
+ if (match.fromNode === this.leftSideLink.componentInstance.uniqueId) {
+ //init the left side requirements Array
+ if (!this.leftSideLink.requirements[reqObjKey]) {
+ this.leftSideLink.requirements[reqObjKey] = [];
+ }
+ //push the match to fromNode object (from node is always the requirement)
+ this.leftSideLink.requirements[reqObjKey].push(match);
+
+ if (match instanceof Models.MatchReqToReq) {
+ //init the right side requirements Array
+ if (!this.rightSideLink.requirements[capObjKey]) {
+ this.rightSideLink.requirements[capObjKey] = [];
+ }
+ this.rightSideLink.requirements[capObjKey].push(match);
+ } else {
+ //init the right side capabilities Array
+ if (!this.rightSideLink.capabilities[capObjKey]) {
+ this.rightSideLink.capabilities[capObjKey] = [];
+ }
+ //add to array
+ this.rightSideLink.capabilities[capObjKey].push(match);
+ }
+
+ } else {
+ if (!this.rightSideLink.requirements[reqObjKey]) {
+ this.rightSideLink.requirements[reqObjKey] = [];
+ }
+ this.rightSideLink.requirements[reqObjKey].push(match);
+
+ if (!this.leftSideLink.capabilities[capObjKey]) {
+ this.leftSideLink.capabilities[capObjKey] = [];
+ }
+ this.leftSideLink.capabilities[capObjKey].push(match);
+ }
+ });
+
+ }
+ }
+
+
+ export class GraphLinkMenuSide {
+ public componentInstance:Models.ComponentsInstances.ComponentInstance;
+ public selectedMatch:Array<any>; //match array returned by function in utils
+ public requirements:any; //array of matches returned by function in utils
+ public capabilities:any; //array of matches returned by function in utils
+
+ constructor(componentInstance:Models.ComponentsInstances.ComponentInstance) {
+ this.componentInstance = componentInstance;
+ this.capabilities = {};
+ this.requirements = {};
+ }
+
+ public selectMatchArr(matchArr:Array<Models.MatchBase>):void {
+ if (this.selectedMatch === matchArr) {
+ this.selectedMatch = undefined;
+ } else {
+ this.selectedMatch = matchArr;
+ }
+ }
+
+
+ //TODO move to match object
+ public getPreviewText(showReq:boolean):string {
+ if (!this.selectedMatch) {
+ return '';
+ }
+
+ let match:any = this.selectedMatch[0];
+ if (showReq) {
+ return match.requirement.ownerName + ': ' + match.requirement.name +
+ ': [' + match.requirement.minOccurrences + ', ' + match.requirement.maxOccurrences + ']';
+ } else if (match.secondRequirement) {
+ return match.secondRequirement.ownerName + ': ' + match.secondRequirement.name +
+ ': [' + match.secondRequirement.minOccurrences + ', ' + match.secondRequirement.maxOccurrences + ']';
+ }
+ else {
+ return match.capability.ownerName + ': ' + match.capability.name +
+ ': [' + match.capability.minOccurrences + ', ' + match.capability.maxOccurrences + ']';
+ }
+ }
+ }
+
+}
diff --git a/catalog-ui/app/scripts/models/graph/relationship.ts b/catalog-ui/app/scripts/models/graph/relationship.ts
new file mode 100644
index 0000000000..e0dfbbd6d1
--- /dev/null
+++ b/catalog-ui/app/scripts/models/graph/relationship.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.Models {
+ 'use strict';
+
+ export class RelationshipModel {
+ fromNode:string;
+ toNode:string;
+ relationships:Array<Relationship>;
+
+ constructor(relationshipModel?:RelationshipModel, singleRelationship?:Relationship) {
+ if(relationshipModel){
+ this.fromNode = relationshipModel.fromNode;
+ this.toNode = relationshipModel.toNode;
+ this.relationships = [];
+ if (relationshipModel.relationships && !singleRelationship) {
+ _.forEach(relationshipModel.relationships, (relation:Models.Relationship):void => {
+ this.relationships.push(new Models.Relationship(relation));
+ });
+ }else if(singleRelationship){
+ this.relationships.push(singleRelationship);
+ }
+ }
+ }
+
+ public setRelationshipModelParams (fromNode: string, toNode:string, relationships:Array<Relationship>) {
+ this.fromNode = fromNode;
+ this.toNode = toNode;
+ this.relationships = relationships;
+ }
+ }
+
+ export class RelationType {
+ type:string;
+
+ constructor(type?:string) {
+ if(type){
+ this.type = type;
+ }
+ }
+ }
+
+ export class Relationship {
+ capability:string;
+ capabilityOwnerId:string;
+ capabilityUid:string;
+ relationship:RelationType;
+ requirement:string;
+ requirementOwnerId:string;
+ requirementUid:string;
+
+ constructor(relationship?:Models.Relationship) {
+ if(relationship) {
+ this.capability = relationship.capability;
+ this.capabilityOwnerId = relationship.capabilityOwnerId;
+ this.capabilityUid = relationship.capabilityUid;
+ this.relationship = new RelationType(relationship.relationship.type);
+ this.requirement = relationship.requirement;
+ this.requirementOwnerId = relationship.requirementOwnerId;
+ this.requirementUid = relationship.requirementUid;
+ } else {
+ this.relationship = new RelationType();
+ }
+
+ }
+
+ //public setRelationProperties = (capability:string, capabilityOwnerId:string, capabilityUid:string, relationship:RelationType, requirement:string, requirementOwnerId:string, requirementUid:string )=>{
+ // this.capability = capability;
+ // this.capabilityOwnerId = capabilityOwnerId;
+ // this.capabilityUid = capabilityUid;
+ // this.relationship = relationship;
+ // this.requirement =requirement;
+ // this.requirementOwnerId = requirementOwnerId;
+ // this.requirementUid = requirementUid;
+ //}
+
+
+ public setRelationProperties = (capability:Models.Capability, requirement:Models.Requirement)=>{
+ this.capability = capability.name;
+ this.capabilityOwnerId = capability.ownerId;
+ this.capabilityUid = capability.uniqueId;
+ this.relationship = new Models.RelationType(capability.type);
+ this.requirement = requirement.name;
+ this.requirementOwnerId = requirement.ownerId;
+ this.requirementUid = requirement.uniqueId;
+ };
+
+ }
+}
diff --git a/catalog-ui/app/scripts/models/inputs.ts b/catalog-ui/app/scripts/models/inputs.ts
new file mode 100644
index 0000000000..68e26e246e
--- /dev/null
+++ b/catalog-ui/app/scripts/models/inputs.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=========================================================
+ */
+/**
+ * Created by obarda on 8/24/2016.
+ */
+/// <reference path="../references"/>
+module Sdc.Models {
+ 'use strict';
+
+ export class InputModel {
+
+ //server data
+ uniqueId:string;
+ name:string;
+ type:string;
+ password:boolean;
+ required:boolean;
+ definition:boolean;
+ parentUniqueId:string;
+ description:string;
+ componentInstanceName:string;
+ componentInstanceId:string;
+
+ //costom properties
+ isNew: boolean;
+ properties:Array<Models.PropertyModel>;
+ inputs:Array<Models.InputModel>;
+ isAlreadySelected: boolean;
+ filterTerm: string;
+
+ constructor(input:InputModel) {
+ if (input) {
+ this.uniqueId = input.uniqueId;
+ this.name = input.name;
+ this.type = input.type;
+ this.description = input.description;
+ this.password = input.password;
+ this.required = input.required;
+ this.definition = input.definition;
+ this.parentUniqueId = input.parentUniqueId;
+ this.description = input.description;
+ this.componentInstanceName = input.componentInstanceName;
+ this.componentInstanceId = input.componentInstanceId;
+ this.filterTerm = this.name + ' ' + this.description + ' ' + this.type + ' ' + this.componentInstanceName;
+ }
+ }
+
+ public toJSON = ():any => {
+ this.isNew = undefined;
+ this.properties = undefined;
+ this.inputs = undefined;
+ this.isAlreadySelected = undefined;
+ this.filterTerm = undefined;
+ return this;
+ };
+ }
+}
diff --git a/catalog-ui/app/scripts/models/instance-inputs-properties-map.ts b/catalog-ui/app/scripts/models/instance-inputs-properties-map.ts
new file mode 100644
index 0000000000..2c67dfd718
--- /dev/null
+++ b/catalog-ui/app/scripts/models/instance-inputs-properties-map.ts
@@ -0,0 +1,39 @@
+/*-
+ * ============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 9/12/2016.
+ */
+/// <reference path="../references"/>
+module Sdc.Models {
+ 'use strict';
+
+ export class InstanceInputsPropertiesMapData {
+ [instanceId:string]: Array<PropertyModel>;
+ }
+
+ export class InstanceInputsPropertiesMap {
+ componentInstanceInputsProperties:InstanceInputsPropertiesMapData;
+
+ constructor(componentInstanceInputsPropertiesMapData:InstanceInputsPropertiesMapData) {
+ this.componentInstanceInputsProperties = componentInstanceInputsPropertiesMapData;
+ }
+ }
+
+}
diff --git a/catalog-ui/app/scripts/models/instances-inputs-map.ts b/catalog-ui/app/scripts/models/instances-inputs-map.ts
new file mode 100644
index 0000000000..1643a125ae
--- /dev/null
+++ b/catalog-ui/app/scripts/models/instances-inputs-map.ts
@@ -0,0 +1,39 @@
+/*-
+ * ============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 9/12/2016.
+ */
+/// <reference path="../references"/>
+module Sdc.Models {
+ 'use strict';
+
+ export class InstancesInputsMapData {
+ [instanceId:string]: Array<InputModel>;
+ }
+
+ export class InstancesInputsMap {
+ componentInstanceInputsMap:InstancesInputsMapData;
+
+ constructor(componentInstanceInputsMapData:InstancesInputsMapData) {
+ this.componentInstanceInputsMap = componentInstanceInputsMapData;
+ }
+ }
+
+}
diff --git a/catalog-ui/app/scripts/models/left-panel.ts b/catalog-ui/app/scripts/models/left-panel.ts
new file mode 100644
index 0000000000..a47170c7c2
--- /dev/null
+++ b/catalog-ui/app/scripts/models/left-panel.ts
@@ -0,0 +1,33 @@
+/*-
+ * ============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.Models {
+ 'use strict';
+
+ export class LeftPanelModel {
+ numberOfElements:number;
+ sortedCategories:any;
+
+ constructor() {
+ this.numberOfElements = 0;
+ this.sortedCategories = {};
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/models/member.ts b/catalog-ui/app/scripts/models/member.ts
new file mode 100644
index 0000000000..21dc907333
--- /dev/null
+++ b/catalog-ui/app/scripts/models/member.ts
@@ -0,0 +1,39 @@
+/*-
+ * ============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 8/2/2016.
+ */
+
+/// <reference path="../references"/>
+module Sdc.Models {
+ 'use strict';
+
+ export class Members {
+
+ [index: string]: string;
+
+ constructor(members?:Members) {
+ _.forEach(members, (memberId:string, index) => {
+ this[index] = memberId;
+ });
+ }
+ }
+}
+
diff --git a/catalog-ui/app/scripts/models/modules/base-module.ts b/catalog-ui/app/scripts/models/modules/base-module.ts
new file mode 100644
index 0000000000..2df52cc907
--- /dev/null
+++ b/catalog-ui/app/scripts/models/modules/base-module.ts
@@ -0,0 +1,108 @@
+/*-
+ * ============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 2/4/2016.
+ */
+/// <reference path="../../references"/>
+module Sdc.Models {
+ 'use strict';
+
+ export class Module {
+
+ public name:string;
+ public groupUUID:string;
+ public invariantUUID:string;
+ public propertyValueCounter:number;
+ public type:string;
+ public typeUid:string;
+ public uniqueId:string;
+ public version: string;
+ public artifacts: Array<string> | Array<Models.ArtifactModel>;
+ public artifactsUuid: Array<string>;
+ public properties: Array<Models.PropertyModel>;
+ public members: Array<string>;
+
+
+ constructor(module?: Module) {
+ if(module) {
+ this.name = module.name;
+ this.groupUUID = module.groupUUID;
+ this.invariantUUID = module.invariantUUID;
+ this.propertyValueCounter = module.propertyValueCounter;
+ this.type = module.type;
+ this.typeUid = module.typeUid;
+ this.uniqueId = module.uniqueId;
+ this.version = module.version;
+ this.artifacts = module.artifacts;
+ this.artifactsUuid = module.artifactsUuid;
+ this.properties = Utils.CommonUtils.initProperties(module.properties);
+ this.members = module.members;
+
+ this.name = this.name.replace(/:/g, '..');
+
+ }
+ }
+ }
+
+ export class DisplayModule extends Module {
+
+ isBase: string;
+ artifacts:Array<Models.ArtifactModel>;
+
+ //custom properties
+ public vfInstanceName: string;
+ public heatName: string;
+ public moduleName: string;
+
+ constructor(displayModule?:Models.DisplayModule) {
+ super(displayModule);
+
+ this.isBase = displayModule.isBase;
+ this.initArtifactsForDisplay(displayModule.artifacts);
+
+ //splitting module name for display and edit
+ let splitName:Array<string> = this.name.split('..');
+ this.vfInstanceName = splitName[0];
+ this.heatName = splitName[1];
+ this.moduleName = splitName[2];
+ }
+
+ private initArtifactsForDisplay = (artifacts:Array<Models.ArtifactModel>):void => {
+ this.artifacts = new Array<Models.ArtifactModel>();
+ _.forEach(artifacts, (artifact:Models.ArtifactModel) => {
+ this.artifacts.push(new Models.ArtifactModel(artifact));
+ });
+ };
+
+ public updateName = ():void => {
+ this.name = this.vfInstanceName + '..' + this.heatName + '..' + this.moduleName;
+ };
+
+ public toJSON = ():any => {
+ this.vfInstanceName = undefined;
+ this.heatName = undefined;
+ this.moduleName = undefined;
+ this.isBase = undefined;
+ this.artifacts = undefined;
+ return this;
+ };
+ }
+}
diff --git a/catalog-ui/app/scripts/models/properties.ts b/catalog-ui/app/scripts/models/properties.ts
new file mode 100644
index 0000000000..679ca03b44
--- /dev/null
+++ b/catalog-ui/app/scripts/models/properties.ts
@@ -0,0 +1,176 @@
+/*-
+ * ============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.Models {
+ 'use strict';
+
+ export class PropertiesGroup {
+ constructor(propertiesObj?:Models.PropertiesGroup){
+ _.forEach(propertiesObj, (properties:Array<Models.PropertyModel>, instance) => {
+ this[instance] = [];
+ _.forEach(properties, (property:Models.PropertyModel):void => {
+ property.resourceInstanceUniqueId = instance;
+ property.readonly = true;
+ this[instance].push(new Models.PropertyModel(property));
+ });
+ });
+ }
+ }
+
+ export interface IPropertyModel {
+
+ //server data
+ uniqueId: string;
+ name: string;
+ constraints: Array<Object>;
+ defaultValue: string;
+ description: string;
+ password: boolean;
+ required: boolean;
+ type: string;
+ source: string;
+ parentUniqueId: string;
+ schema: Models.SchemaPropertyGroupModel;
+
+ //instance properties
+ value:string;
+ valueUniqueUid:string;
+ path:Array<string>;
+ rules:Array<Object>;
+
+ //custom properties
+ resourceInstanceUniqueId: string;
+ readonly: boolean;
+ simpleType: string;
+ }
+
+ export class PropertyModel implements IPropertyModel{
+
+ //server data
+ uniqueId:string;
+ name:string;
+ constraints:Array<Object>;
+ defaultValue:string;
+ description:string;
+ password:boolean;
+ required:boolean;
+ type:string;
+ source:string;
+ parentUniqueId:string;
+ schema: Models.SchemaPropertyGroupModel;
+
+ //instance properties
+ value:string;
+ valueUniqueUid:string;
+ path:Array<string>;
+ rules:Array<Object>;
+
+ //custom properties
+ resourceInstanceUniqueId:string;
+ readonly:boolean;
+ simpleType: string;
+ filterTerm: string;
+ isAlreadySelected: boolean;
+
+ constructor(property?:Models.PropertyModel) {
+ if (property) {
+ this.uniqueId = property.uniqueId;
+ this.name = property.name;
+ this.constraints = property.constraints;
+ this.defaultValue = property.defaultValue;
+ this.description = property.description;
+ this.password = property.password;
+ this.required = property.required;
+ this.type = property.type;
+ this.source = property.source;
+ this.parentUniqueId = property.parentUniqueId;
+ this.schema = property.schema;
+ this.value = property.value?property.value:property.defaultValue;
+ this.valueUniqueUid = property.valueUniqueUid;
+ this.path = property.path;
+ this.rules = property.rules;
+ this.resourceInstanceUniqueId = property.resourceInstanceUniqueId;
+ this.readonly = property.readonly;
+ this.simpleType = property.simpleType;
+
+
+ }
+
+ if(!this.schema || !this.schema.property) {
+ this.schema = new Models.SchemaPropertyGroupModel(new Models.SchemaProperty());
+ } else {
+ //forcing creating new object, so editing different one than the object in the table
+ this.schema = new Models.SchemaPropertyGroupModel(new Models.SchemaProperty(this.schema.property));
+ }
+ if(property) {
+ this.filterTerm = this.name + " " + (this.description||"") +" " + this.type;
+ if(this.schema.property && this.schema.property.type) {
+ this.filterTerm += " " +this.schema.property.type;
+ }
+ }
+ }
+
+ public convertToServerObject:Function = ():string => {
+ let serverObject = {};
+ let mapData = {
+ "type": this.type,
+ "required": this.required || false,
+ "defaultValue": this.defaultValue != "" && this.defaultValue != "[]" && this.defaultValue != "{}" ? this.defaultValue :null,
+ "description": this.description,
+ "constraints": this.constraints,
+ "isPassword": this.password || false,
+ "schema": this.schema,
+ "name": this.name
+ };
+ serverObject[this.name] = mapData;
+
+ return JSON.stringify(serverObject);
+ };
+
+
+ // public convertValueToView () {
+ // //unwrapping value {} or [] if type is complex
+ // if (this.defaultValue && (this.type === 'map' || this.type === 'list') &&
+ // ['[','{'].indexOf(this.defaultValue.charAt(0)) > -1 &&
+ // [']','}'].indexOf(this.defaultValue.slice(-1)) > -1) {
+ // this.defaultValue = this.defaultValue.slice(1, -1);
+ // }
+ //
+ // //also for value - for the modal in canvas
+ // if (this.value && (this.type === 'map' || this.type === 'list') &&
+ // ['[','{'].indexOf(this.value.charAt(0)) > -1 &&
+ // [']','}'].indexOf(this.value.slice(-1)) > -1) {
+ // this.value = this.value.slice(1, -1);
+ // }
+ // }
+
+ public toJSON = ():any => {
+ if(!this.resourceInstanceUniqueId){
+ this.value = undefined;
+ }
+ this.readonly = undefined;
+ this.resourceInstanceUniqueId = undefined;
+ this.simpleType = undefined;
+ this.value = this.value === "{}" || this.value === "[]" ? undefined: this.value;
+ this.defaultValue = this.defaultValue === "{}" || this.defaultValue === "[]" ? undefined: this.defaultValue;
+ return this;
+ };
+ }
+}
diff --git a/catalog-ui/app/scripts/models/requirement.ts b/catalog-ui/app/scripts/models/requirement.ts
new file mode 100644
index 0000000000..091bfc139e
--- /dev/null
+++ b/catalog-ui/app/scripts/models/requirement.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=========================================================
+ */
+ /*
+ * Created by obarda on 4/20/2016.
+ */
+/// <reference path="../references"/>
+module Sdc.Models {
+ 'use strict';
+ //this is an object contains keys, when each key has matching array.
+ // for example: key = tosca.capabilities.network. and the match array is array of requirements objects
+ export class RequirementsGroup{
+ constructor(requirementGroupObj?:Models.RequirementsGroup){
+ _.forEach(requirementGroupObj, (requirementsArrayObj:Array<Models.Requirement>, instance) => {
+ this[instance] = [];
+ _.forEach(requirementsArrayObj, (requirement:Models.Requirement):void => {
+ this[instance].push(new Models.Requirement(requirement));
+ });
+ });
+ }
+ }
+
+ export class Requirement {
+
+ //server data
+ capability:string;
+ name: string;
+ ownerId: string;
+ ownerName:string;
+ node:string;
+ uniqueId:string;
+ relationship: string;
+ minOccurrences: string;
+ maxOccurrences: string;
+ //custom
+ filterTerm:string;
+ constructor(requirement?:Requirement) {
+
+ if(requirement) {
+ this.capability = requirement.capability;
+ this.name = requirement.name;
+ this.ownerId = requirement.ownerId;
+ this.ownerName = requirement.ownerName;
+ this.node = requirement.node;
+ this.uniqueId = requirement.uniqueId;
+ this.relationship = requirement.relationship;
+ this.minOccurrences = requirement.minOccurrences;
+ this.maxOccurrences = requirement.maxOccurrences;
+ this.initFilterTerm();
+
+ }
+ }
+
+ public getFullTitle():string {
+ return this.ownerName + ': ' + this.name +
+ ': [' + this.minOccurrences + ', ' + this.maxOccurrences + ']';
+ }
+
+ public toJSON = ():any => {
+ this.filterTerm = undefined;
+ return this;
+ };
+
+ private initFilterTerm = ():void =>{
+ this.filterTerm = (this.name + " ") +
+ (this.ownerName + " " ) +
+ (this.capability ? (this.capability.substring("tosca.capabilities.".length) + " " ) : "") +
+ (this.node? (this.node.substring("tosca.nodes.".length) +" ") : "") +
+ (this.relationship? (this.relationship.substring("tosca.relationships.".length) +" ") : "") +
+ this.minOccurrences+","+this.maxOccurrences;
+ }
+ }
+}
+
+
diff --git a/catalog-ui/app/scripts/models/schema-attribute.ts b/catalog-ui/app/scripts/models/schema-attribute.ts
new file mode 100644
index 0000000000..725a7589e0
--- /dev/null
+++ b/catalog-ui/app/scripts/models/schema-attribute.ts
@@ -0,0 +1,37 @@
+/*-
+ * ============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.Models {
+ 'use strict';
+
+ export class SchemaAttributeGroupModel{
+ property: SchemaAttribute;
+
+ constructor(schemaAttribute?:Models.SchemaAttribute) {
+ this.property = schemaAttribute;
+ }
+ }
+
+ export class SchemaAttribute extends SchemaProperty{
+
+ }
+}
+
+
diff --git a/catalog-ui/app/scripts/models/tab.ts b/catalog-ui/app/scripts/models/tab.ts
new file mode 100644
index 0000000000..cc42d4f348
--- /dev/null
+++ b/catalog-ui/app/scripts/models/tab.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=========================================================
+ */
+/**
+ * Created by obarda on 7/31/2016.
+ */
+/// <reference path="../references"/>
+module Sdc.Models {
+ 'use strict';
+
+ export class Tab {
+
+ public templateUrl:string;
+ public controller:string;
+ public data:any;
+ public icon:string;
+ public name:string;
+
+ constructor(templateUrl:string, controller:string, name:string, data?:any, icon?:string) {
+
+ this.templateUrl = templateUrl;
+ this.controller = controller;
+ this.icon = icon;
+ this.data = data;
+ this.name = name;
+ }
+ }
+}
+
+
+
diff --git a/catalog-ui/app/scripts/models/tooltip-data.ts b/catalog-ui/app/scripts/models/tooltip-data.ts
new file mode 100644
index 0000000000..027904b245
--- /dev/null
+++ b/catalog-ui/app/scripts/models/tooltip-data.ts
@@ -0,0 +1,28 @@
+/*-
+ * ============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.Models {
+ 'use strict';
+
+ export class TooltipData{
+ }
+}
+
+
diff --git a/catalog-ui/app/scripts/models/user.ts b/catalog-ui/app/scripts/models/user.ts
new file mode 100644
index 0000000000..836066f5f9
--- /dev/null
+++ b/catalog-ui/app/scripts/models/user.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.Models {
+ 'use strict';
+
+ export enum UserRole {
+ ADMIN,
+ DESIGNER,
+ TESTER,
+ GOVERNOR,
+ OPS,
+ PRODUCT_MANAGER,
+ PRODUCT_STRATEGIST
+ }
+
+ export interface IUserManager {
+ isInEditMode: boolean;
+ filterTerm: string;
+ }
+
+ export interface IUserProperties extends IUserManager{
+ firstName: string;
+ lastName: string;
+ userId: string;
+ email: string;
+ role: string;
+ tempRole:string;
+ lastLoginTime: string;
+ status:string;
+ }
+
+ export interface IUser {
+ resource: Services.IUserResource;
+ getRole(): UserRole;
+ getRoleToView(): string;
+ getName(): string;
+ getFirstName(): string;
+ getLastName(): string;
+ }
+
+ export class User implements IUser {
+
+ constructor(public resource:Services.IUserResource) {
+ }
+
+ public getLastName = () => {
+ return this.resource.lastName;
+ }
+
+ public getFirstName = () => {
+ return this.resource.firstName;
+ }
+
+ public getName = () => {
+ return this.resource.firstName + ' ' + this.resource.lastName;
+ }
+
+ public getLastLogin = () => {
+ if (!this.resource.lastLoginTime || this.resource.lastLoginTime === "0") {
+ return "";
+ } else {
+ return this.resource.lastLoginTime;
+ }
+ }
+
+ public getRole = ():UserRole => {
+ let role:UserRole;
+ switch (UserRole[this.resource.role.toUpperCase()]) {
+ case UserRole.ADMIN:
+ role = UserRole.ADMIN;
+ break;
+ case UserRole.DESIGNER:
+ role = UserRole.DESIGNER;
+ break;
+ case UserRole.TESTER:
+ role = UserRole.TESTER;
+ break;
+ case UserRole.GOVERNOR:
+ role = UserRole.GOVERNOR;
+ break;
+ case UserRole.OPS:
+ role = UserRole.OPS;
+ break;
+ case UserRole.PRODUCT_MANAGER:
+ role = UserRole.PRODUCT_MANAGER;
+ break;
+ case UserRole.PRODUCT_STRATEGIST:
+ role = UserRole.PRODUCT_STRATEGIST;
+ break;
+ }
+ return role;
+ }
+
+ public getRoleToView = ():string => {
+ let role:string = this.resource.role.toLowerCase().replace('governor','governance_Rep');
+ return role.charAt(0).toUpperCase() + role.slice(1).replace('_',' ');
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/models/validate.ts b/catalog-ui/app/scripts/models/validate.ts
new file mode 100644
index 0000000000..21540d38b6
--- /dev/null
+++ b/catalog-ui/app/scripts/models/validate.ts
@@ -0,0 +1,29 @@
+/*-
+ * ============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.Models {
+ 'use strict';
+
+ export interface IValidate{
+ isValid : boolean;
+ }
+}
+
+
diff --git a/catalog-ui/app/scripts/modules/directive-module.ts b/catalog-ui/app/scripts/modules/directive-module.ts
new file mode 100644
index 0000000000..70a15378fa
--- /dev/null
+++ b/catalog-ui/app/scripts/modules/directive-module.ts
@@ -0,0 +1,106 @@
+/*-
+ * ============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 {
+ let moduleName:string = 'Sdc.Directives';
+ let directiveModule:ng.IModule = angular.module(moduleName, []);
+
+ directiveModule.directive('clickedOutside', Directives.ClickedOutsideDirective.factory);
+ directiveModule.directive('loader', Directives.LoaderDirective.factory);
+ directiveModule.directive('userHeaderDetails', Directives.UserHeaderDetailsDirective.factory);
+ directiveModule.directive('ellipsis', Directives.EllipsisDirective.factory);
+ directiveModule.directive('downloadArtifact', Directives.DownloadArtifactDirective.factory);
+ directiveModule.directive('fileType', Directives.FileTypeDirective.factory);
+ directiveModule.directive('invalidCharacters', Directives.InvalidCharactersDirective.factory);
+ directiveModule.directive('tutorial', Directives.TutorialDirective.factory);
+ directiveModule.directive('perfectScrollbar', Directives.PerfectScrollerDirective.factory);
+ directiveModule.directive('expandCollapse', Directives.ExpandCollapseDirective.factory);
+ directiveModule.directive('sdcModal', Directives.SdcModalDirective.factory);
+ directiveModule.directive('sdcMessages', Directives.SdcMessagesDirective.factory);
+ directiveModule.directive('sdcMessage', Directives.SdcMessageDirective.factory);
+ directiveModule.directive('sdcErrorTooltip', Directives.SdcErrorTooltipDirective.factory);
+ directiveModule.directive('fileOpener', Directives.FileOpenerDirective.factory);
+ directiveModule.directive('fileUpload', Directives.FileUploadDirective.factory);
+ directiveModule.directive('structureTree', Directives.StructureTreeDirective.factory);
+ directiveModule.directive('sdcWizardStep', Directives.SdcWizardStepDirective.factory);
+ directiveModule.directive('sdcPageSelector', Directives.PageSelectorDirective.factory);
+ directiveModule.directive('sdcSmartTooltip', Directives.SmartTooltipDirective.factory);
+ directiveModule.directive('printGraphScreen', Directives.PrintGraphScreenDirective.factory);
+ directiveModule.directive('sdcTag', Directives.TagDirective.factory);
+ directiveModule.directive('sdcTags', Directives.SdcTagsDirective.factory);
+ directiveModule.directive('sdcKeyboardEvents', Directives.SdcKeyboardEventsDirective.factory);
+ directiveModule.directive('expandCollapseMenuBox', Directives.ExpandCollapseMenuBoxDirective.factory);
+ directiveModule.directive('sdcPageScroll', Directives.SdcPageScrollDirective.factory);
+ directiveModule.directive('punchOut', Directives.PunchOutDirective.factory);
+ directiveModule.directive('relationMenu', Directives.RelationMenuDirective.factory);
+ directiveModule.directive('customValidation', Directives.CustomValidationDirective.factory);
+ directiveModule.directive('ecompHeader', Directives.EcompHeaderDirective.factory);
+ directiveModule.directive('editNamePopover', Directives.EditNamePopoverDirective.factory);
+ directiveModule.directive('fieldsStructure', Directives.DataTypeFieldsStructureDirective.factory);
+ directiveModule.directive('typeMap', Directives.TypeMapDirective.factory);
+ directiveModule.directive('typeList', Directives.TypeListDirective.factory);
+ directiveModule.directive('infoTooltip', Directives.InfoTooltipDirective.factory);
+
+ directiveModule.directive('sdcTabs', Directives.SdcTabsDirective.factory);
+ directiveModule.directive('sdcSingleTab', Directives.SdcSingleTabDirective.factory);
+ directiveModule.directive('innerSdcSingleTab', Directives.InnerSdcSingleTabDirective.factory);
+
+ //composition
+ directiveModule.directive('palette', Directives.Palette.factory);
+ directiveModule.directive('compositionGraph', Directives.CompositionGraph.factory);
+
+ //deployment
+ directiveModule.directive('deploymentGraph', Directives.DeploymentGraph.factory);
+
+ // Layouts
+ directiveModule.directive('topNav', Directives.TopNavDirective.factory);
+ directiveModule.directive('topProgress', Directives.TopProgressDirective.factory);
+
+ // Elements
+ directiveModule.directive('sdcCheckbox', Directives.CheckboxElementDirective.factory);
+ directiveModule.directive('sdcRadioButton', Directives.RadiobuttonElementDirective.factory);
+
+ //Graph Utils - Common
+ directiveModule.service('CommonGraphUtils', Sdc.Graph.Utils.CommonGraphUtils);
+
+ //Composition Graph Utils
+ directiveModule.service('CompositionGraphNodesUtils', Sdc.Graph.Utils.CompositionGraphNodesUtils);
+ directiveModule.service('CompositionGraphGeneralUtils', Sdc.Graph.Utils.CompositionGraphGeneralUtils);
+ directiveModule.service('CompositionGraphLinkUtils', Sdc.Graph.Utils.CompositionGraphLinkUtils);
+ directiveModule.service('MatchCapabilitiesRequirementsUtils', Sdc.Graph.Utils.MatchCapabilitiesRequirementsUtils);
+
+ //Composition Graph Utils
+ directiveModule.service('DeploymentGraphGeneralUtils', Sdc.Graph.Utils.DeploymentGraphGeneralUtils);
+
+ //Util service for graph
+ directiveModule.service('NodesFactory', Sdc.Utils.NodesFactory);
+ directiveModule.service('LinksFactory', Sdc.Utils.LinksFactory);
+ directiveModule.service('ImageCreatorService', Sdc.Utils.ImageCreatorService);
+
+ //directiveModule.service('GraphUtilsServerUpdateQueue', Sdc.Directives.GraphUtilsServerUpdateQueue);
+
+ //controller for go.js
+ directiveModule.controller('SdcWizardStepDirectiveController', Directives.SdcWizardStepDirectiveController);
+
+ // Events
+ directiveModule.directive('onLastRepeat', Directives.OnLastRepeatDirective.factory);
+}
+
+
diff --git a/catalog-ui/app/scripts/modules/filters.ts b/catalog-ui/app/scripts/modules/filters.ts
new file mode 100644
index 0000000000..1bf31507fd
--- /dev/null
+++ b/catalog-ui/app/scripts/modules/filters.ts
@@ -0,0 +1,44 @@
+/*-
+ * ============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 {
+
+ let moduleName: string = 'Sdc.Filters';
+ let filterModule: ng.IModule = angular.module(moduleName, []);
+ filterModule.filter("resourceName", Sdc.Filters.ResourceNameFilter);
+ filterModule.filter("graphResourceName", Sdc.Filters.GraphResourceNameFilter);
+ filterModule.filter("categoryNameFilter", Sdc.Filters.CategoryNameFilter);
+ filterModule.filter("entityFilter", Sdc.Filters.EntityFilter);
+ filterModule.filter("truncate", Sdc.Filters.TruncateFilter);
+ filterModule.filter("catalogStatusFilter", Sdc.Filters.CatalogStatusFilter);
+ filterModule.filter("categoryTypeFilter", Sdc.Filters.CategoryTypeFilter);
+ filterModule.filter("stringToDateFilter", Sdc.Filters.StringToDateFilter);
+ filterModule.filter("categoryIcon", Sdc.Filters.CategoryIconFilter);
+ filterModule.filter("capitalizeFilter", Sdc.Filters.CapitalizeFilter);
+ filterModule.filter("underscoreLessFilter", Sdc.Filters.UnderscoreLessFilter);
+ filterModule.filter("resourceTypeName", Sdc.Filters.ResourceTypeFilter);
+ filterModule.filter("relationName", Sdc.Filters.RelationNameFilter);
+ filterModule.filter("trim", Sdc.Filters.TrimFilter);
+ filterModule.filter("clearWhiteSpaces", Sdc.Filters.ClearWhiteSpacesFilter);
+ filterModule.filter('testsId', Sdc.Filters.TestsIdFilter);
+
+ //Added by Ikram
+ filterModule.filter("productCategoryNameFilter", Sdc.Filters.ProductCategoryNameFilter);
+}
diff --git a/catalog-ui/app/scripts/modules/service-module.ts b/catalog-ui/app/scripts/modules/service-module.ts
new file mode 100644
index 0000000000..c77e8b0ad4
--- /dev/null
+++ b/catalog-ui/app/scripts/modules/service-module.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 {
+ let moduleName:string = 'Sdc.Services';
+ let serviceModule:ng.IModule = angular.module(moduleName, []);
+
+ serviceModule.service('Sdc.Services.ConfigurationUiService', Services.ConfigurationUiService);
+ serviceModule.service('Sdc.Services.CookieService', Services.CookieService);
+ serviceModule.service('Sdc.Services.EntityService', Services.EntityService);
+ serviceModule.service('Sdc.Services.AvailableIconsService', Services.AvailableIconsService);
+ serviceModule.service('Sdc.Services.RelationIconsService', Services.RelationIconsService);
+ serviceModule.service('Sdc.Services.UrlToBase64Service', Services.UrlToBase64Service);
+ serviceModule.service('Sdc.Services.CacheService', Services.CacheService);
+ serviceModule.service('Sdc.Services.HeaderInterceptor', Services.HeaderInterceptor);
+ serviceModule.service('Sdc.Services.HttpErrorInterceptor', Services.HttpErrorInterceptor);
+ serviceModule.service('Sdc.Services.SharingService', Services.SharingService);
+ serviceModule.service('Sdc.Services.SdcVersionService', Services.SdcVersionService);
+ serviceModule.service('Sdc.Services.ActivityLogService', Services.ActivityLogService);
+ serviceModule.service('Sdc.Services.OnboardingService', Services.OnboardingService);
+ serviceModule.service('Sdc.Services.EcompHeaderService', Services.EcompHeaderService);
+ serviceModule.service('Sdc.Services.DataTypesService', Services.DataTypesService);
+
+ //Components Services
+ serviceModule.service('Sdc.Services.Components.ComponentService', Services.Components.ComponentService);
+ serviceModule.service('Sdc.Services.Components.ServiceService', Services.Components.ServiceService);
+ serviceModule.service('Sdc.Services.Components.ResourceService', Services.Components.ResourceService);
+ serviceModule.service('Sdc.Services.Components.ProductService', Services.Components.ProductService);
+ serviceModule.service('LeftPaletteLoaderService', Services.Components.LeftPaletteLoaderService);
+ serviceModule.service('EventListenerService', Services.EventListenerService);
+ serviceModule.service('Sdc.Services.ProgressService', Services.ProgressService);
+
+ //Utils
+ serviceModule.service('ArtifactsUtils', Sdc.Utils.ArtifactsUtils);
+ serviceModule.service('FileUtils', Sdc.Utils.FileUtils);
+ serviceModule.service('ValidationUtils', Sdc.Utils.ValidationUtils);
+
+
+
+
+ serviceModule.service('AngularJSBridge', Sdc.Services.AngularJSBridge);
+ serviceModule.service('LoaderService', Sdc.Services.LoaderService);
+
+ serviceModule.factory('Sdc.Services.UserResourceService', Services.UserResourceService.getResource);
+ serviceModule.factory('Sdc.Services.CategoryResourceService', Services.CategoryResourceService.getResource);
+
+}
diff --git a/catalog-ui/app/scripts/modules/utils.ts b/catalog-ui/app/scripts/modules/utils.ts
new file mode 100644
index 0000000000..fd5eaf2a4b
--- /dev/null
+++ b/catalog-ui/app/scripts/modules/utils.ts
@@ -0,0 +1,39 @@
+/*-
+ * ============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 2/11/2016.
+ */
+/// <reference path="../references"/>
+module Sdc {
+ let moduleName:string = 'Sdc.Utils';
+ let serviceModule:ng.IModule = angular.module(moduleName, []);
+
+
+
+ //Utils
+ serviceModule.service('ComponentFactory', Sdc.Utils.ComponentFactory);
+ serviceModule.service('ComponentInstanceFactory', Sdc.Utils.ComponentInstanceFactory);
+ serviceModule.service('ChangeLifecycleStateHandler', Sdc.Utils.ChangeLifecycleStateHandler);
+ serviceModule.service('ModalsHandler', Sdc.Utils.ModalsHandler);
+ serviceModule.service('MenuHandler', Sdc.Utils.MenuHandler);
+
+
+
+}
diff --git a/catalog-ui/app/scripts/modules/view-model-module.ts b/catalog-ui/app/scripts/modules/view-model-module.ts
new file mode 100644
index 0000000000..19cf5b45f4
--- /dev/null
+++ b/catalog-ui/app/scripts/modules/view-model-module.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 {
+ let moduleName: string = 'Sdc.ViewModels';
+ let viewModelModule: ng.IModule = angular.module(moduleName, []);
+
+ viewModelModule
+ .controller(moduleName+'.DashboardViewModel', ViewModels.DashboardViewModel)
+ .controller(moduleName+'.CompositionViewModel', ViewModels.CompositionViewModel)
+
+ .controller(moduleName+'.DetailsViewModel', ViewModels.DetailsViewModel)
+ .controller(moduleName+'.ResourceArtifactsViewModel', ViewModels.ResourceArtifactsViewModel)
+ .controller(moduleName+'.PropertyFormViewModel', ViewModels.PropertyFormViewModel)
+ .controller(moduleName+'.ArtifactResourceFormViewModel', ViewModels.ArtifactResourceFormViewModel)
+ .controller(moduleName+'.AttributeFormViewModel', ViewModels.AttributeFormViewModel)
+ .controller(moduleName+'.ResourcePropertiesViewModel', ViewModels.ResourcePropertiesViewModel)
+ .controller(moduleName+'.CatalogViewModel', ViewModels.CatalogViewModel)
+ .controller(moduleName+'.OnboardVendorViewModel', ViewModels.OnboardVendorViewModel)
+ .controller(moduleName+'.DistributionViewModel', ViewModels.DistributionViewModel)
+ .controller(moduleName+'.SupportViewModel', ViewModels.SupportViewModel)
+ .controller(moduleName+'.ConfirmationModalViewModel', ViewModels.ConfirmationModalViewModel)
+ .controller(moduleName+'.EmailModalViewModel', ViewModels.EmailModalViewModel)
+ .controller(moduleName+'.MessageModalViewModel', ViewModels.MessageModalViewModel)
+ .controller(moduleName+'.ServerMessageModalViewModel', ViewModels.ServerMessageModalViewModel)
+ .controller(moduleName+'.ClientMessageModalViewModel', ViewModels.ClientMessageModalViewModel)
+ .controller(moduleName+'.ErrorViewModel', ViewModels.ErrorViewModel)
+ .controller(moduleName+'.ComponentViewerViewModel', ViewModels.ComponentViewerViewModel)
+ .controller(moduleName+'.RelationsViewModel', ViewModels.RelationsViewModel)
+ .controller(moduleName+'.ResourceInstanceNameViewModel', ViewModels.ResourceInstanceNameViewModel)
+ .controller(moduleName+'.WelcomeViewModel', ViewModels.WelcomeViewModel)
+ .controller(moduleName+'.PreLoadingViewModel', ViewModels.PreLoadingViewModel)
+ .controller(moduleName+'.TutorialEndViewModel', ViewModels.TutorialEndViewModel)
+ .controller(moduleName+'.AdminDashboardViewModel', ViewModels.AdminDashboardViewModel)
+ .controller(moduleName+'.EnvParametersFormViewModel', ViewModels.EnvParametersFormViewModel)
+ .controller(moduleName+'.StructureViewModel', ViewModels.StructureViewModel)
+ .controller(moduleName+'.AddCategoryModalViewModel', ViewModels.AddCategoryModalViewModel)
+ .controller(moduleName+'.DashboardCoverViewModel', ViewModels.DashboardCoverViewModel)
+ .controller(moduleName+'.UserManagementViewModel', ViewModels.UserManagementViewModel)
+ .controller(moduleName+'.CategoryManagementViewModel', ViewModels.CategoryManagementViewModel)
+ .controller(moduleName+'.WelcomeStepsControllerViewModel', ViewModels.WelcomeStepsControllerViewModel)
+ .controller(moduleName+'.OnboardingModalViewModel', ViewModels.OnboardingModalViewModel)
+ .controller(moduleName+'.DistributionStatusModalViewModel', ViewModels.DistributionStatusModalViewModel)
+
+ .controller(moduleName+'.Wizard.EditWizardViewModel', ViewModels.Wizard.EditWizardViewModel)
+ .controller(moduleName+'.Wizard.CreateWizardViewModel', ViewModels.Wizard.CreateWizardViewModel)
+ .controller(moduleName+'.Wizard.ImportWizardViewModel', ViewModels.Wizard.ImportWizardViewModel)
+ .controller(moduleName+'.Wizard.GeneralStepViewModel', ViewModels.Wizard.GeneralStepViewModel)
+ .controller(moduleName+'.Wizard.IconsStepViewModel', ViewModels.Wizard.IconsStepViewModel)
+ .controller(moduleName+'.Wizard.ArtifactInformationStepViewModel', ViewModels.Wizard.ArtifactInformationStepViewModel)
+ .controller(moduleName+'.Wizard.ArtifactDeploymentStepViewModel', ViewModels.Wizard.ArtifactDeploymentStepViewModel)
+ .controller(moduleName+'.Wizard.PropertiesStepViewModel', ViewModels.Wizard.PropertiesStepViewModel)
+ .controller(moduleName+'.Wizard.ArtifactResourceFormStepViewModel', ViewModels.Wizard.ArtifactResourceFormStepViewModel)
+ .controller(moduleName+'.Wizard.PropertyFormViewModel', ViewModels.Wizard.PropertyFormViewModel)
+ .controller(moduleName+'.Wizard.HierarchyStepViewModel',ViewModels.Wizard.HierarchyStepViewModel)
+
+ //NEW
+ .controller(moduleName+'.WorkspaceViewModel', ViewModels.WorkspaceViewModel)
+ .controller(moduleName+'.GeneralViewModel', ViewModels.GeneralViewModel)
+ .controller(moduleName+'.IconsViewModel', ViewModels.IconsViewModel)
+ .controller(moduleName+'.DeploymentArtifactsViewModel', ViewModels.DeploymentArtifactsViewModel)
+ .controller(moduleName+'.InformationArtifactsViewModel', ViewModels.InformationArtifactsViewModel)
+ .controller(moduleName+'.ToscaArtifactsViewModel', ViewModels.ToscaArtifactsViewModel)
+ .controller(moduleName+'.PropertiesViewModel', ViewModels.PropertiesViewModel)
+ .controller(moduleName+'.AttributesViewModel', ViewModels.AttributesViewModel)
+ .controller(moduleName+'.ProductHierarchyViewModel',ViewModels.ProductHierarchyViewModel)
+ .controller(moduleName+'.ActivityLogViewModel',ViewModels.ActivityLogViewModel)
+ .controller(moduleName+'.ManagementWorkflowViewModel',ViewModels.ManagementWorkflowViewModel)
+ .controller(moduleName+'.NetworkCallFlowViewModel',ViewModels.NetworkCallFlowViewModel)
+ .controller(moduleName+'.DeploymentViewModel',ViewModels.DeploymentViewModel)
+ .controller(moduleName+'.ResourceInputsViewModel',ViewModels.ResourceInputsViewModel)
+ .controller(moduleName+'.ServiceInputsViewModel', ViewModels.ServiceInputsViewModel)
+ .controller(moduleName+'.ReqAndCapabilitiesViewModel', ViewModels.ReqAndCapabilitiesViewModel)
+
+
+
+ //TABS
+ .controller(moduleName+'.HierarchyViewModel',ViewModels.HierarchyViewModel)
+}
diff --git a/catalog-ui/app/scripts/services/activity-log-service.ts b/catalog-ui/app/scripts/services/activity-log-service.ts
new file mode 100644
index 0000000000..6fe27c447e
--- /dev/null
+++ b/catalog-ui/app/scripts/services/activity-log-service.ts
@@ -0,0 +1,48 @@
+/*-
+ * ============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.Services {
+ 'use strict';
+
+ // Define an interface of the object you want to use, providing it's properties
+ export interface IActivityLogService{
+ getActivityLogService(type :string, id: string): ng.IPromise<Array<Models.Activity>>;
+ }
+
+ export class ActivityLogService implements IActivityLogService{
+
+
+ static '$inject' = ['$http', '$q','sdcConfig'];
+ private api: Models.IApi;
+
+ constructor(private $http: ng.IHttpService, private $q: ng.IQService, sdcConfig: Models.IAppConfigurtaion){
+ this.api = sdcConfig.api;
+ }
+
+ getActivityLogService = (type:string, id:string): ng.IPromise<Array<Models.Activity>> =>{
+ let defer = this.$q.defer<any>();
+ this.$http.get(this.api.root + this.api.GET_activity_log.replace(':type', type).replace(':id', id))
+ .success((activityLog: any) => {
+ defer.resolve(activityLog);
+ });
+ return defer.promise;
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/services/angular-js-bridge-service.ts b/catalog-ui/app/scripts/services/angular-js-bridge-service.ts
new file mode 100644
index 0000000000..2d8fb01b13
--- /dev/null
+++ b/catalog-ui/app/scripts/services/angular-js-bridge-service.ts
@@ -0,0 +1,22 @@
+module Sdc.Services {
+ export class AngularJSBridge{
+ private static _$filter: ng.IFilterService;
+ private static _sdcConfig: Models.IAppConfigurtaion;
+
+ public static getFilter(filterName: string){
+ return AngularJSBridge._$filter(filterName);
+ }
+
+ public static getAngularConfig(){
+ return AngularJSBridge._sdcConfig;
+ }
+
+
+ constructor($filter: ng.IFilterService, sdcConfig: Models.IAppConfigurtaion){
+ AngularJSBridge._$filter = $filter;
+ AngularJSBridge._sdcConfig = sdcConfig;
+ }
+ }
+
+ AngularJSBridge.$inject = ['$filter', 'sdcConfig']
+} \ No newline at end of file
diff --git a/catalog-ui/app/scripts/services/available-icons-service.ts b/catalog-ui/app/scripts/services/available-icons-service.ts
new file mode 100644
index 0000000000..5c20afe5f2
--- /dev/null
+++ b/catalog-ui/app/scripts/services/available-icons-service.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=========================================================
+ */
+/**
+ * Created by obarda on 2/23/2016.
+ */
+/// <reference path="../references"/>
+module Sdc.Services {
+ 'use strict';
+
+ interface IAvailableIconsService {
+ getIcons(componentType: Utils.Constants.ComponentType):Array<string>;
+ }
+
+ export class AvailableIconsService implements IAvailableIconsService {
+ constructor() {}
+ public getIcons = (componentType: string): Array<string> => {
+ let icons: string[];
+ switch (componentType){
+ case Utils.Constants.ComponentType.SERVICE:
+ icons = [
+ 'call_controll',
+ 'mobility',
+ 'network_l_1-3',
+ 'network_l_4'
+ ];
+ break;
+
+ case Utils.Constants.ComponentType.RESOURCE:
+ icons= [
+ 'router',
+ 'database',
+ 'network',
+ 'objectStorage',
+ 'connector',
+ 'brocade',
+ 'cisco',
+ 'ericsson',
+ 'tropo',
+ 'fortinet',
+ 'att',
+ 'broadsoft',
+ 'alcatelLucent',
+ 'metaswitch',
+ 'aricent',
+ 'mySql',
+ 'oracle',
+ 'nokia_siemens',
+ 'juniper',
+ 'call_controll',
+ 'borderElement',
+ 'applicationServer',
+ 'server',
+ 'port',
+ 'loadBalancer',
+ 'compute',
+ 'gateway',
+ 'cp',
+ 'vl',
+ 'vfw',
+ 'firewall'
+ ];
+ break;
+
+ case Utils.Constants.ComponentType.PRODUCT:
+ icons = [
+ 'vfw',
+ 'network',
+ 'security',
+ 'cloud',
+ 'setting',
+ 'orphan',
+ 'wanx',
+ 'vrouter',
+ 'ucpe',
+ 'mobility'
+
+ ];
+ break;
+
+ }
+ return icons;
+ }
+
+ }
+}
+
+
+
diff --git a/catalog-ui/app/scripts/services/cache-service.ts b/catalog-ui/app/scripts/services/cache-service.ts
new file mode 100644
index 0000000000..3e5e5495c7
--- /dev/null
+++ b/catalog-ui/app/scripts/services/cache-service.ts
@@ -0,0 +1,58 @@
+/*-
+ * ============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.Services {
+
+ 'use strict';
+
+ interface ICacheService {
+ get(key:string):any;
+ set(key:string, value:any):void;
+ }
+
+ export class CacheService implements ICacheService {
+
+ static '$inject' = ['sdcConfig', '$document'];
+ private storage:Utils.Dictionary<string, any>;
+
+ constructor() {
+ this.storage = new Utils.Dictionary<string, any>();
+ };
+
+ public get = (key:string):any => {
+ return this.storage.getValue(key);
+ };
+
+ public set = (key:string, value:any):void => {
+ this.storage.setValue(key, value);
+ };
+
+ public remove = (key:string):void => {
+ if (this.storage.containsKey(key)){
+ this.storage.remove(key);
+ }
+ };
+
+ public contains = (key:string):boolean => {
+ return this.storage.containsKey(key);
+ };
+
+ }
+}
diff --git a/catalog-ui/app/scripts/services/category-resource-service.ts b/catalog-ui/app/scripts/services/category-resource-service.ts
new file mode 100644
index 0000000000..eef1b445e6
--- /dev/null
+++ b/catalog-ui/app/scripts/services/category-resource-service.ts
@@ -0,0 +1,83 @@
+/*-
+ * ============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.Services {
+ import IMainCategory = Sdc.Models.IMainCategory;
+ 'use strict';
+
+ // Define an interface of the object you want to use, providing it's properties
+ export interface ICategoryResource extends Models.IUserProperties,ng.resource.IResource<ICategoryResource>{
+ name:string;
+ uniqueId:string;
+ subcategories:Array<ICategoryResource>;
+
+ getAllCategories(params?: Object, success?: Function, error?: Function): Array<IMainCategory>;
+ $saveSubCategory(params?: Object, success?: Function, error?: Function): any;
+ $deleteSubCategory(params?: Object, success?: Function, error?: Function): any;
+ }
+
+ // Define your resource, adding the signature of the custom actions
+ export interface ICategoryResourceClass extends ng.resource.IResourceClass<ICategoryResource>{
+ getAllCategories(params?: Object, success?: Function, error?: Function): Array<IMainCategory>;
+ saveSubCategory(params?: Object, success?: Function, error?: Function): any;
+ deleteSubCategory(params?: Object, success?: Function, error?: Function): any;
+ }
+
+ export class CategoryResourceService{
+
+ public static getResource = (
+ $resource: ng.resource.IResourceService,
+ sdcConfig: Models.IAppConfigurtaion
+ ): ICategoryResourceClass => {
+
+ // Define your custom actions here as IActionDescriptor
+ let getAllCategoriesAction : ng.resource.IActionDescriptor = {
+ method: 'GET',
+ isArray: true,
+ url: sdcConfig.api.root + sdcConfig.api.GET_categories
+ };
+ let saveSubCategory : ng.resource.IActionDescriptor = {
+ method: 'POST',
+ isArray: false,
+ url: sdcConfig.api.root + sdcConfig.api.POST_subcategory
+ };
+ let deleteSubCategory: ng.resource.IActionDescriptor = {
+ method: 'DELETE',
+ isArray: false,
+ url: sdcConfig.api.root + sdcConfig.api.POST_subcategory
+ };
+
+
+ let url: string = sdcConfig.api.root + sdcConfig.api.POST_category;
+ let categoryResource: ICategoryResourceClass = <ICategoryResourceClass>$resource(
+ url,
+ { types: '@types', categoryId: '@categoryId' },
+ {
+ getAllCategories: getAllCategoriesAction,
+ saveSubCategory: saveSubCategory,
+ deleteSubCategory: deleteSubCategory
+ }
+ );
+
+ return categoryResource;
+ }
+ }
+ CategoryResourceService.getResource.$inject = ['$resource', 'sdcConfig'];
+}
diff --git a/catalog-ui/app/scripts/services/components/component-service.ts b/catalog-ui/app/scripts/services/components/component-service.ts
new file mode 100644
index 0000000000..393ea71c03
--- /dev/null
+++ b/catalog-ui/app/scripts/services/components/component-service.ts
@@ -0,0 +1,698 @@
+/*-
+ * ============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.Services.Components {
+
+ 'use strict';
+
+ declare let CryptoJS:any;
+
+ export interface IComponentService {
+
+ getComponent(id:string);
+ updateComponent(component:Models.Components.Component):ng.IPromise<Models.Components.Component>;
+ changeLifecycleState(component:Models.Components.Component, state:string, userRemarks:any):ng.IPromise<Models.Components.Component> ;
+ validateName(newName:string, subtype?:string):ng.IPromise<Models.IValidate>;
+ createComponent(component:Models.Components.Component):ng.IPromise<Models.Components.Component>;
+ addOrUpdateArtifact(componentId:string, artifact:Models.ArtifactModel):ng.IPromise<Models.ArtifactModel>;
+ deleteArtifact(componentId:string, artifact:string, artifactLabel):ng.IPromise<Models.ArtifactModel>;
+ addProperty(componentId:string, property:Models.PropertyModel):ng.IPromise<Models.PropertyModel>;
+ updateProperty(componentId:string, property:Models.PropertyModel):ng.IPromise<Models.PropertyModel>;
+ addAttribute(componentId:string, attribute:Models.AttributeModel):ng.IPromise<Models.AttributeModel>;
+ updateAttribute(componentId:string, attribute:Models.AttributeModel):ng.IPromise<Models.AttributeModel>;
+ deleteProperty(componentId:string, propertyId:string):ng.IPromise<Models.PropertyModel>;
+ deleteAttribute(componentId:string, attributeId:string):ng.IPromise<Models.AttributeModel>;
+ changeResourceInstanceVersion(componentId:string, componentInstanceId:string, componentUid:string):ng.IPromise<Models.ComponentsInstances.ComponentInstance>;
+ updateInstanceArtifact(componentId:string, instanceId:string, artifact:Models.ArtifactModel):ng.IPromise<Models.ArtifactModel>;
+ addInstanceArtifact(componentId: string, instanceId: string, artifact:Models.ArtifactModel):ng.IPromise<Models.ArtifactModel>;
+ deleteInstanceArtifact(componentId: string, instanceId: string, artifact:string, artifactLabel):ng.IPromise<Models.ArtifactModel>;
+ createComponentInstance(componentId:string, componentInstance:Models.ComponentsInstances.ComponentInstance):ng.IPromise<Models.ComponentsInstances.ComponentInstance>;
+ updateComponentInstance(componentId:string, componentInstance:Models.ComponentsInstances.ComponentInstance):ng.IPromise<Models.ComponentsInstances.ComponentInstance>;
+ updateMultipleComponentInstances(componentId:string, instances:Array<Models.ComponentsInstances.ComponentInstance>):ng.IPromise< Array<Models.ComponentsInstances.ComponentInstance>>;
+ downloadArtifact(componentId:string, artifactId:string):ng.IPromise<Models.IFileDownload>;
+ uploadInstanceEnvFile(componentId:string, instanceId:string, artifact:Models.ArtifactModel):ng.IPromise<Models.ArtifactModel>;
+ downloadInstanceArtifact(componentId:string, instanceId:string, artifactId:string):ng.IPromise<Models.IFileDownload>;
+ deleteComponentInstance(componentId:string, componentInstanceId:string):ng.IPromise<Models.ComponentsInstances.ComponentInstance>;
+ createRelation(componentId:string, link:Models.RelationshipModel):ng.IPromise<Models.RelationshipModel>;
+ deleteRelation(componentId:string, link:Models.RelationshipModel):ng.IPromise<Models.RelationshipModel>;
+ getRequirementsCapabilities(componentId:string):ng.IPromise<any>;
+ updateInstanceProperty(componentId:string, property:Models.PropertyModel):ng.IPromise<Models.PropertyModel>;
+ updateInstanceAttribute(componentId:string, attribute:Models.AttributeModel):ng.IPromise<Models.AttributeModel>;
+ getComponentInstancesFilteredByInputsAndProperties(componentId:string, searchText:string):ng.IPromise<Array<Models.ComponentsInstances.ComponentInstance>>
+ getComponentInstanceInputs(componentId:string, instanceId:string, originComponentUid):ng.IPromise<Array<Models.InputModel>>;
+ getComponentInputs(componentId:string):ng.IPromise<Array<Models.InputModel>>;
+ getComponentInstanceInputProperties(componentId:string, instanceId:string, inputId:string):ng.IPromise<Array<Models.PropertyModel>>;
+ getModuleForDisplay(componentId:string, moduleId:string):ng.IPromise<Models.DisplayModule>;
+ updateGroupMetadata(componentId:string, group:Models.Module):ng.IPromise<Models.Module>;
+ getComponentInputInputs(serviceId:string, input:string): ng.IPromise<Array<Models.InputModel>>;
+ createInputsFromInstancesInputs(serviceId:string, instancesInputsMap:Models.InstancesInputsMap): ng.IPromise<Array<Models.InputModel>>;
+ createInputsFromInstancesInputsProperties(resourceId:string, instanceInputsPropertiesMap:Models.InstanceInputsPropertiesMap): ng.IPromise<Array<Models.PropertyModel>>;
+ deleteComponentInput(serviceId:string, inputId:string):ng.IPromise<Models.InputModel>;
+ }
+
+ export class ComponentService implements IComponentService {
+
+ static '$inject' = [
+ '$log',
+ 'Restangular',
+ 'sdcConfig',
+ 'Sdc.Services.SharingService',
+ '$q',
+ '$interval',
+ '$base64',
+ 'ComponentInstanceFactory'
+ ];
+
+ constructor(protected $log: ng.ILogService,
+ protected restangular:restangular.IElement,
+ protected sdcConfig:Models.IAppConfigurtaion,
+ protected sharingService:Sdc.Services.SharingService,
+ protected $q:ng.IQService,
+ protected $interval:any,
+ protected $base64:any,
+ protected ComponentInstanceFactory:Utils.ComponentInstanceFactory) {
+
+ this.restangular.setBaseUrl(sdcConfig.api.root + sdcConfig.api.component_api_root);
+ this.restangular.setRequestInterceptor(function (elem, operation) {
+ if (operation === "remove") {
+ return null;
+ }
+ return elem;
+ });
+ // this.restangular.setDefaultHeaders({'Content-Type': 'application/json; charset=UTF-8'});
+ }
+
+ //this function is override by each service, we need to change this method to abstract when updtaing typescript version
+ protected createComponentObject = (component:Models.Components.Component):Models.Components.Component => {
+ return component;
+ };
+
+ public getComponent = (id:string):ng.IPromise<Models.Components.Component> => {
+ let deferred = this.$q.defer();
+ this.restangular.one(id).get().then((response:Models.Components.Component) => {
+ let component:Models.Components.Component = this.createComponentObject(response);
+ //this.$log.debug("Component Loaded successfully : ", component);
+ deferred.resolve(component);
+ }, (err)=> {
+ this.$log.debug("Failed to load component with ID: " + id);
+ deferred.reject(err);
+ });
+ return deferred.promise;
+ };
+
+ public updateComponent = (component:Models.Components.Component):ng.IPromise<Models.Components.Component> => {
+ // If this is resource
+ if (component instanceof Sdc.Models.Components.Resource) {
+ let resource:Sdc.Models.Components.Resource = <Sdc.Models.Components.Resource>component;
+ if (resource.importedFile) {
+ // Update resource with payload data.
+ return this.updateResourceWithPayload(resource);
+ } else {
+ if (component.csarUUID) {
+ // Update resource without payload data.
+ return this.updateResource(component);
+ } else {
+ // Update resource without payload data (metadata).
+ return this.updateResourceMetadata(component);
+ }
+ }
+ } else {
+ return this.updateService(component);
+ }
+ };
+
+ private updateService = (component:Models.Components.Component):ng.IPromise<Models.Components.Component> => {
+ let deferred = this.$q.defer();
+ this.restangular.one(component.uniqueId).one("metadata").customPUT(JSON.stringify(component)).then((response:Models.Components.Component) => {
+ let component:Models.Components.Component = this.createComponentObject(response);
+ deferred.resolve(component);
+ }, (err)=> {
+ deferred.reject(err);
+ });
+ return deferred.promise;
+ };
+
+ private updateResource = (component:Models.Components.Component):ng.IPromise<Models.Components.Component> => {
+ let deferred = this.$q.defer();
+ this.restangular.one(component.uniqueId).customPUT(JSON.stringify(component)).then((response:Models.Components.Component) => {
+ let component:Models.Components.Component = this.createComponentObject(response);
+ deferred.resolve(component);
+ }, (err)=> {
+ deferred.reject(err);
+ });
+ return deferred.promise;
+ };
+
+ private updateResourceMetadata = (component:Models.Components.Component):ng.IPromise<Models.Components.Component> => {
+ let deferred = this.$q.defer();
+ this.restangular.one(component.uniqueId).one('metadata').customPUT(JSON.stringify(component)).then((response:Models.Components.Component) => {
+ let component:Models.Components.Component = this.createComponentObject(response);
+ deferred.resolve(component);
+ }, (err)=> {
+ deferred.reject(err);
+ });
+ return deferred.promise;
+ };
+
+ /**
+ * Only resource can be updated with payload data
+ * @param component
+ * @returns {IPromise<T>}
+ */
+ private updateResourceWithPayload = (resource:Sdc.Models.Components.Resource):ng.IPromise<Models.Components.Component> => {
+ let deferred = this.$q.defer();
+
+ resource.payloadData = resource.importedFile.base64;
+ resource.payloadName = resource.importedFile.filename;
+ let headerObj = this.getHeaderMd5(resource);
+
+ this.restangular.one(resource.uniqueId).customPUT(JSON.stringify(resource), '', {}, headerObj).then((response:Models.Components.Component) => {
+ let componentResult:Models.Components.Component = this.createComponentObject(response);
+ deferred.resolve(componentResult);
+ }, (err)=> {
+ deferred.reject(err);
+ });
+
+ return deferred.promise;
+ };
+
+ public createComponent = (component:Models.Components.Component):ng.IPromise<Models.Components.Component> => {
+ let deferred = this.$q.defer();
+ let headerObj = this.getHeaderMd5(component);
+ this.restangular.customPOST(JSON.stringify(component), '', {}, headerObj).then((response:Models.Components.Component) => {
+ let component:Models.Components.Component = this.createComponentObject(response);
+ deferred.resolve(component);
+ }, (err)=> {
+ deferred.reject(err);
+ });
+ return deferred.promise;
+ };
+
+ public validateName = (newName:string, subtype?:string):ng.IPromise<Models.IValidate> => {
+ let deferred = this.$q.defer();
+ this.restangular.one("validate-name").one(newName).get({'subtype': subtype}).then((response:any) => {
+ deferred.resolve(response.plain());
+ }, (err)=> {
+ deferred.reject(err);
+ });
+ return deferred.promise;
+ };
+
+ public changeLifecycleState = (component:Models.Components.Component, state:string, userRemarks:any):ng.IPromise<Models.Components.Component> => {
+ let deferred = this.$q.defer();
+ this.restangular.one(component.uniqueId).one(state).customPOST(userRemarks).then((response:Models.Components.Component) => {
+ this.sharingService.addUuidValue(response.uniqueId, response.uuid);
+ let component:Models.Components.Component = this.createComponentObject(response);
+ deferred.resolve(component);
+ }, (err)=> {
+ deferred.reject(err);
+ });
+ return deferred.promise;
+ };
+
+ // ------------------------------------------------ Artifacts API --------------------------------------------------//
+ public addOrUpdateArtifact = (componentId:string, artifact:Models.ArtifactModel):ng.IPromise<Models.ArtifactModel> => {
+ let deferred = this.$q.defer();
+ let headerObj = {};
+ if (artifact.payloadData) {
+ headerObj = this.getHeaderMd5(artifact);
+ }
+ this.restangular.one(componentId).one("artifacts").customPOST(JSON.stringify(artifact), artifact.uniqueId, {}, headerObj).then((response:any) => {
+ deferred.resolve(response.plain());
+ }, (err)=> {
+ deferred.reject(err);
+ });
+ return deferred.promise;
+ };
+
+ public downloadArtifact = (componentId:string, artifactId:string):ng.IPromise<Models.IFileDownload> => {
+ let deferred = this.$q.defer();
+ this.restangular.one(componentId).one("artifacts").one(artifactId).get().then((response:any) => {
+ deferred.resolve(response.plain());
+ }, (err)=> {
+ deferred.reject(err);
+ });
+ return deferred.promise;
+ };
+
+ public deleteArtifact = (componentId:string, artifactId:string, artifactLabel:string):ng.IPromise<Models.ArtifactModel> => {
+ let deferred = this.$q.defer();
+ this.restangular.one(componentId).one("artifacts").one(artifactId).remove({'operation': artifactLabel}).then((response:Models.ArtifactModel) => {
+ deferred.resolve(response);
+ }, (err)=> {
+ deferred.reject(err);
+ });
+ return deferred.promise;
+ };
+
+
+ // ------------------------------------------------ Properties API --------------------------------------------------//
+ public addProperty = (componentId:string, property:Models.PropertyModel):ng.IPromise<Models.PropertyModel> => {
+ let deferred = this.$q.defer();
+ this.restangular.one(componentId).one("properties").customPOST(property.convertToServerObject()).then((response:any) => {
+ let property:Models.PropertyModel = new Models.PropertyModel(response[Object.keys(response)[0]]);
+ deferred.resolve(property);
+ }, (err)=> {
+ deferred.reject(err);
+ });
+ return deferred.promise;
+ };
+
+ public updateProperty = (componentId:string, property:Models.PropertyModel):ng.IPromise<Models.PropertyModel> => {
+ let deferred = this.$q.defer();
+ this.restangular.one(componentId).one("properties").one(property.uniqueId).customPUT(property.convertToServerObject()).then((response:any) => {
+ let property:Models.PropertyModel = new Models.PropertyModel(response[Object.keys(response)[0]]);
+ deferred.resolve(property);
+ }, (err)=> {
+ deferred.reject(err);
+ });
+ return deferred.promise;
+ };
+
+ public deleteProperty = (componentId:string, propertyId:string):ng.IPromise<Models.PropertyModel> => {
+ let deferred = this.$q.defer();
+ this.restangular.one(componentId).one("properties").one(propertyId).remove().then((response:any) => {
+ deferred.resolve(response);
+ }, (err)=> {
+ deferred.reject(err);
+ });
+ return deferred.promise;
+ };
+
+ // ------------------------------------------------ Attributes API --------------------------------------------------//
+ public addAttribute = (componentId:string, attribute:Models.AttributeModel):ng.IPromise<Models.AttributeModel> => {
+ let deferred = this.$q.defer();
+ this.restangular.one(componentId).one("attributes").customPOST(attribute.convertToServerObject()).then((response:any) => {
+ let attribute:Models.AttributeModel = new Models.AttributeModel(response);
+ deferred.resolve(attribute);
+ }, (err)=> {
+ deferred.reject(err);
+ });
+ return deferred.promise;
+ };
+
+ public updateAttribute = (componentId:string, attribute:Models.AttributeModel):ng.IPromise<Models.AttributeModel> => {
+ let deferred = this.$q.defer();
+ this.restangular.one(componentId).one("attributes").one(attribute.uniqueId).customPUT(attribute.convertToServerObject()).then((response:any) => {
+ let attribute:Models.AttributeModel = new Models.AttributeModel(response);
+ deferred.resolve(attribute);
+ }, (err)=> {
+ deferred.reject(err);
+ });
+ return deferred.promise;
+ };
+
+ public deleteAttribute = (componentId:string, attributeId:string):ng.IPromise<Models.AttributeModel> => {
+ let deferred = this.$q.defer();
+ this.restangular.one(componentId).one("attributes").one(attributeId).remove().then((response:any) => {
+ deferred.resolve(response);
+ }, (err)=> {
+ deferred.reject(err);
+ });
+ return deferred.promise;
+ };
+
+ // ------------------------------------------------ Component Instances API --------------------------------------------------//
+
+ public createComponentInstance = (componentId:string, componentInstance:Models.ComponentsInstances.ComponentInstance):ng.IPromise<Models.ComponentsInstances.ComponentInstance> => {
+ let deferred = this.$q.defer();
+ this.restangular.one(componentId).one("resourceInstance").customPOST(JSON.stringify(componentInstance)).then((response:any) => {
+ let componentInstance:Models.ComponentsInstances.ComponentInstance = Utils.ComponentInstanceFactory.createComponentInstance(response);
+ this.$log.debug("Component Instance created", componentInstance);
+ deferred.resolve(componentInstance);
+ }, (err)=> {
+ this.$log.debug("Failed to create componentInstance. With Name: " + componentInstance.name);
+ deferred.reject(err);
+ });
+ return deferred.promise;
+ };
+
+ public updateComponentInstance = (componentId:string, componentInstance:Models.ComponentsInstances.ComponentInstance):ng.IPromise<Models.ComponentsInstances.ComponentInstance> => {
+ let deferred = this.$q.defer();
+ this.restangular.one(componentId).one("resourceInstance").one(componentInstance.uniqueId).customPOST(JSON.stringify(componentInstance)).then((response:any) => {
+ let componentInstance:Models.ComponentsInstances.ComponentInstance = Utils.ComponentInstanceFactory.createComponentInstance(response);
+ this.$log.debug("Component Instance was updated", componentInstance);
+ deferred.resolve(componentInstance);
+ }, (err)=> {
+ this.$log.debug("Failed to update componentInstance. With ID: " + componentInstance.uniqueId + "Name: " + componentInstance.name);
+ deferred.reject(err);
+ });
+ return deferred.promise;
+ };
+
+ public updateMultipleComponentInstances = (componentId:string, instances:Array<Models.ComponentsInstances.ComponentInstance>):ng.IPromise<Array<Models.ComponentsInstances.ComponentInstance>> => {
+ let deferred = this.$q.defer();
+ this.restangular.one(componentId).one("resourceInstance/multipleComponentInstance").customPOST(JSON.stringify(instances)).then((response:any) => {
+ this.$log.debug("Multiple Component Instances was updated", response);
+ let updateInstances:Array<Models.ComponentsInstances.ComponentInstance> = new Array<Models.ComponentsInstances.ComponentInstance>();
+ _.forEach(response, (componentInstance:Models.ComponentsInstances.ComponentInstance) => {
+ let updatedComponentInstance:Models.ComponentsInstances.ComponentInstance = Utils.ComponentInstanceFactory.createComponentInstance(componentInstance);
+ updateInstances.push(updatedComponentInstance);
+ });
+ deferred.resolve(updateInstances);
+ }, (err)=> {
+ this.$log.debug("Failed to update Multiple componentInstance.");
+ deferred.reject(err);
+ });
+ return deferred.promise;
+ };
+
+ public deleteComponentInstance = (componentId:string, componentInstanceId:string):ng.IPromise<Models.ComponentsInstances.ComponentInstance> => {
+ let deferred = this.$q.defer();
+ this.restangular.one(componentId).one("resourceInstance").one(componentInstanceId).remove().then(() => {
+ this.$log.debug("Component Instance was deleted");
+ deferred.resolve();
+ }, (err)=> {
+ this.$log.debug("Failed to delete componentInstance. With ID: " + componentInstanceId);
+ deferred.reject(err);
+ });
+ return deferred.promise;
+ };
+
+ public changeResourceInstanceVersion = (componentId:string, componentInstanceId:string, componentUid:string):ng.IPromise<Models.ComponentsInstances.ComponentInstance> => {
+ let deferred = this.$q.defer();
+ this.restangular.one(componentId).one("resourceInstance").one(componentInstanceId).one("changeVersion").customPOST({'componentUid': componentUid}).then((response:any) => {
+ let componentInstance:Models.ComponentsInstances.ComponentInstance = Utils.ComponentInstanceFactory.createComponentInstance(response);
+ deferred.resolve(componentInstance);
+ }, (err)=> {
+ deferred.reject(err);
+ });
+ return deferred.promise;
+ };
+
+ public downloadInstanceArtifact = (componentId:string, instanceId:string, artifactId:string):ng.IPromise<Models.IFileDownload> => {
+ let deferred = this.$q.defer();
+ this.restangular.one(componentId).one("resourceInstances").one(instanceId).one("artifacts").one(artifactId).get().then((response:any) => {
+ deferred.resolve(response.plain());
+ }, (err)=> {
+ deferred.reject(err);
+ });
+ return deferred.promise;
+ };
+
+ public updateInstanceArtifact = (componentId:string, instanceId:string, artifact:Models.ArtifactModel):ng.IPromise<Models.ArtifactModel> => {
+ let deferred = this.$q.defer();
+ let headerObj = {};
+ if(artifact.payloadData){
+ headerObj = this.getHeaderMd5(artifact);
+ }
+ this.restangular.one(componentId).one("resourceInstance").one(instanceId).one("artifacts").customPOST(JSON.stringify(artifact), artifact.uniqueId , {}, headerObj).then((response: any) => {
+ let newArtifact = new Models.ArtifactModel(response);
+ deferred.resolve(newArtifact);
+ }, (err)=>{
+ deferred.reject(err);
+ });
+ return deferred.promise;
+ };
+
+ public addInstanceArtifact = (componentId: string, instanceId: string, artifact:Models.ArtifactModel): ng.IPromise<Models.ArtifactModel> => {
+ let deferred = this.$q.defer();
+ let headerObj = {};
+ if(artifact.payloadData){
+ headerObj = this.getHeaderMd5(artifact);
+ }
+ this.restangular.one(componentId).one("resourceInstance").one(instanceId).one("artifacts").customPOST(JSON.stringify(artifact), artifact.uniqueId , {}, headerObj).then((response: any) => {
+ let artifact:Models.ArtifactModel = new Models.ArtifactModel(response.plain());
+ deferred.resolve(artifact);
+ }, (err)=> {
+ deferred.reject(err);
+ });
+ return deferred.promise;
+ };
+
+ public deleteInstanceArtifact = (componentId: string , instanceId: string, artifactId:string, artifactLabel: string): ng.IPromise<Models.ArtifactModel> => {
+ let deferred = this.$q.defer();
+ this.restangular.one(componentId).one("resourceInstance").one(instanceId).one("artifacts").one(artifactId).remove({'operation': artifactLabel}).then((response: Models.ArtifactModel) => {
+ deferred.resolve(response);
+ }, (err)=>{
+ deferred.reject(err);
+ });
+ return deferred.promise;
+ };
+
+ public uploadInstanceEnvFile = (componentId:string, instanceId:string, artifact:Models.ArtifactModel):ng.IPromise<Models.ArtifactModel> => {
+ let deferred = this.$q.defer();
+ let headerObj = {};
+ if (artifact.payloadData) {
+ headerObj = this.getHeaderMd5(artifact);
+ }
+ this.restangular.one(componentId).one("resourceInstance").one(instanceId).one("artifacts").customPOST(JSON.stringify(artifact), artifact.uniqueId, {}, headerObj).then((response:any) => {
+ let newArtifact = new Models.ArtifactModel(response);
+ deferred.resolve(newArtifact);
+ }, (err)=> {
+ deferred.reject(err);
+ });
+ return deferred.promise;
+ };
+
+ public updateInstanceProperty = (componentId:string, property:Models.PropertyModel):ng.IPromise<Models.PropertyModel> => {
+ let deferred = this.$q.defer();
+ let instanceId = property.resourceInstanceUniqueId;
+ this.restangular.one(componentId).one("resourceInstance").one(instanceId).one("property").customPOST(JSON.stringify(property)).then((response:any) => {
+ let newProperty = new Models.PropertyModel(response);
+ newProperty.readonly = true;
+ newProperty.resourceInstanceUniqueId = instanceId;
+ deferred.resolve(newProperty);
+ }, (err)=> {
+ deferred.reject(err);
+ });
+ return deferred.promise;
+ };
+
+ public updateInstanceAttribute = (componentId:string, attribute:Models.AttributeModel):ng.IPromise<Models.AttributeModel> => {
+ let deferred = this.$q.defer();
+ let instanceId = attribute.resourceInstanceUniqueId;
+ this.restangular.one(componentId).one("resourceInstance").one(instanceId).one("attribute").customPOST(JSON.stringify(attribute)).then((response:any) => {
+ let newAttribute = new Models.AttributeModel(response);
+ newAttribute.readonly = true;
+ newAttribute.resourceInstanceUniqueId = instanceId;
+ deferred.resolve(newAttribute);
+ }, (err)=> {
+ deferred.reject(err);
+ });
+ return deferred.promise;
+ };
+
+ public createRelation = (componentId:string, link:Models.RelationshipModel):ng.IPromise<Models.RelationshipModel> => {
+ let deferred = this.$q.defer();
+ this.restangular.one(componentId).one("resourceInstance").one("associate").customPOST(JSON.stringify(link)).then((response:any) => {
+ let relation:Models.RelationshipModel = new Models.RelationshipModel(response.plain());
+ this.$log.debug("Link created successfully ", relation);
+ deferred.resolve(relation);
+ }, (err)=> {
+ this.$log.debug("Failed to create Link From: " + link.fromNode + "To: " + link.toNode);
+ deferred.reject(err);
+ });
+ return deferred.promise;
+ };
+
+ public deleteRelation = (componentId:string, link:Models.RelationshipModel):ng.IPromise<Models.RelationshipModel> => {
+ let deferred = this.$q.defer();
+ this.restangular.one(componentId).one("resourceInstance").one("dissociate").customPUT(JSON.stringify(link)).then((response:any) => {
+ let relation:Models.RelationshipModel = new Models.RelationshipModel(response);
+ this.$log.debug("Link deleted successfully ", relation);
+ deferred.resolve(relation);
+ }, (err)=> {
+ this.$log.debug("Failed to delete Link From: " + link.fromNode + "To: " + link.toNode);
+ deferred.reject(err);
+ });
+ return deferred.promise;
+ };
+
+ public getRequirementsCapabilities = (componentId:string):ng.IPromise<any> => {
+ let deferred = this.$q.defer();
+ this.restangular.one(componentId).one("requirmentsCapabilities").get().then((response:any) => {
+ this.$log.debug("Component requirement capabilities recived: ", response);
+ deferred.resolve(response);
+ }, (err)=> {
+ this.$log.debug("Failed to get requirements & capabilities");
+ deferred.reject(err);
+ });
+ return deferred.promise;
+ };
+
+ public getModuleForDisplay = (componentId:string, moduleId:string):ng.IPromise<Models.DisplayModule> => {
+ let deferred = this.$q.defer();
+ this.restangular.one(componentId).one("groups").one(moduleId).get().then((response:any) => {
+ this.$log.debug("module loaded successfully: ", response);
+ let module:Models.DisplayModule = new Models.DisplayModule(response);
+ deferred.resolve(module);
+ }, (err)=> {
+ this.$log.debug("Failed to get module with id: ", moduleId);
+ deferred.reject(err);
+ });
+ return deferred.promise;
+ };
+
+ public getComponentInstancesFilteredByInputsAndProperties = (componentId:string, searchText?:string):ng.IPromise<Array<Models.ComponentsInstances.ComponentInstance>> => {
+ let deferred = this.$q.defer();
+ this.restangular.one(componentId).one("componentInstances").get({'searchText': searchText}).then((response:any) => {
+ this.$log.debug("component instances return successfully: ", response);
+ let componentInstances:Array<Models.ComponentsInstances.ComponentInstance> = Utils.CommonUtils.initComponentInstances(response);
+ deferred.resolve(componentInstances);
+ }, (err) => {
+ this.$log.debug("Failed to get component instances of component with id: " + componentId);
+ deferred.reject(err);
+ });
+
+ return deferred.promise;
+ };
+
+ public getComponentInstanceInputs = (componentId:string, instanceId:string, originComponentUid):ng.IPromise<Array<Models.InputModel>> => {
+
+ let deferred = this.$q.defer();
+ this.restangular.one(componentId).one("componentInstances").one(instanceId).one(originComponentUid).one("inputs").get().then((response:any) => {
+ this.$log.debug("component instance input return successfully: ", response);
+ let inputsArray:Array<Models.InputModel> = new Array<Models.InputModel>();
+ _.forEach(response, (inputObj:Models.InputModel) => {
+ inputsArray.push(new Models.InputModel(inputObj));
+ });
+ deferred.resolve(inputsArray);
+ }, (err) => {
+ this.$log.debug("Failed to get component instance input with id: " + instanceId);
+ deferred.reject(err);
+ });
+
+ return deferred.promise;
+ };
+
+ public getComponentInputs = (componentId:string):ng.IPromise<Array<Models.InputModel>> => {
+
+ let deferred = this.$q.defer();
+ this.restangular.one(componentId).one("inputs").get().then((response:any) => {
+ this.$log.debug("component inputs return successfully: ", response);
+ let inputsArray:Array<Models.InputModel> = new Array<Models.InputModel>();
+ _.forEach(response, (inputObj:Models.InputModel) => {
+ inputsArray.push(new Models.InputModel(inputObj));
+ });
+ deferred.resolve(inputsArray);
+ }, (err) => {
+ this.$log.debug("Failed to get component inputs for component with id: " + componentId);
+ deferred.reject(err);
+ });
+
+ return deferred.promise;
+ };
+
+ public getComponentInstanceInputProperties = (componentId:string, instanceId:string, inputId:string):ng.IPromise<Array<Models.PropertyModel>> => {
+
+ let deferred = this.$q.defer();
+ this.restangular.one(componentId).one("componentInstances").one(instanceId).one(inputId).one("properties").get().then((response:any) => {
+ this.$log.debug("component instance input properties return successfully: ", response);
+ let propertiesArray:Array<Models.PropertyModel> = new Array<Models.PropertyModel>();
+ _.forEach(response, (propertyObj:Models.PropertyModel) => {
+ propertiesArray.push(new Models.PropertyModel(propertyObj));
+ });
+ deferred.resolve(propertiesArray);
+ }, (err) => {
+ this.$log.debug("Failed to get component instance input properties with instanceId: " + instanceId + "and input id: " + inputId);
+ deferred.reject(err);
+ });
+
+ return deferred.promise;
+ };
+
+ public updateGroupMetadata = (componentId:string, group:Models.Module):ng.IPromise<Models.Module> => {
+
+ let deferred = this.$q.defer();
+ this.restangular.one(componentId).one("groups").one(group.uniqueId).one("metadata").customPUT(JSON.stringify(group)).then((response:Models.Module) => {
+ this.$log.debug("group metadata updated successfully: ", response);
+ let updatedGroup:Models.Module = new Models.Module(response);
+
+ deferred.resolve(updatedGroup);
+ }, (err) => {
+ this.$log.debug("Failed to update group metadata for component: " + componentId + " for group with id: " + group.uniqueId);
+ deferred.reject(err);
+ });
+
+ return deferred.promise;
+ };
+
+ public getComponentInputInputs = (serviceId:string, inputId:string): ng.IPromise<Array<Models.InputModel>> => {
+ let defer = this.$q.defer<any>();
+ this.restangular.one(serviceId).one("inputs").one(inputId).one("inputs").get().then((response: any) => {
+ let inputsArray:Array<Models.InputModel> = new Array<Models.InputModel>();
+ _.forEach(response, (inputObj:Models.InputModel) => {
+ inputsArray.push(new Models.InputModel(inputObj));
+ });
+ defer.resolve(inputsArray);
+ }, (err)=>{
+ this.$log.debug("failed to get inputs of input : ", err);
+ defer.reject(err);
+ });
+ return defer.promise;
+ };
+
+ createInputsFromInstancesInputsProperties = (resourceId:string, instancePropertyMap:Models.InstanceInputsPropertiesMap): ng.IPromise<Array<Models.PropertyModel>> => {
+ let defer = this.$q.defer<any>();
+ this.restangular.one(resourceId).one("create/properties").customPOST(instancePropertyMap).then((response: any) => {
+ let inputsArray:Array<Models.PropertyModel> = new Array<Models.PropertyModel>();
+ _.forEach(response, (inputObj:Models.PropertyModel) => {
+ inputsArray.push(new Models.PropertyModel(inputObj));
+ });
+ defer.resolve(inputsArray);
+ }, (err)=>{
+ this.$log.debug("failed to create service inputs from VF instances inputs : ", err);
+ defer.reject(err);
+ });
+ return defer.promise;
+ };
+
+ createInputsFromInstancesInputs = (serviceId:string, instancesMap:Models.InstancesInputsMap): ng.IPromise<Array<Models.InputModel>> => {
+ let defer = this.$q.defer<any>();
+ this.restangular.one(serviceId).one("create/inputs").customPOST(instancesMap).then((response: any) => {
+ let inputsArray:Array<Models.InputModel> = new Array<Models.InputModel>();
+ _.forEach(response, (inputObj:Models.InputModel) => {
+ inputsArray.push(new Models.InputModel(inputObj));
+ });
+ defer.resolve(inputsArray);
+ }, (err)=>{
+ this.$log.debug("failed to create service inputs from VF instances inputs : ", err);
+ defer.reject(err);
+ });
+ return defer.promise;
+ };
+
+ deleteComponentInput = (serviceId:string, inputId:string) : ng.IPromise<Models.InputModel> => {
+ var defer = this.$q.defer();
+ this.restangular.one(serviceId).one("delete").one(inputId).one("input").remove().then((response: any) => {
+ var inputToDelete = new Models.InputModel(response);
+
+ defer.resolve(inputToDelete);
+ }, (err)=> {
+ console.log("failed to delete input from service: ", err);
+ defer.reject(err);
+ });
+ return defer.promise;
+ };
+
+ private getHeaderMd5 = (object:any):any => {
+ let headerObj={};
+ // This is ugly workaround!!!
+ // The md5 result is not correct if we do not add the line JSON.stringify(resource); twice.
+ JSON.stringify(object);
+ let componentString:string = JSON.stringify(object);
+ let md5Result = md5(componentString).toLowerCase();
+ headerObj = {'Content-MD5': this.$base64.encode(md5Result)};
+ return headerObj;
+ };
+
+ }
+}
diff --git a/catalog-ui/app/scripts/services/components/product-service.ts b/catalog-ui/app/scripts/services/components/product-service.ts
new file mode 100644
index 0000000000..69171fbbfa
--- /dev/null
+++ b/catalog-ui/app/scripts/services/components/product-service.ts
@@ -0,0 +1,61 @@
+/*-
+ * ============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 2/8/2016.
+ */
+/// <reference path="../../references"/>
+module Sdc.Services.Components {
+ 'use strict';
+
+ export interface IProductService extends IComponentService {
+
+ }
+
+ export class ProductService extends ComponentService implements IProductService {
+
+ static '$inject' = [
+ '$log',
+ 'Restangular',
+ 'sdcConfig',
+ 'Sdc.Services.SharingService',
+ '$q',
+ '$interval',
+ '$base64',
+ 'ComponentInstanceFactory'
+ ];
+
+ constructor(protected $log: ng.ILogService,
+ protected restangular: restangular.IElement,
+ protected sdcConfig: Models.IAppConfigurtaion,
+ protected sharingService: Sdc.Services.SharingService,
+ protected $q: ng.IQService,
+ protected $interval: any,
+ protected $base64: any,
+ protected ComponentInstanceFactory: Utils.ComponentInstanceFactory) {
+ super($log, restangular, sdcConfig, sharingService, $q, $interval, $base64, ComponentInstanceFactory);
+
+ this.restangular = restangular.one("products");
+ }
+
+ createComponentObject = (component: Models.Components.Component): Models.Components.Component => {
+ return new Models.Components.Product(this, this.$q, <Models.Components.Product>component);
+ };
+ }
+}
diff --git a/catalog-ui/app/scripts/services/components/resource-service.ts b/catalog-ui/app/scripts/services/components/resource-service.ts
new file mode 100644
index 0000000000..48781da48e
--- /dev/null
+++ b/catalog-ui/app/scripts/services/components/resource-service.ts
@@ -0,0 +1,61 @@
+/*-
+ * ============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 2/4/2016.
+ */
+/// <reference path="../../references"/>
+module Sdc.Services.Components {
+ 'use strict';
+
+ export interface IResourceService extends IComponentService {
+
+ }
+
+ export class ResourceService extends ComponentService implements IResourceService {
+
+ static '$inject' = [
+ '$log',
+ 'Restangular',
+ 'sdcConfig',
+ 'Sdc.Services.SharingService',
+ '$q',
+ '$interval',
+ '$base64',
+ 'ComponentInstanceFactory'
+ ];
+
+ constructor(protected $log: ng.ILogService,
+ protected restangular: restangular.IElement,
+ protected sdcConfig: Models.IAppConfigurtaion,
+ protected sharingService: Sdc.Services.SharingService,
+ protected $q: ng.IQService,
+ protected $interval: any,
+ protected $base64: any,
+ protected ComponentInstanceFactory: Utils.ComponentInstanceFactory) {
+ super($log, restangular, sdcConfig, sharingService, $q, $interval, $base64, ComponentInstanceFactory);
+
+ this.restangular = restangular.one("resources");
+ }
+
+ createComponentObject = (component: Models.Components.Component): Models.Components.Component => {
+ return new Models.Components.Resource(this, this.$q, <Models.Components.Resource>component);
+ };
+ }
+}
diff --git a/catalog-ui/app/scripts/services/components/service-service.ts b/catalog-ui/app/scripts/services/components/service-service.ts
new file mode 100644
index 0000000000..fef0b47512
--- /dev/null
+++ b/catalog-ui/app/scripts/services/components/service-service.ts
@@ -0,0 +1,97 @@
+/*-
+ * ============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 2/4/2016.
+ */
+/// <reference path="../../references"/>
+module Sdc.Services.Components {
+ 'use strict';
+
+
+ export interface IServiceService extends IComponentService {
+ getDistributionsList(uuid: string): ng.IPromise<Array<Models.Distribution>>;
+ getDistributionComponents(distributionId: string): ng.IPromise<Array<Models.DistributionComponent>>;
+ markAsDeployed(serviceId: string, distributionId: string): ng.IPromise<any>;
+ }
+
+ export class ServiceService extends ComponentService implements IServiceService {
+
+ static '$inject' = [
+ '$log',
+ 'Restangular',
+ 'sdcConfig',
+ 'Sdc.Services.SharingService',
+ '$q',
+ '$interval',
+ '$base64',
+ 'ComponentInstanceFactory'
+ ];
+
+ public distribution: string = "distribution";
+
+ constructor(protected $log: ng.ILogService,
+ protected restangular: restangular.IElement,
+ protected sdcConfig: Models.IAppConfigurtaion,
+ protected sharingService: Sdc.Services.SharingService,
+ protected $q: ng.IQService,
+ protected $interval: any,
+ protected $base64: any,
+ protected ComponentInstanceFactory: Utils.ComponentInstanceFactory) {
+ super($log, restangular, sdcConfig, sharingService, $q, $interval, $base64, ComponentInstanceFactory);
+
+ this.restangular = restangular.one("services");
+ }
+
+ getDistributionsList = (uuid: string): ng.IPromise<Array<Models.Distribution>> => {
+ let defer = this.$q.defer<Array<Models.Distribution>>();
+ this.restangular.one(uuid).one("distribution").get().then((distributions: any) => {
+ defer.resolve(<Array<Models.Distribution>> distributions.distributionStatusOfServiceList);
+ }, (err)=> {
+ defer.reject(err);
+ });
+ return defer.promise;
+ };
+
+ getDistributionComponents = (distributionId: string): ng.IPromise<Array<Models.DistributionComponent>> => {
+ let defer = this.$q.defer<Array<Models.DistributionComponent>>();
+ this.restangular.one("distribution").one(distributionId).get().then((distributions: any) => {
+ defer.resolve(<Array<Models.DistributionComponent>> distributions.distributionStatusList);
+ }, (err)=> {
+ defer.reject(err);
+ });
+ return defer.promise;
+ };
+
+ markAsDeployed = (serviceId: string, distributionId: string): ng.IPromise<any> => {
+ let defer = this.$q.defer<any>();
+ this.restangular.one(serviceId).one("distribution").one(distributionId).one("markDeployed").customPOST().then((result: any) => {
+ defer.resolve(result);
+ }, (err)=> {
+
+ defer.reject(err);
+ });
+ return defer.promise;
+ };
+
+ createComponentObject = (component: Models.Components.Component): Models.Components.Component => {
+ return new Models.Components.Service(this, this.$q, <Models.Components.Service>component);
+ };
+ }
+}
diff --git a/catalog-ui/app/scripts/services/components/utils/composition-left-palette-service.ts b/catalog-ui/app/scripts/services/components/utils/composition-left-palette-service.ts
new file mode 100644
index 0000000000..25ac1cdf17
--- /dev/null
+++ b/catalog-ui/app/scripts/services/components/utils/composition-left-palette-service.ts
@@ -0,0 +1,248 @@
+/*-
+ * ============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/13/2016.
+ */
+/// <reference path="../../../references"/>
+module Sdc.Services.Components {
+
+ 'use strict';
+
+ export class LeftPanelLatestVersion {
+ uid: string;
+ version: string;
+ }
+
+ export class LeftPaletteDataObject {
+ currentUpdatingIdsList: Array<string>;
+ latestVersionAndIdsList: Array<LeftPanelLatestVersion>;
+ fullDataLeftPaletteComponents: Array<Models.Components.Component>;
+ displayLeftPanelComponents: Array<Models.DisplayComponent>;
+ onFinishLoadingEvent: string;
+
+ constructor(onFinishEventListener: string) {
+
+ this.fullDataLeftPaletteComponents = new Array<Models.Components.Component>();
+ this.displayLeftPanelComponents = new Array<Models.DisplayComponent>();
+ this.currentUpdatingIdsList = new Array<string>();
+ this.latestVersionAndIdsList = new Array<LeftPanelLatestVersion>();
+ this.onFinishLoadingEvent = onFinishEventListener;
+ }
+ }
+
+ export class LeftPaletteLoaderService {
+
+ static '$inject' = [
+ 'Restangular',
+ 'sdcConfig',
+ '$q',
+ '$base64',
+ 'ComponentFactory',
+ 'EventListenerService'
+
+ ];
+
+ constructor(protected restangular: restangular.IElement,
+ protected sdcConfig: Models.IAppConfigurtaion,
+ protected $q: ng.IQService,
+ protected $base64: any,
+ protected ComponentFactory: Utils.ComponentFactory,
+ protected EventListenerService: Services.EventListenerService) {
+
+ this.restangular.setBaseUrl(sdcConfig.api.root + sdcConfig.api.component_api_root);
+ }
+
+ private serviceLeftPaletteData: LeftPaletteDataObject;
+ private resourceLeftPaletteData: LeftPaletteDataObject;
+ private productLeftPaletteData: LeftPaletteDataObject;
+ private vlData: LeftPaletteDataObject;
+
+ public loadLeftPanel = (): void => {
+
+ this.serviceLeftPaletteData = new LeftPaletteDataObject(Utils.Constants.EVENTS.SERVICE_LEFT_PALETTE_UPDATE_EVENT);
+ this.resourceLeftPaletteData = new LeftPaletteDataObject(Utils.Constants.EVENTS.RESOURCE_LEFT_PALETTE_UPDATE_EVENT);
+ this.productLeftPaletteData = new LeftPaletteDataObject(Utils.Constants.EVENTS.PRODUCT_LEFT_PALETTE_UPDATE_EVENT);
+ this.vlData = new LeftPaletteDataObject(Utils.Constants.EVENTS.VL_LEFT_PALETTE_UPDATE_EVENT);
+
+ //initiating service palette
+ this.updateComponentLeftPalette(Utils.Constants.ComponentType.SERVICE);
+
+ //initiating resource palette
+ this.updateComponentLeftPalette(Utils.Constants.ComponentType.RESOURCE);
+
+ //initiating product palette
+ this.updateComponentLeftPalette(Utils.Constants.ComponentType.PRODUCT);
+
+ //initiating vl
+ this.updateComponentLeftPalette(Utils.Constants.ResourceType.VL);
+ };
+
+ private updateData = (latestVersionComponents: Array<Models.Components.Component>, leftPaletteDataObj: LeftPaletteDataObject) => {
+
+ let fullDataComponentsArray: Array<Models.Components.Component> = new Array<Models.Components.Component>();
+ let displayComponentsArray: Array<Models.DisplayComponent> = new Array<Models.DisplayComponent>();
+
+ _.forEach(latestVersionComponents, (componentObj: any) => {
+ let component: Models.Components.Component = this.ComponentFactory.createComponent(componentObj);
+ fullDataComponentsArray.push(component);
+ displayComponentsArray.push(new Models.DisplayComponent(component));
+ });
+
+ leftPaletteDataObj.fullDataLeftPaletteComponents = leftPaletteDataObj.fullDataLeftPaletteComponents.concat(fullDataComponentsArray);
+ leftPaletteDataObj.displayLeftPanelComponents = leftPaletteDataObj.displayLeftPanelComponents.concat(displayComponentsArray);
+ };
+
+ private getTypeUrl = (componentType: string): string => {
+ return Utils.Constants.ComponentType.PRODUCT === componentType ? "services" : "resources";
+ };
+
+ private onFinishLoading = (componentType: string, leftPaletteData: LeftPaletteDataObject): void => {
+ leftPaletteData.currentUpdatingIdsList = [];
+ this.EventListenerService.notifyObservers(leftPaletteData.onFinishLoadingEvent);
+ };
+
+ private getPartialLastVersionFullComponents = (componentType: string, componentInternalType: string, leftPaletteData: LeftPaletteDataObject): void => {
+ this.restangular.one(this.getTypeUrl(componentType)).one('/latestversion/notabstract').customPOST(leftPaletteData.currentUpdatingIdsList, '', {'internalComponentType': componentInternalType}).then((componentsArray: any) => {
+ this.updateData(componentsArray, leftPaletteData);
+ this.onFinishLoading(componentType, leftPaletteData); //when finish loading update view
+ });
+ };
+
+ private removeNotUpdatedComponents = (leftPaletteObject: LeftPaletteDataObject) => {
+
+ leftPaletteObject.fullDataLeftPaletteComponents = _.filter(leftPaletteObject.fullDataLeftPaletteComponents, (component)=> {
+ return leftPaletteObject.currentUpdatingIdsList.indexOf(component.uniqueId) != -1;
+ });
+ leftPaletteObject.displayLeftPanelComponents = _.filter(leftPaletteObject.displayLeftPanelComponents, (component)=> {
+ return leftPaletteObject.currentUpdatingIdsList.indexOf(component.uniqueId) != -1;
+ });
+ };
+
+ private findIdsToUpdate = (leftPaletteObj: LeftPaletteDataObject): Array<string> => {
+ let idsToUpdate = <string[]>_.difference(leftPaletteObj.currentUpdatingIdsList, _.map(leftPaletteObj.fullDataLeftPaletteComponents, 'uniqueId'));
+ let neededUpdate = _.filter(leftPaletteObj.fullDataLeftPaletteComponents, (component) => {
+ let updated = _.find(leftPaletteObj.latestVersionAndIdsList, (versionAndId: LeftPanelLatestVersion) => {
+ return versionAndId.uid === component.uniqueId && versionAndId.version != component.version
+ });
+ return updated != undefined;
+ });
+ if (neededUpdate && neededUpdate.length > 0) {
+ let neededUpdateIds = <string[]>_.map(neededUpdate, 'uid');
+ idsToUpdate.concat(neededUpdateIds);
+ }
+ return idsToUpdate;
+ };
+
+ private updateCurrentIdsList = (componentType: string, leftPaletteObj: LeftPaletteDataObject): void=> {
+ this.removeNotUpdatedComponents(leftPaletteObj);
+ leftPaletteObj.currentUpdatingIdsList = this.findIdsToUpdate(leftPaletteObj);
+ //remove all components that needed update from current lists
+ if (leftPaletteObj.currentUpdatingIdsList.length > 0) {
+ leftPaletteObj.displayLeftPanelComponents = _.filter(leftPaletteObj.displayLeftPanelComponents, (component)=> {
+ return leftPaletteObj.currentUpdatingIdsList.indexOf(component.uniqueId) === -1;
+ });
+ leftPaletteObj.fullDataLeftPaletteComponents = _.filter(leftPaletteObj.fullDataLeftPaletteComponents, (component)=> {
+ return leftPaletteObj.currentUpdatingIdsList.indexOf(component.uniqueId) === -1;
+ });
+ }
+ };
+
+ private updateLeftPalette = (componentType, componentInternalType: string, leftPaletteData: LeftPaletteDataObject): void => {
+ if (leftPaletteData.currentUpdatingIdsList.length > 0) return; //this means the service is still performing update
+ this.restangular.one(this.getTypeUrl(componentType)).one('/latestversion/notabstract/uidonly').get({'internalComponentType': componentInternalType}).then((latestVersionUniqueIds: Array<LeftPanelLatestVersion>) => {
+ leftPaletteData.latestVersionAndIdsList = latestVersionUniqueIds;
+ leftPaletteData.currentUpdatingIdsList = <string[]>_.map(latestVersionUniqueIds, 'uid')
+
+ if (leftPaletteData.fullDataLeftPaletteComponents.length === 0) { //this is when first loading product or resource left palette
+ this.getPartialLastVersionFullComponents(componentType, componentInternalType, leftPaletteData);
+ } else {
+ this.updateCurrentIdsList(componentType, leftPaletteData);
+ if (leftPaletteData.currentUpdatingIdsList.length === 0) {
+ this.onFinishLoading(componentType, leftPaletteData); //when finish loading update view
+ return;
+ }
+ this.getPartialLastVersionFullComponents(componentType, componentInternalType, leftPaletteData);
+ }
+ });
+ };
+
+ public getLeftPanelComponentsForDisplay = (componentType: string): Array<Models.DisplayComponent> => {
+ switch (componentType) {
+ case Utils.Constants.ComponentType.SERVICE:
+ return this.serviceLeftPaletteData.displayLeftPanelComponents;
+ case Utils.Constants.ComponentType.PRODUCT:
+ return this.productLeftPaletteData.displayLeftPanelComponents;
+ default:
+ return this.resourceLeftPaletteData.displayLeftPanelComponents;
+ }
+ };
+
+ public getFullDataComponentList = (componentType: string): Array<Models.Components.Component> => {
+ switch (componentType) {
+ case Utils.Constants.ResourceType.VL:
+ return this.vlData.fullDataLeftPaletteComponents;
+ case Utils.Constants.ComponentType.SERVICE:
+ return this.serviceLeftPaletteData.fullDataLeftPaletteComponents;
+ case Utils.Constants.ComponentType.PRODUCT:
+ return this.productLeftPaletteData.fullDataLeftPaletteComponents;
+ default :
+ return this.resourceLeftPaletteData.fullDataLeftPaletteComponents;
+ }
+ };
+
+ public getFullDataComponentListWithVls = (componentType: string): Array<Models.Components.Component> => {
+ let listPart1: Array<Models.Components.Component>;
+ let listPart2: Array<Models.Components.Component>;
+ if (componentType === Utils.Constants.ResourceType.VL) {
+ listPart1 = [];
+ listPart2 = this.getFullDataComponentList(Utils.Constants.ResourceType.VL);
+ } else {
+ listPart1 = this.getFullDataComponentList(componentType);
+ listPart2 = this.getFullDataComponentList(Utils.Constants.ResourceType.VL);
+ }
+ return listPart1.concat(listPart2);
+ };
+
+ public updateSpecificComponentLeftPalette = (component: Models.Components.Component, componentType: string): void => {
+ let listComponents: Array<Models.Components.Component> = this.getFullDataComponentList(componentType);
+ for (let i in listComponents) {
+ if (listComponents[i].uniqueId === component.uniqueId) {
+ listComponents[i] = component;
+ }
+ }
+ };
+
+ public updateComponentLeftPalette = (componentType): void => {
+ switch (componentType) {
+ case Utils.Constants.ResourceType.VL:
+ this.updateLeftPalette(Utils.Constants.ComponentType.RESOURCE, Utils.Constants.ResourceType.VL, this.vlData);
+ break;
+ case Utils.Constants.ComponentType.SERVICE:
+ this.updateLeftPalette(Utils.Constants.ComponentType.SERVICE, Utils.Constants.ComponentType.SERVICE, this.serviceLeftPaletteData);
+ break;
+ case Utils.Constants.ComponentType.PRODUCT:
+ this.updateLeftPalette(Utils.Constants.ComponentType.PRODUCT, Utils.Constants.ComponentType.SERVICE, this.productLeftPaletteData);
+ break;
+ default:
+ this.updateLeftPalette(Utils.Constants.ComponentType.RESOURCE, Utils.Constants.ResourceType.VF, this.resourceLeftPaletteData);
+ }
+ };
+ }
+}
diff --git a/catalog-ui/app/scripts/services/configuration-ui-service.ts b/catalog-ui/app/scripts/services/configuration-ui-service.ts
new file mode 100644
index 0000000000..51eb3dcd9c
--- /dev/null
+++ b/catalog-ui/app/scripts/services/configuration-ui-service.ts
@@ -0,0 +1,49 @@
+/*-
+ * ============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.Services {
+
+ 'use strict'
+
+ interface IConfigurationUiService {
+ getConfigurationUi(): ng.IPromise<any>;
+ }
+
+ export class ConfigurationUiService implements IConfigurationUiService{
+
+ static '$inject' = ['$http', '$q','sdcConfig'];
+ private api: Models.IApi;
+
+ constructor(private $http: ng.IHttpService, private $q: ng.IQService, sdcConfig: Models.IAppConfigurtaion){
+ this.api = sdcConfig.api;
+ }
+
+ getConfigurationUi = (): ng.IPromise<any> =>{
+ let defer = this.$q.defer<any>();
+ this.$http.get(this.api.root+this.api.GET_configuration_ui)
+ .success((result: any) => {
+ defer.resolve(result);
+ });
+ return defer.promise;
+ }
+ }
+
+
+}
diff --git a/catalog-ui/app/scripts/services/cookie-service.ts b/catalog-ui/app/scripts/services/cookie-service.ts
new file mode 100644
index 0000000000..b23a7dccde
--- /dev/null
+++ b/catalog-ui/app/scripts/services/cookie-service.ts
@@ -0,0 +1,95 @@
+/*-
+ * ============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.Services {
+
+ 'use strict';
+
+ interface ICookieService {
+ getUserId(): string;
+ getFirstName(): string;
+ getLastName(): string;
+ getEmail(): string;
+ getUserIdSuffix(): string;
+ }
+
+ export class CookieService implements ICookieService {
+
+ static '$inject' = ['sdcConfig', '$document'];
+ private cookie: Sdc.Models.ICookie;
+ private cookiePrefix: string;
+
+
+ constructor(sdcConfig: Models.IAppConfigurtaion, private $document) {
+ this.cookie = sdcConfig.cookie;
+
+ this.cookiePrefix = '';
+ let junctionName: string = this.getCookieByName(this.cookie.junctionName);
+ if ((junctionName !== null) && (junctionName !== '')){
+ this.cookiePrefix = this.cookie.prefix+junctionName+'!';
+ }
+ }
+
+ private getCookieByName = (cookieName: string): string => {
+ cookieName += '=';
+ let cookies: Array<string> = this.$document[0].cookie.split(';');
+ let cookieVal: string = '';
+ cookies.forEach((cookie: string) => {
+ while(cookie.charAt(0) === ' '){
+ cookie = cookie.substring(1);
+ }
+ if(cookie.indexOf(cookieName) === 0){
+ cookieVal = cookie.substring(cookieName.length, cookie.length);
+ return;
+ }
+ });
+ return cookieVal;
+ };
+
+ public getUserIdSuffix = (): string => {
+ return this.cookie.userIdSuffix;
+ };
+
+ public getUserId = (): string => {
+ let userIdCookieName: string = this.cookiePrefix+this.cookie.userIdSuffix;
+ let userId: string = this.getCookieByName(userIdCookieName);
+ return userId;
+ };
+
+ public getFirstName = (): string => {
+ let firstNameCookieName: string = this.cookiePrefix+this.cookie.userFirstName;
+ let firstName: string = this.getCookieByName(firstNameCookieName);
+ return firstName;
+ };
+
+ public getLastName = (): string => {
+ let lastNameCookieName: string = this.cookiePrefix+this.cookie.userLastName;
+ let lastName: string = this.getCookieByName(lastNameCookieName);
+ return lastName;
+ };
+
+ public getEmail = (): string => {
+ let emailCookieName: string = this.cookiePrefix+this.cookie.userEmail;
+ let email: string = this.getCookieByName(emailCookieName);
+ return email;
+ };
+
+ }
+}
diff --git a/catalog-ui/app/scripts/services/data-types-service.ts b/catalog-ui/app/scripts/services/data-types-service.ts
new file mode 100644
index 0000000000..0e5fca8b5d
--- /dev/null
+++ b/catalog-ui/app/scripts/services/data-types-service.ts
@@ -0,0 +1,129 @@
+/*-
+ * ============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.Services {
+
+ 'use strict';
+
+ export interface IDataTypesService {
+ //declare methods
+ getAllDataTypes():ng.IPromise<Models.PropertyModel>;
+ getFirsLevelOfDataTypeProperties(dataTypeName:string, dataTypes:Models.DataTypesMap):Array<Models.DataTypePropertyModel>;
+ isDataTypeForSchemaType(property:Models.SchemaProperty, types:Models.DataTypesMap):boolean;
+ isDataTypeForPropertyType(property:Models.PropertyModel, types:Models.DataTypesMap):boolean;
+ isDataTypeForDataTypePropertyType(property:Models.DataTypePropertyModel, types:Models.DataTypesMap):boolean;
+ }
+
+ export class DataTypesService implements IDataTypesService {
+
+ static '$inject' = [
+ 'sdcConfig',
+ '$q',
+ '$http'
+ ];
+
+ constructor(private sdcConfig:Models.IAppConfigurtaion,
+ private $q:ng.IQService,
+ private $http:ng.IHttpService) {
+ }
+
+ //if the dt derived from simple- return the first parent type, else- return null
+ private getTypeForDataTypeDerivedFromSimple = (dataTypeName:string, dataTypes:Models.DataTypesMap):string => {
+ /////////temporary hack for tosca primitives///////////////////////
+ if(!dataTypes[dataTypeName]){
+ return 'string';
+ }
+ ///////////////////////////////////////////////////////////////////
+ if(dataTypes[dataTypeName].derivedFromName == "tosca.datatypes.Root" || dataTypes[dataTypeName].properties){
+ return null;
+ }
+ if(Utils.Constants.PROPERTY_DATA.SIMPLE_TYPES.indexOf(dataTypes[dataTypeName].derivedFromName) > -1 ){
+ return dataTypes[dataTypeName].derivedFromName
+ }
+ return this.getTypeForDataTypeDerivedFromSimple(dataTypes[dataTypeName].derivedFromName,dataTypes);
+ };
+
+ public getAllDataTypes = ():ng.IPromise<Models.PropertyModel> => {
+ let deferred = this.$q.defer();
+ this.$http({
+ url: this.sdcConfig.api.root + this.sdcConfig.api.component_api_root + "dataTypes",
+ method: "get"
+ })
+ .success((response:any) => {
+ deferred.resolve(response);
+ })
+ .error((err) => {
+ deferred.reject(err);
+ });
+ return deferred.promise;
+ };
+
+ //return list of data type properties and all its parents properties
+ //(not include the properties of its properties, in case this data type has not primitive properties)
+ public getFirsLevelOfDataTypeProperties = (dataTypeName:string, dataTypes:Models.DataTypesMap):Array<Models.DataTypePropertyModel> => {
+ let properties = dataTypes[dataTypeName].properties || [];
+ if(dataTypes[dataTypeName].derivedFromName != "tosca.datatypes.Root" ){
+ properties = this.getFirsLevelOfDataTypeProperties(dataTypes[dataTypeName].derivedFromName,dataTypes).concat(properties);
+ }
+ return properties;
+ };
+
+ //return false when type= data type (=not simple type) that not derived from simple type
+ public isDataTypeForSchemaType = (property:Models.SchemaProperty, types:Models.DataTypesMap):boolean=>{
+ property.simpleType="";
+ if(property.type && Utils.Constants.PROPERTY_DATA.TYPES.indexOf(property.type) > -1){
+ return false;
+ }
+ let simpleType = this.getTypeForDataTypeDerivedFromSimple(property.type, types);
+ if(simpleType){
+ property.simpleType=simpleType;
+ return false;
+ }
+ return true;
+ };
+
+ public isDataTypeForPropertyType = (property:Models.PropertyModel, types:Models.DataTypesMap):boolean=>{
+ property.simpleType="";
+ if(property.type && Utils.Constants.PROPERTY_DATA.TYPES.indexOf(property.type) > -1){
+ return false;
+ }
+ let simpleType = this.getTypeForDataTypeDerivedFromSimple(property.type, types);
+ if(simpleType){
+ property.simpleType=simpleType;
+ return false;
+ }
+ return true;
+ };
+
+ public isDataTypeForDataTypePropertyType = (property:Models.DataTypePropertyModel, types:Models.DataTypesMap):boolean=>{
+ property.simpleType="";
+ if(property.type && Utils.Constants.PROPERTY_DATA.TYPES.indexOf(property.type) > -1){
+ return false;
+ }
+ let simpleType = this.getTypeForDataTypeDerivedFromSimple(property.type, types);
+ if(simpleType){
+ property.simpleType=simpleType;
+ return false;
+ }
+ return true;
+ };
+
+ }
+}
diff --git a/catalog-ui/app/scripts/services/ecomp-service.ts b/catalog-ui/app/scripts/services/ecomp-service.ts
new file mode 100644
index 0000000000..d7910bd612
--- /dev/null
+++ b/catalog-ui/app/scripts/services/ecomp-service.ts
@@ -0,0 +1,54 @@
+/*-
+ * ============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.Services {
+
+ 'use strict';
+
+ interface IEcompHeaderService {
+ getMenuItems(userId): ng.IPromise<Array<any>>;
+ }
+
+ export class EcompHeaderService implements IEcompHeaderService {
+ static '$inject' = ['$http', '$q', 'sdcConfig'];
+ private api:Models.IApi;
+
+ constructor(private $http:ng.IHttpService,
+ private $q:ng.IQService,
+ private sdcConfig:Models.IAppConfigurtaion) {
+ this.api = sdcConfig.api;
+ }
+
+ getMenuItems = (userId):ng.IPromise<Array<any>> => {
+ let defer = this.$q.defer<Array<any>>();
+ //defer.resolve(this.mockData);
+ this.$http.get(this.api.root + this.api.GET_ecomp_menu_items.replace(':userId', userId))
+ .success((response:any) => {
+ defer.resolve(response);
+ })
+ .error((response) => {
+ defer.reject(response);
+ });
+
+ return defer.promise;
+ };
+
+ }
+}
diff --git a/catalog-ui/app/scripts/services/entity-service.ts b/catalog-ui/app/scripts/services/entity-service.ts
new file mode 100644
index 0000000000..a9d5a421ce
--- /dev/null
+++ b/catalog-ui/app/scripts/services/entity-service.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.Services {
+
+ 'use strict';
+
+ interface IEntityService {
+ getAllComponents(): ng.IPromise<Array<Models.Components.Component>>;
+ }
+
+ interface IComponentsArray {
+ services:Array<Models.Components.Service>;
+ resources:Array<Models.Components.Resource>;
+ products:Array<Models.Components.Product>;
+ }
+
+ export class EntityService implements IEntityService {
+ static '$inject' = ['$http', '$q', 'sdcConfig', 'Sdc.Services.SharingService','ComponentFactory','Sdc.Services.CacheService'];
+ private api:Models.IApi;
+
+ constructor(private $http:ng.IHttpService,
+ private $q:ng.IQService,
+ private sdcConfig:Models.IAppConfigurtaion,
+ private sharingService:Sdc.Services.SharingService,
+ private ComponentFactory: Sdc.Utils.ComponentFactory,
+ private cacheService:Sdc.Services.CacheService
+ ) {
+ this.api = sdcConfig.api;
+ }
+
+ getCatalog = ():ng.IPromise<Array<Models.Components.Component>> => {
+ let defer = this.$q.defer<Array<Models.Components.Component>>();
+ this.$http.get(this.api.root + this.api.GET_catalog)
+ .success((followedResponse:IComponentsArray) => {
+
+ let componentsList:Array<Models.Components.Component> = new Array();
+
+ followedResponse.services.forEach((serviceResponse: Models.Components.Service) => {
+ let component:Models.Components.Service = this.ComponentFactory.createService(serviceResponse); // new Models.Components.Service(serviceResponse);
+ componentsList.push(component);
+ this.sharingService.addUuidValue(component.uniqueId, component.uuid);
+ });
+
+ followedResponse.resources.forEach((resourceResponse:Models.Components.Resource) => {
+ let component:Models.Components.Resource = this.ComponentFactory.createResource(resourceResponse);
+ componentsList.push(component);
+ this.sharingService.addUuidValue(component.uniqueId, component.uuid);
+ });
+
+ followedResponse.products.forEach((productResponse:Models.Components.Product) => {
+
+ let component:Models.Components.Product = this.ComponentFactory.createProduct(productResponse);
+ componentsList.push(component);
+ this.sharingService.addUuidValue(component.uniqueId, component.uuid);
+ });
+
+ this.cacheService.set('breadcrumbsComponents',componentsList);
+ defer.resolve(componentsList);
+ })
+ .error((responce) => {
+ defer.reject(responce);
+ });
+ return defer.promise;
+ };
+
+ getAllComponents = ():ng.IPromise<Array<Models.Components.Component>> => {
+ let defer = this.$q.defer<Array<Models.Components.Component>>();
+ this.$http.get(this.api.root + this.api.GET_element)
+ .success((componentResponse:IComponentsArray) => {
+ let componentsList:Array<Models.Components.Component> = [];
+
+ componentResponse.services && componentResponse.services.forEach((serviceResponse:Models.Components.Service) => {
+ let component:Models.Components.Service = this.ComponentFactory.createService(serviceResponse);
+ componentsList.push(component);
+ this.sharingService.addUuidValue(component.uniqueId, component.uuid);
+ });
+
+ componentResponse.resources && componentResponse.resources.forEach((resourceResponse:Models.Components.Resource) => {
+ let component:Models.Components.Resource = this.ComponentFactory.createResource(resourceResponse);
+ componentsList.push(component);
+ this.sharingService.addUuidValue(component.uniqueId, component.uuid);
+ });
+
+ componentResponse.products && componentResponse.products.forEach((productsResponse:Models.Components.Product) => {
+ let component:Models.Components.Product = this.ComponentFactory.createProduct(productsResponse);
+ componentsList.push(component);
+ this.sharingService.addUuidValue(component.uniqueId, component.uuid);
+ });
+ this.cacheService.set('breadcrumbsComponents',componentsList);
+ defer.resolve(componentsList);
+ });
+
+ return defer.promise;
+ };
+ }
+}
diff --git a/catalog-ui/app/scripts/services/event-listener-service.ts b/catalog-ui/app/scripts/services/event-listener-service.ts
new file mode 100644
index 0000000000..67afd65c56
--- /dev/null
+++ b/catalog-ui/app/scripts/services/event-listener-service.ts
@@ -0,0 +1,78 @@
+/*-
+ * ============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/4/2016.
+ */
+/// <reference path="../references"/>
+module Sdc.Services {
+
+ 'use strict';
+
+ interface IEventListenerService {
+
+ }
+
+ interface ICallbackData {
+ callback:Function;
+ args:any[];
+ }
+
+ export class EventListenerService implements IEventListenerService {
+
+ public observerCallbacks:Utils.Dictionary<string, ICallbackData[]> = new Utils.Dictionary<string, Array<ICallbackData>>();
+
+ //register an observer + callback
+ public registerObserverCallback = (eventName:string, callback:Function, ...args) => {
+ let callbackData = {
+ callback: callback,
+ args: args
+ }
+
+ if (this.observerCallbacks.containsKey(eventName)) {
+ let callbacks = this.observerCallbacks.getValue(eventName);
+
+ // Only insert the callback if the callback is different from existing callbacks.
+ for (let i = 0; i < callbacks.length; i++) {
+ if (callbacks[i].toString() === callback.toString()) {
+ return; // Do not add this callback.
+ }
+ }
+
+ callbacks.push(callbackData);
+ this.observerCallbacks.setValue(eventName, callbacks);
+ } else {
+ this.observerCallbacks.setValue(eventName, [callbackData]);
+ }
+ };
+
+ //unregister an observer
+ public unRegisterObserver = (eventName:string) => {
+ if (this.observerCallbacks.containsKey(eventName)) {
+ this.observerCallbacks.remove(eventName);
+ }
+ };
+
+ public notifyObservers = function (eventName:string, ...args) {
+ _.forEach(this.observerCallbacks.getValue(eventName), (callbackData:ICallbackData) => {
+ callbackData.callback(...args);
+ });
+ };
+ }
+}
diff --git a/catalog-ui/app/scripts/services/header-interceptor.ts b/catalog-ui/app/scripts/services/header-interceptor.ts
new file mode 100644
index 0000000000..7f362d3f8c
--- /dev/null
+++ b/catalog-ui/app/scripts/services/header-interceptor.ts
@@ -0,0 +1,93 @@
+/*-
+ * ============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.Services {
+ 'use strict';
+
+ //Method name should be exactly "response" - http://docs.angularjs.org/api/ng/service/$http
+ export interface IInterceptor {
+ request: Function;
+
+ }
+
+ export class HeaderInterceptor implements IInterceptor {
+ public static $inject = [
+ '$log',
+ '$injector',
+ '$q',
+ 'uuid4',
+ 'Sdc.Services.SharingService',
+ 'sdcConfig',
+ '$location'
+
+
+ ];
+
+ public static Factory($log: ng.ILogService,
+ $injector: ng.auto.IInjectorService,
+ $q: ng.IQService,
+ uuid4: any,
+ sharingService: Sdc.Services.SharingService,
+ sdcConfig: Models.IAppConfigurtaion,
+ $location: ng.ILocationService) {
+ return new HeaderInterceptor($log, $injector, $q, uuid4, sharingService, sdcConfig, $location);
+ }
+
+ constructor(private $log: ng.ILogService,
+ private $injector: ng.auto.IInjectorService,
+ private $q: ng.IQService,
+ private uuid4: any,
+ private sharingService: Sdc.Services.SharingService,
+ private sdcConfig: Models.IAppConfigurtaion,
+ private $location: ng.ILocationService) {
+ this.$log.debug('initializing AuthenticationInterceptor');
+ }
+
+ public request = (requestSuccess): ng.IPromise<any> => {
+ requestSuccess.headers['X-ECOMP-RequestID'] = this.uuid4.generate();
+ /**
+ * For every request to the server, that the service id, or resource id is sent in the URL, need to pass UUID in the header.
+ * Check if the unique id exists in uuidMap, and if so get the UUID and add it to the header.
+ */
+ let map: Utils.Dictionary<string, string> = this.sharingService.getUuidMap();
+ if (map && requestSuccess.url.indexOf(this.sdcConfig.api.root) === 0) {
+ this.$log.debug("url: " + requestSuccess.url);
+ map.forEach((key: string) => {
+ if (requestSuccess.url.indexOf(key) !== -1) {
+ requestSuccess.headers['X-ECOMP-ServiceID'] = this.sharingService.getUuidValue(key);
+ }
+ });
+ }
+ return requestSuccess;
+ };
+
+ public response = (responseSuccess): ng.IPromise<any> => {
+ let responseData = responseSuccess.data;
+ if (responseData) {
+ let data = JSON.stringify(responseData);
+ if (data && (data.indexOf("Global Logon: Login") > 0)) {
+ this.$location.path('dashboard/welcome');
+ window.location.reload();
+ }
+ }
+ return responseSuccess;
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/services/http-error-interceptor.ts b/catalog-ui/app/scripts/services/http-error-interceptor.ts
new file mode 100644
index 0000000000..c04659dfec
--- /dev/null
+++ b/catalog-ui/app/scripts/services/http-error-interceptor.ts
@@ -0,0 +1,118 @@
+/*-
+ * ============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.Services {
+ 'use strict';
+
+ export class HttpErrorInterceptor {
+ public static $inject = ['$injector', '$q'];
+
+ public static Factory($injector: ng.auto.IInjectorService, $q: angular.IQService) {
+ return new HttpErrorInterceptor($injector, $q);
+ }
+
+ constructor(private $injector: ng.auto.IInjectorService, private $q: angular.IQService) {
+ }
+
+ public formatMessageArrays = (message: string, variables: Array<string>)=> {
+ return message.replace(/\[%(\d+)\]/g, function (_, m) {
+ let tmp = [];
+ let list = variables[--m].split(";");
+ list.forEach(function (item) {
+ tmp.push("<li>" + item + "</li>");
+ });
+ return "<ul>" + tmp.join("") + "</ul>";
+ });
+ };
+
+ public responseError = (rejection: any)=> {
+
+ let text: string;
+ let variables;
+ let messageId: string = "";
+ let isKnownException = false;
+
+ if (rejection.data && rejection.data.serviceException) {
+ text = rejection.data.serviceException.text;
+ variables = rejection.data.serviceException.variables;
+ messageId = rejection.data.serviceException.messageId;
+ isKnownException = true;
+ } else if (rejection.data && rejection.data.requestError && rejection.data.requestError.serviceException) {
+ text = rejection.data.requestError.serviceException.text;
+ variables = rejection.data.requestError.serviceException.variables;
+ messageId = rejection.data.requestError.serviceException.messageId;
+ isKnownException = true;
+ } else if (rejection.data && rejection.data.requestError && rejection.data.requestError.policyException) {
+ text = rejection.data.requestError.policyException.text;
+ variables = rejection.data.requestError.policyException.variables;
+ messageId = rejection.data.requestError.policyException.messageId;
+ isKnownException = true;
+ } else if (rejection.data) {
+ text = 'Wrong error format from server';
+ console.error(text);
+ isKnownException = false;
+ }
+
+ let data: Sdc.ViewModels.IServerMessageModalModel;
+ if (isKnownException) {
+ // Remove the "Error: " text at the begining
+ if (text.trim().indexOf("Error:") === 0) {
+ text = text.replace("Error:", "").trim();
+ }
+
+ //mshitrit DE199895 bug fix
+ let count: number = 0;
+ variables.forEach(function (item) {
+ variables[count] = item ? item.replace('<', '&lt').replace('>', '&gt') : '';
+ count++;
+ });
+
+ // Format the message in case has array to <ul><li>
+ text = this.formatMessageArrays(text, variables);
+
+ // Format the message %1 %2
+ text = text.format(variables);
+
+ // Need to inject the MessageService manually to prevent circular dependencies (because MessageService use $templateCache that use $http).
+ data = {
+ title: 'Error',
+ message: text,
+ messageId: messageId,
+ status: rejection.status,
+ severity: Utils.Constants.SEVERITY.ERROR
+ };
+ } else {
+ // Need to inject the MessageService manually to prevent circular dependencies (because MessageService use $templateCache that use $http).
+ data = {
+ title: 'Error',
+ message: rejection.status !== -1 ? rejection.statusText : "Error getting response from server",
+ messageId: messageId,
+ status: rejection.status,
+ severity: Utils.Constants.SEVERITY.ERROR
+ };
+ }
+
+ let modalsHandler = this.$injector.get('ModalsHandler');
+ modalsHandler.openServerMessageModal(data);
+
+ return this.$q.reject(rejection);
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/services/loader-service.ts b/catalog-ui/app/scripts/services/loader-service.ts
new file mode 100644
index 0000000000..6a1a1febe1
--- /dev/null
+++ b/catalog-ui/app/scripts/services/loader-service.ts
@@ -0,0 +1,26 @@
+/**
+ * Created by obarda on 3/13/2016.
+ */
+/// <reference path="../references"/>
+module Sdc.Services {
+ 'use strict';
+
+ export class LoaderService {
+
+
+ constructor(private eventListenerService: Services.EventListenerService) {
+
+ }
+
+ public showLoader(...args) {
+ this.eventListenerService.notifyObservers(Utils.Constants.EVENTS.SHOW_LOADER_EVENT, ...args);
+ }
+
+ public hideLoader(...args) {
+ this.eventListenerService.notifyObservers(Utils.Constants.EVENTS.HIDE_LOADER_EVENT, ...args);
+ }
+
+ }
+
+ LoaderService.$inject = ['EventListenerService'];
+}
diff --git a/catalog-ui/app/scripts/services/onboarding-service.ts b/catalog-ui/app/scripts/services/onboarding-service.ts
new file mode 100644
index 0000000000..c09871d67f
--- /dev/null
+++ b/catalog-ui/app/scripts/services/onboarding-service.ts
@@ -0,0 +1,103 @@
+/*-
+ * ============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.Services {
+
+ 'use strict';
+
+ interface IOnboardingService {
+ getOnboardingComponents(): ng.IPromise<Array<Models.Components.IComponent>>;
+ getComponentFromCsarUuid(csarUuid:string): ng.IPromise<Models.Components.Component>;
+ downloadOnboardingCsar(packageId:string):ng.IPromise<Models.IFileDownload>;
+ }
+
+ export class OnboardingService implements IOnboardingService {
+
+ static '$inject' = ['$http', '$q', 'sdcConfig', 'ComponentFactory'];
+ private api:Models.IApi;
+
+ constructor(private $http:ng.IHttpService,
+ private $q:ng.IQService,
+ private sdcConfig:Models.IAppConfigurtaion,
+ private ComponentFactory: Sdc.Utils.ComponentFactory
+ ) {
+ this.api = sdcConfig.api;
+ }
+
+ getOnboardingComponents = ():ng.IPromise<Array<Models.Components.IComponent>> => {
+ let defer = this.$q.defer<Array<Models.Components.IComponent>>();
+ this.$http.get(this.api.GET_onboarding)
+ .success((response:any) => {
+ let onboardingComponents:Array<Models.ICsarComponent> = response.results;
+ let componentsList:Array<Models.Components.IComponent> = new Array();
+
+ onboardingComponents.forEach((obc: Models.ICsarComponent) => {
+ let component:Models.Components.Component = this.ComponentFactory.createFromCsarComponent(obc);
+ componentsList.push(component);
+ });
+
+ defer.resolve(componentsList);
+ })
+ .error((response) => {
+ defer.reject(response);
+ });
+
+ return defer.promise;
+ };
+
+ downloadOnboardingCsar = (packageId:string):ng.IPromise<Models.IFileDownload> => {
+ let defer = this.$q.defer();
+ this.$http({
+ url: this.api.GET_onboarding + "/" + packageId,
+ method: "get",
+ responseType: "blob"
+ })
+ .success((response:any) => {
+ defer.resolve(response);
+ })
+ .error((err) => {
+ defer.reject(err);
+ });
+
+ return defer.promise;
+ };
+
+ getComponentFromCsarUuid = (csarUuid:string):ng.IPromise<Models.Components.Component> => {
+ let defer = this.$q.defer<Models.Components.Component>();
+ this.$http.get(this.api.root + this.api.GET_component_from_csar_uuid.replace(':csar_uuid', csarUuid))
+ .success((response:any) => {
+ let component:Models.Components.Resource;
+ // If the status is 400, this means that the component not found.
+ // I do not want to return error from server, because a popup will appear in client with the error.
+ // So returning success (200) with status 400.
+ if (response.status!==400) {
+ component = new Models.Components.Resource(null, this.$q, <Models.Components.Resource>response);
+ }
+ defer.resolve(component);
+ })
+ .error((response) => {
+ defer.reject(response);
+ });
+
+ return defer.promise;
+ };
+
+ }
+}
diff --git a/catalog-ui/app/scripts/services/progress-service.ts b/catalog-ui/app/scripts/services/progress-service.ts
new file mode 100644
index 0000000000..caa463fc98
--- /dev/null
+++ b/catalog-ui/app/scripts/services/progress-service.ts
@@ -0,0 +1,112 @@
+/*-
+ * ============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/7/2016.
+ */
+/**
+ * Created by obarda on 7/4/2016.
+ */
+/// <reference path="../references"/>
+module Sdc.Services {
+
+ 'use strict';
+ import IIntervalService = angular.IIntervalService;
+
+ export class ProgressService {
+
+ public progresses:any = {};
+
+ static '$inject' = ['$interval'];
+
+ constructor(
+ protected $interval:any
+ ) {}
+
+ private totalProgress:number = 90;
+ private startProgress:number = 10;
+ private onePercentIntervalSeconds:number = 5;
+ private createComponentInterval;
+
+ public setProgressValue(name:string, value:number):void {
+ if (!this.progresses[name]) {
+ this.progresses[name]={};
+ }
+ this.progresses[name].value = value;
+ }
+
+ public getProgressValue(name:string):number{
+ if (this.progresses[name]){
+ return this.progresses[name].value;
+ }
+ return 0;
+ }
+
+ public deleteProgressValue(name:string):void{
+ this.stopCreateComponentInterval();
+ delete this.progresses[name];
+ }
+
+
+ private stopCreateComponentInterval = ():void => {
+ this.$interval.cancel(this.createComponentInterval);
+ };
+
+
+
+ public initCreateComponentProgress = (componentId:string):void => {
+ var progressValue:number = this.startProgress;
+ if(!this.getProgressValue(componentId)){
+ this.stopCreateComponentInterval();
+ this.setProgressValue(componentId, this.startProgress);
+ this.createComponentInterval = this.$interval(():void => {
+ //TODO replace getProgressMockData to real data after BE provide the API
+ var progressValue = this.getProgressMockData(componentId);
+ if (progressValue<=this.totalProgress ) {
+ this.setProgressValue(componentId, progressValue);
+ } else {
+ /**
+ * Currently the progress is not really checking against the BE.
+ * So the progress can pass 100. So the workaround for now, in case we pass 90 (totalProgress)
+ * stop the interval, so the progress will be kept at 90 until the promise will return value and set
+ * the progress to 100.
+ */
+ this.deleteProgressValue(componentId);
+ }
+ }, this.onePercentIntervalSeconds*1000);
+ }
+
+ };
+
+
+ private getProgressMockData =(id:string):number =>{
+ var progressValue = this.getProgressValue(id);
+ if(progressValue>0){
+ progressValue = progressValue + 1;
+ }
+ //if not finish always stay on 90%
+ if (progressValue>90){
+ progressValue =90;
+ }
+
+ return progressValue;
+ }
+
+ }
+}
diff --git a/catalog-ui/app/scripts/services/relation-icons-service.ts b/catalog-ui/app/scripts/services/relation-icons-service.ts
new file mode 100644
index 0000000000..3d3b494f16
--- /dev/null
+++ b/catalog-ui/app/scripts/services/relation-icons-service.ts
@@ -0,0 +1,61 @@
+/*-
+ * ============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.Services {
+ 'use strict';
+
+ export interface IRelationIconsService {
+ getIconPath(iconName:string):string;
+ }
+
+ export class RelationIconsService implements IRelationIconsService {
+ constructor() {}
+ private icons: Array<string> = [
+ 'AttachesTo',
+ 'BindsTo',
+ 'DependsOn',
+ 'HostedOn',
+ 'LinksTo',
+ 'RoutesTo'
+ ];
+
+ public getIconPath = (relationshipType:string): string => {
+ let result:string = 'ConnectedTo';
+ let baseUrl:string = '/styles/images/relationship-icons/';
+
+ if (relationshipType) {
+ let arr = relationshipType.split('.'); // looks like tosca.relationships.AttachesTo
+ relationshipType = arr[arr.length - 1];
+ if (this.icons.indexOf(relationshipType) > -1) {
+ result = relationshipType;
+ }
+ if('LinksTo'==result){
+ return '';
+ }
+ }
+
+ return baseUrl + result + '.svg';
+ }
+
+ }
+}
+
+
+
diff --git a/catalog-ui/app/scripts/services/sdc-version-service.ts b/catalog-ui/app/scripts/services/sdc-version-service.ts
new file mode 100644
index 0000000000..1744381bca
--- /dev/null
+++ b/catalog-ui/app/scripts/services/sdc-version-service.ts
@@ -0,0 +1,48 @@
+/*-
+ * ============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.Services {
+ 'use strict';
+
+ export interface ISdcVersionService {
+ getVersion():ng.IPromise<any>;
+ }
+ export class SdcVersionService implements ISdcVersionService{
+
+ static '$inject' = ['$http', '$q','sdcConfig'];
+ private api: Models.IApi;
+
+ constructor(private $http: ng.IHttpService, private $q: ng.IQService, sdcConfig: Models.IAppConfigurtaion){
+ this.api = sdcConfig.api;
+ }
+
+ public getVersion():ng.IPromise<any>{
+ let defer = this.$q.defer<Array<Models.Distribution>>();
+ let url = this.api.root + this.api.GET_SDC_Version;
+ console.log("======================>" + url);
+ this.$http.get(url)
+ .success((version: any) => {
+ defer.resolve(version);
+ });
+ return defer.promise;
+ }
+ }
+}
+
diff --git a/catalog-ui/app/scripts/services/sharing-service.ts b/catalog-ui/app/scripts/services/sharing-service.ts
new file mode 100644
index 0000000000..14c3158611
--- /dev/null
+++ b/catalog-ui/app/scripts/services/sharing-service.ts
@@ -0,0 +1,41 @@
+/*-
+ * ============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.Services {
+ 'use strict';
+
+ export class SharingService {
+
+ private uuidMap: Utils.Dictionary<string, string> = new Utils.Dictionary<string,string>();
+
+ public getUuidValue = (uniqueId:string):string => {
+ return this.uuidMap.getValue(uniqueId);
+ };
+
+ public addUuidValue = (uniqueId:string, uuid:string):void => {
+ this.uuidMap.setValue(uniqueId, uuid);
+ };
+
+ public getUuidMap = ():Utils.Dictionary<string, string> => {
+ return this.uuidMap;
+ };
+
+ }
+}
diff --git a/catalog-ui/app/scripts/services/url-tobase64-service.ts b/catalog-ui/app/scripts/services/url-tobase64-service.ts
new file mode 100644
index 0000000000..2d6980da3f
--- /dev/null
+++ b/catalog-ui/app/scripts/services/url-tobase64-service.ts
@@ -0,0 +1,52 @@
+/*-
+ * ============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.Services {
+ 'use strict';
+
+ export interface IUrlToBase64Service {
+ downloadUrl(url:string, callback:Function):void;
+ }
+
+ export class UrlToBase64Service implements IUrlToBase64Service {
+ constructor() {}
+
+ public downloadUrl = (url:string, callback:Function): void => {
+ let xhr :any = new XMLHttpRequest();
+
+ xhr.onload = ():void => {
+ let reader = new FileReader();
+ reader.onloadend = ():void => {
+ if (xhr.status === 200) {
+ callback(reader.result);
+ } else {
+ callback(null);
+ }
+ };
+ reader.readAsDataURL(xhr.response);
+ };
+ xhr.open('GET', url);
+ xhr.responseType = 'blob';
+ xhr.send();
+ }
+
+ }
+}
+
diff --git a/catalog-ui/app/scripts/services/user-resource-service.ts b/catalog-ui/app/scripts/services/user-resource-service.ts
new file mode 100644
index 0000000000..7414e2221e
--- /dev/null
+++ b/catalog-ui/app/scripts/services/user-resource-service.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.Services {
+ 'use strict';
+
+ // Define an interface of the object you want to use, providing it's properties
+ export interface IUserResource extends Models.IUserProperties,ng.resource.IResource<IUserResource>{
+
+ }
+
+ // Define your resource, adding the signature of the custom actions
+ export interface IUserResourceClass extends ng.resource.IResourceClass<IUserResource>{
+ authorize(): IUserResource;
+ getLoggedinUser(): IUserResource;
+ setLoggedinUser(user: IUserResource): void;
+ getAllUsers(success?: Function, error?: Function): Array<IUserResource>;
+ createUser(IResourceResource, success?: Function, error?: Function): void;
+ editUserRole(IResourceResource, success?: Function, error?: Function): void;
+ deleteUser(IResourceResource, success?: Function, error?: Function): void;
+ }
+
+ export class UserResourceService{
+
+ public static getResource = (
+ $resource: ng.resource.IResourceService,
+ sdcConfig: Models.IAppConfigurtaion,
+ cookieService: Services.CookieService
+ ): IUserResourceClass => {
+
+ let url: string = sdcConfig.api.root+sdcConfig.api.GET_user;
+ let authorizeUrl: string = sdcConfig.api.root+sdcConfig.api.GET_user_authorize;
+ let authorizeActionHeaders: any = {};
+ let cookie: Models.ICookie = sdcConfig.cookie;
+ authorizeActionHeaders[cookie.userFirstName] = cookieService.getFirstName();
+ authorizeActionHeaders[cookie.userLastName] = cookieService.getLastName();
+ authorizeActionHeaders[cookie.userEmail] = cookieService.getEmail();
+ authorizeActionHeaders[cookie.userIdSuffix] = cookieService.getUserId();
+
+ // Define your custom actions here as IActionDescriptor
+ let authorizeAction : ng.resource.IActionDescriptor = {
+ method: 'GET',
+ isArray: false,
+ url: authorizeUrl,
+ headers: authorizeActionHeaders
+ };
+
+ let getAllUsers : ng.resource.IActionDescriptor = {
+ method: 'GET',
+ isArray: true,
+ url: sdcConfig.api.root + sdcConfig.api.GET_all_users
+ };
+
+ let editUserRole : ng.resource.IActionDescriptor = {
+ method: 'POST',
+ isArray: false,
+ url: sdcConfig.api.root + sdcConfig.api.POST_edit_user_role,
+ transformRequest: (data, headers)=>{
+ data.payloadData = undefined;
+ data.payloadName = undefined;
+ return JSON.stringify(data);
+ }
+ };
+
+ let deleteUser : ng.resource.IActionDescriptor = {
+ method: 'DELETE',
+ isArray: false,
+ url: sdcConfig.api.root + sdcConfig.api.DELETE_delete_user
+ };
+
+ let createUser : ng.resource.IActionDescriptor = {
+ method: 'POST',
+ isArray: false,
+ url: sdcConfig.api.root + sdcConfig.api.POST_create_user,
+ transformRequest: (data, headers)=>{
+ data.payloadData = undefined;
+ data.payloadName = undefined;
+ return JSON.stringify(data);
+ }
+ };
+ let userResource: IUserResourceClass = <IUserResourceClass>$resource(
+ url,
+ { id: '@id'},
+ {
+ authorize: authorizeAction,
+ getAllUsers: getAllUsers,
+ createUser: createUser,
+ editUserRole:editUserRole,
+ deleteUser:deleteUser}
+ );
+
+ let _loggedinUser: IUserResource;
+
+ userResource.getLoggedinUser = () => {
+ return _loggedinUser;
+ };
+
+ userResource.setLoggedinUser = (loggedinUser: IUserResource) => {
+ _loggedinUser = loggedinUser;
+ };
+
+ return userResource;
+ }
+ }
+ UserResourceService.getResource.$inject = ['$resource', 'sdcConfig', 'Sdc.Services.CookieService'];
+}
diff --git a/catalog-ui/app/scripts/utils/artifacts-utils.ts b/catalog-ui/app/scripts/utils/artifacts-utils.ts
new file mode 100644
index 0000000000..439a0e98d0
--- /dev/null
+++ b/catalog-ui/app/scripts/utils/artifacts-utils.ts
@@ -0,0 +1,121 @@
+/*-
+ * ============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.Utils {
+ export class ArtifactsUtils {
+
+ static '$inject' = [
+ '$filter',
+ '$templateCache',
+ '$modal'
+ ];
+
+ constructor(private $filter:ng.IFilterService,
+ private $templateCache:ng.ITemplateCacheService,
+ private $modal:ng.ui.bootstrap.IModalService) {
+
+ }
+
+ public getArtifactTypeByState(currentState:string):string {
+ switch (currentState) {
+ case "workspace.composition.lifecycle":
+ return "interface";
+ case "workspace.composition.api":
+ return "api";
+ case "workspace.composition.deployment":
+ return "deployment";
+ default:
+ return "normal";
+ }
+ }
+
+ public getTitle(artifactType:string, selectedComponent:Models.Components.Component):string {
+ switch (artifactType) {
+ case "interface":
+ return "Lifecycle Management";
+ case "api":
+ return "API Artifacts";
+ case "deployment":
+ return "Deployment Artifacts";
+ default:
+ if (!selectedComponent) {
+ return "";
+ } else {
+ return this.$filter("resourceName")(selectedComponent.name) + ' Artifacts';
+ }
+ }
+ }
+
+ public setArtifactType = (artifact:Models.ArtifactModel, artifactType:string):void => {
+ switch (artifactType) {
+ case "api":
+ artifact.artifactGroupType = 'SERVICE_API';
+ break;
+ case "deployment":
+ artifact.artifactGroupType = 'DEPLOYMENT';
+ break;
+ default:
+ artifact.artifactGroupType = 'INFORMATIONAL';
+ break;
+ }
+ };
+
+ public isLicenseType = (artifactType:string) :boolean => {
+ let isLicense:boolean = false;
+
+ if(Utils.Constants.ArtifactType.VENDOR_LICENSE === artifactType || Utils.Constants.ArtifactType.VF_LICENSE === artifactType) {
+ isLicense = true;
+ }
+
+ return isLicense;
+ };
+
+ public removeArtifact = (artifact:Models.ArtifactModel, artifactsArr:Array<Models.ArtifactModel>):void => {
+
+ if (!artifact.mandatory && (Utils.Constants.ArtifactGroupType.INFORMATION == artifact.artifactGroupType ||
+ Utils.Constants.ArtifactGroupType.DEPLOYMENT == artifact.artifactGroupType)) {
+ _.remove(artifactsArr, {uniqueId: artifact.uniqueId});
+ }
+ else {
+ let artifactToDelete = _.find(artifactsArr, {uniqueId: artifact.uniqueId});
+
+ delete artifactToDelete.esId;
+ delete artifactToDelete.description;
+ delete artifactToDelete.artifactName;
+ delete artifactToDelete.apiUrl;
+ }
+ };
+
+ public addAnotherAfterSave(scope:Sdc.ViewModels.IArtifactResourceFormViewModelScope) {
+ let newArtifact = new Models.ArtifactModel();
+ this.setArtifactType(newArtifact, scope.artifactType);
+ scope.editArtifactResourceModel.artifactResource = newArtifact;
+
+ scope.forms.editForm['description'].$setPristine();
+ if(scope.forms.editForm['artifactLabel']){
+ scope.forms.editForm['artifactLabel'].$setPristine();
+ }
+ if(scope.forms.editForm['type']){
+ scope.forms.editForm['type'].$setPristine();
+ }
+
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/utils/change-lifecycle-state-handler.ts b/catalog-ui/app/scripts/utils/change-lifecycle-state-handler.ts
new file mode 100644
index 0000000000..7722a899b9
--- /dev/null
+++ b/catalog-ui/app/scripts/utils/change-lifecycle-state-handler.ts
@@ -0,0 +1,151 @@
+/*-
+ * ============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 2/11/2016.
+ */
+/// <reference path="../references"/>
+module Sdc.Utils {
+
+ export class ChangeLifecycleStateHandler {
+
+ static '$inject' = [
+ 'sdcConfig',
+ 'sdcMenu',
+ 'ComponentFactory',
+ '$templateCache',
+ '$filter',
+ '$modal',
+ 'ModalsHandler'
+ ];
+
+ constructor(
+ private sdcConfig:Models.IAppConfigurtaion,
+ private sdcMenu:Models.IAppMenu,
+ private ComponentFactory: Sdc.Utils.ComponentFactory,
+ private $templateCache:ng.ITemplateCacheService,
+ private $filter:ng.IFilterService,
+ private $modal:ng.ui.bootstrap.IModalService,
+ private ModalsHandler: Utils.ModalsHandler
+
+ ) {
+
+ }
+
+ changeLifecycleState = (component:Models.Components.Component, data:any, scope:any, onSuccessCallback?: Function, onErrorCallback?: Function):void => {
+
+ let self = this;
+
+ let getContacts = (component:Models.Components.Component):string =>{
+ let testers = this.sdcConfig.testers;
+ let result:string = testers[component.componentType][component.categories[0].name]?
+ testers[component.componentType][component.categories[0].name]:
+ testers[component.componentType]['default'];
+ return result;
+ };
+
+ let onSuccess = (newComponent:Models.Components.Component):void => {
+ //scope.isLoading = false;
+ console.info(component.componentType.toLowerCase + ' change state ' , newComponent);
+ if(onSuccessCallback) {
+ onSuccessCallback(self.ComponentFactory.createComponent(newComponent));
+ }
+ };
+
+ let onError = (error):void => {
+ scope.isLoading = false;
+ console.info('Failed to changeLifecycleState to ', data.url);
+ if(onErrorCallback) {
+ onErrorCallback(error);
+ }
+ };
+
+ let comment:Models.AsdcComment = new Models.AsdcComment();
+ if (data.alertModal) {
+ // Show alert dialog if defined in menu.json
+ //-------------------------------------------------
+ let onOk = (confirmationText):void => {
+ comment.userRemarks = confirmationText;
+ scope.isLoading = true;
+ component.changeLifecycleState(data.url, comment).then(onSuccess, onError);
+ };
+
+ let onCancel = ():void => {
+ console.info('Cancel pressed');
+ scope.isLoading = false;
+ };
+
+ let modalTitle = this.sdcMenu.alertMessages[data.alertModal].title;
+ let modalMessage = this.sdcMenu.alertMessages[data.alertModal].message.format([component.componentType.toLowerCase()]);
+ this.ModalsHandler.openAlertModal(modalTitle, modalMessage).then(onOk, onCancel);
+ } else if (data.confirmationModal) {
+ // Show confirmation dialog if defined in menu.json
+ //-------------------------------------------------
+ let onOk = (confirmationText):void => {
+ comment.userRemarks = confirmationText;
+ scope.isLoading = true;
+ component.changeLifecycleState(data.url, comment).then(onSuccess, onError);
+ };
+
+ let onCancel = ():void => {
+ console.info('Cancel pressed');
+ scope.isLoading = false;
+ };
+
+ let modalTitle = this.sdcMenu.confirmationMessages[data.confirmationModal].title;
+ let modalMessage = this.sdcMenu.confirmationMessages[data.confirmationModal].message.format([component.componentType.toLowerCase()]);
+ let modalShowComment = this.sdcMenu.confirmationMessages[data.confirmationModal].showComment;
+ this.ModalsHandler.openConfirmationModal(modalTitle, modalMessage, modalShowComment).then(onOk, onCancel);
+
+ } else if (data.emailModal) {
+ // Show email dialog if defined in menu.json
+ //-------------------------------------------------
+ let onOk = (resource):void => {
+ if (resource){
+ onSuccess(resource);
+ } else {
+ onError("Error changing life cycle state");
+ }
+ };
+
+ let onCancel = ():void => {
+ scope.isLoading = false;
+ };
+
+ let emailModel: ViewModels.IEmailModalModel = <ViewModels.IEmailModalModel>{};
+ emailModel.email = <ViewModels.IEmailModalModel_Email>{};
+ emailModel.data = <ViewModels.IEmailModalModel_Data>{};
+ emailModel.title = this.$filter('translate')("EMAIL_MODAL_TITLE");
+ emailModel.email.to = getContacts(component);
+ emailModel.email.subject = this.$filter('translate')("EMAIL_MODAL_SUBJECT", "{'entityName': '" + this.$filter('resourceName')(component.name) + "','entityVersion': '" + component.version + "'}");
+ emailModel.email.message = '';
+ emailModel.data.component = component;
+ emailModel.data.stateUrl = data.url;
+
+ this.ModalsHandler.openEmailModal(emailModel).then(onOk, onCancel);
+
+ } else {
+ // Submit to server only (no modal is shown).
+ scope.isLoading = true;
+ component.changeLifecycleState(data.url, comment).then(onSuccess, onError);
+ }
+
+ }
+ }
+}
diff --git a/catalog-ui/app/scripts/utils/common-utils.ts b/catalog-ui/app/scripts/utils/common-utils.ts
new file mode 100644
index 0000000000..aef6b9908d
--- /dev/null
+++ b/catalog-ui/app/scripts/utils/common-utils.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=========================================================
+ */
+/**
+ * Created by obarda on 6/30/2016.
+ */
+/// <reference path="../references.ts"/>
+module Sdc.Utils {
+
+ export class CommonUtils {
+
+ static initProperties(propertiesObj:Array<Sdc.Models.PropertyModel>, uniqueId?:string):Array<Sdc.Models.PropertyModel> {
+
+ let properties = new Array<Sdc.Models.PropertyModel>();
+ if (propertiesObj) {
+ _.forEach(propertiesObj, (property:Sdc.Models.PropertyModel):void => {
+ if (uniqueId) {
+ property.readonly = property.parentUniqueId != uniqueId;
+ }
+ properties.push(new Sdc.Models.PropertyModel(property));
+ });
+ }
+ return properties;
+ };
+
+ static initAttributes(attributesObj:Array<Sdc.Models.AttributeModel>, uniqueId?:string):Array<Sdc.Models.AttributeModel> {
+
+ let attributes = new Array<Sdc.Models.AttributeModel>();
+ if (attributesObj) {
+ _.forEach(attributesObj, (attribute:Sdc.Models.AttributeModel):void => {
+ if (uniqueId) {
+ attribute.readonly = attribute.parentUniqueId != uniqueId;
+ }
+ attributes.push(new Sdc.Models.AttributeModel(attribute));
+ });
+ }
+ return attributes;
+ };
+
+ static initComponentInstances(componentInstanceObj:Array<Models.ComponentsInstances.ResourceInstance>):Array<Models.ComponentsInstances.ResourceInstance> {
+
+ let componentInstances = new Array<Models.ComponentsInstances.ResourceInstance>();
+ if (componentInstanceObj) {
+ _.forEach(componentInstanceObj, (instance:Models.ComponentsInstances.ResourceInstance):void => {
+ componentInstances.push(Utils.ComponentInstanceFactory.createComponentInstance(instance));
+ });
+ }
+ return componentInstances;
+ };
+
+ static initModules(moduleArrayObj:Array<Models.Module>):Array<Models.Module> {
+
+ let modules = new Array<Models.Module>();
+
+ if (moduleArrayObj) {
+ _.forEach(moduleArrayObj, (module:Models.Module):void => {
+ if(module.type === "org.openecomp.groups.VfModule"){
+ modules.push(new Models.Module(module));
+ }
+ });
+ }
+ return modules;
+ };
+ }
+}
diff --git a/catalog-ui/app/scripts/utils/component-factory.ts b/catalog-ui/app/scripts/utils/component-factory.ts
new file mode 100644
index 0000000000..1bb139dc45
--- /dev/null
+++ b/catalog-ui/app/scripts/utils/component-factory.ts
@@ -0,0 +1,181 @@
+/*-
+ * ============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 2/8/2016.
+ */
+/// <reference path="../references"/>
+module Sdc.Utils {
+ 'use strict';
+ import Resource = Sdc.Models.Components.Resource;
+
+ export class ComponentFactory {
+
+ static '$inject' = [
+ 'Sdc.Services.Components.ResourceService',
+ 'Sdc.Services.Components.ServiceService',
+ 'Sdc.Services.Components.ProductService',
+ 'Sdc.Services.CacheService',
+ '$q'
+ ];
+
+ constructor(
+ private ResourceService:Services.Components.ResourceService,
+ private ServiceService:Services.Components.ServiceService,
+ private ProductService:Services.Components.ProductService,
+ private cacheService:Services.CacheService,
+ private $q: ng.IQService) {
+ }
+
+ public createComponent = (component:Models.Components.Component):Models.Components.Component => {
+ let newComponent:Models.Components.Component;
+ switch (component.componentType) {
+
+ case 'SERVICE':
+ newComponent = new Models.Components.Service(this.ServiceService, this.$q, <Models.Components.Service> component);
+ break;
+
+ case 'RESOURCE':
+ newComponent = new Models.Components.Resource(this.ResourceService, this.$q, <Models.Components.Resource> component);
+ break;
+
+ case 'PRODUCT':
+ newComponent = new Models.Components.Product(this.ProductService, this.$q, <Models.Components.Product> component);
+ break;
+ }
+ return newComponent;
+ };
+
+ public createProduct = (product:Models.Components.Product):Models.Components.Product => {
+ let newProduct:Models.Components.Product = new Models.Components.Product(this.ProductService, this.$q, <Models.Components.Product> product);
+ return newProduct;
+ };
+
+ public createService = (service:Models.Components.Service):Models.Components.Service => {
+ let newService:Models.Components.Service = new Models.Components.Service(this.ServiceService, this.$q, <Models.Components.Service> service);
+ return newService;
+ };
+
+ public createResource = (resource:Models.Components.Resource):Models.Components.Resource => {
+ let newResource:Models.Components.Resource = new Models.Components.Resource(this.ResourceService, this.$q, <Models.Components.Resource> resource);
+ return newResource;
+ };
+
+ public createFromCsarComponent = (csar:Models.ICsarComponent):Models.Components.Component => {
+ let newResource:Sdc.Models.Components.Resource = <Sdc.Models.Components.Resource>this.createEmptyComponent(Sdc.Utils.Constants.ComponentType.RESOURCE);
+ newResource.name = csar.vspName;
+
+ /**
+ * Onboarding CSAR contains category and sub category that are uniqueId.
+ * Need to find the category and sub category and extract the name from them.
+ * First concat all sub categories to one array.
+ * Then find the selected sub category and category.
+ * @type {any}
+ */
+ let availableCategories = angular.copy(this.cacheService.get('resourceCategories'));
+ let allSubs = [];
+ _.each(availableCategories, (main:Models.IMainCategory)=>{
+ if (main.subcategories) {
+ allSubs = allSubs.concat(main.subcategories);
+ }
+ });
+
+ let selectedCategory:Models.IMainCategory = _.find(availableCategories, function(main:Models.IMainCategory){
+ return main.uniqueId === csar.category;
+ });
+
+ let selectedSubCategory:Models.ISubCategory = _.find(allSubs,(sub:Models.ISubCategory)=>{
+ return sub.uniqueId === csar.subCategory;
+ });
+
+ // Build the categories and sub categories array (same format as component category)
+ let categories:Array<Models.IMainCategory> = new Array();
+ let subcategories:Array<Models.ISubCategory> = new Array();
+ if (selectedCategory && selectedSubCategory) {
+ subcategories.push(selectedSubCategory);
+ selectedCategory.subcategories = subcategories;
+ categories.push(selectedCategory);
+ }
+
+ // Fill the component with details from CSAR
+ newResource.selectedCategory = selectedCategory && selectedSubCategory ? selectedCategory.name + "_#_" + selectedSubCategory.name : '';
+ newResource.categories = categories;
+ newResource.vendorName = csar.vendorName;
+ newResource.vendorRelease = csar.vendorRelease;
+ newResource.csarUUID = csar.packageId;
+ newResource.csarPackageType = csar.packageType;
+ newResource.csarVersion = csar.version;
+ newResource.packageId = csar.packageId;
+ newResource.description = csar.description;
+ return newResource;
+ };
+
+ public createEmptyComponent = (componentType: string):Models.Components.Component => {
+ let newComponent:Models.Components.Component;
+
+ switch (componentType) {
+
+ case Utils.Constants.ComponentType.SERVICE:
+ newComponent = new Models.Components.Service(this.ServiceService, this.$q);
+ break;
+
+ case Utils.Constants.ComponentType.RESOURCE:
+ case Utils.Constants.ResourceType.VF:
+ case Utils.Constants.ResourceType.VL:
+ case Utils.Constants.ResourceType.VFC:
+ case Utils.Constants.ResourceType.CP:
+ newComponent = new Models.Components.Resource(this.ResourceService, this.$q);
+ break;
+
+ case Utils.Constants.ComponentType.PRODUCT:
+ newComponent = new Models.Components.Product(this.ProductService, this.$q);
+ break;
+ }
+ newComponent.componentType = componentType;
+ newComponent.tags = [];
+ newComponent.icon = Utils.Constants.DEFAULT_ICON;
+ return newComponent;
+ };
+
+
+ public getServiceFromServer = (componentId: string): ng.IPromise<Models.Components.Service> => {
+ let service: Models.Components.Service = <Models.Components.Service>this.createEmptyComponent(Utils.Constants.ComponentType.SERVICE);
+ service.setUniqueId(componentId);
+ return service.getComponent();
+ };
+
+ public getResourceFromServer = (componentId: string): ng.IPromise<Models.Components.Resource> => {
+ let resource: Models.Components.Resource = <Models.Components.Resource>this.createEmptyComponent(Utils.Constants.ComponentType.RESOURCE);
+ resource.setUniqueId(componentId);
+ return resource.getComponent();
+ };
+
+ public getComponentFromServer = (componentType: string, componentId: string): ng.IPromise<Models.Components.Component> => {
+ let newComponent: Models.Components.Component = this.createEmptyComponent(componentType);
+ newComponent.setUniqueId(componentId);
+ return newComponent.getComponent();
+ };
+
+ public createComponentOnServer = (componentObject:Models.Components.Component):ng.IPromise<Models.Components.Component> => {
+ let component: Models.Components.Component = this.createComponent(componentObject);
+ return component.createComponentOnServer();
+
+ };
+ }
+}
diff --git a/catalog-ui/app/scripts/utils/component-instance-factory.ts b/catalog-ui/app/scripts/utils/component-instance-factory.ts
new file mode 100644
index 0000000000..5f698aa46c
--- /dev/null
+++ b/catalog-ui/app/scripts/utils/component-instance-factory.ts
@@ -0,0 +1,85 @@
+/*-
+ * ============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/7/2016.
+ */
+/**
+ * Created by obarda on 2/8/2016.
+ */
+/// <reference path="../references"/>
+module Sdc.Utils {
+ 'use strict';
+
+ export class ComponentInstanceFactory {
+
+ static createComponentInstance(componentInstance:Models.ComponentsInstances.ComponentInstance):Models.ComponentsInstances.ComponentInstance {
+ let newComponentInstance:Models.ComponentsInstances.ComponentInstance;
+ switch (componentInstance.originType) {
+ case 'SERVICE':
+ newComponentInstance = new Models.ComponentsInstances.ServiceInstance(componentInstance);
+ break;
+
+ case 'PRODUCT':
+ newComponentInstance = new Models.ComponentsInstances.ProductInstance(componentInstance);
+ break;
+
+ default :
+ newComponentInstance = new Models.ComponentsInstances.ResourceInstance(componentInstance);
+ break;
+ }
+ return newComponentInstance;
+ };
+
+ public createEmptyComponentInstance = (componentInstanceType?: string):Models.ComponentsInstances.ComponentInstance => {
+ let newComponentInstance:Models.ComponentsInstances.ComponentInstance;
+ switch (componentInstanceType) {
+ case 'SERVICE':
+ newComponentInstance = new Models.ComponentsInstances.ServiceInstance();
+ break;
+
+ case 'PRODUCT':
+ newComponentInstance = new Models.ComponentsInstances.ProductInstance();
+ break;
+
+ default :
+ newComponentInstance = new Models.ComponentsInstances.ResourceInstance();
+ break;
+ }
+ return newComponentInstance;
+ };
+
+ public createComponentInstanceFromComponent = (component: Models.Components.Component):Models.ComponentsInstances.ComponentInstance => {
+ let newComponentInstance:Models.ComponentsInstances.ComponentInstance = this.createEmptyComponentInstance(component.componentType);
+ newComponentInstance.uniqueId = component.uniqueId + (new Date()).getTime();
+ newComponentInstance.posX = 0;
+ newComponentInstance.posY = 0;
+ newComponentInstance.name = component.name;
+ newComponentInstance.componentVersion = component.version;
+ newComponentInstance.originType = component.getComponentSubType();
+ //new component instance -> req. & cap. are added on successful instance creation
+ newComponentInstance.requirements = component.requirements;
+ newComponentInstance.capabilities = component.capabilities;
+ newComponentInstance.icon = component.icon;
+ newComponentInstance.componentUid = component.uniqueId;
+ return newComponentInstance;
+ };
+
+ }
+}
diff --git a/catalog-ui/app/scripts/utils/constants.ts b/catalog-ui/app/scripts/utils/constants.ts
new file mode 100644
index 0000000000..6db1edf29d
--- /dev/null
+++ b/catalog-ui/app/scripts/utils/constants.ts
@@ -0,0 +1,247 @@
+/*-
+ * ============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 2/18/2016.
+ */
+/// <reference path="../references"/>
+module Sdc.Utils.Constants {
+
+ import SuiteOrSpec = jasmine.SuiteOrSpec;
+ export let DEFAULT_ICON = 'defaulticon';
+ export let CP_END_POINT = 'CpEndPoint';
+ export let CHANGE_COMPONENT_CSAR_VERSION_FLAG = 'changeComponentCsarVersion';
+ export let IMAGE_PATH = '';
+
+ export class ComponentType {
+ static SERVICE = 'SERVICE';
+ static RESOURCE = 'RESOURCE';
+ static PRODUCT = 'PRODUCT';
+ }
+
+ export class ResourceType {
+ static VF = 'VF';
+ static VL = 'VL';
+ static CP = 'CP';
+ static VFC = 'VFC';
+ }
+
+ export class ComponentState {
+ static CERTIFICATION_IN_PROGRESS = 'CERTIFICATION_IN_PROGRESS';
+ static CERTIFIED = 'CERTIFIED';
+ static NOT_CERTIFIED_CHECKOUT = 'NOT_CERTIFIED_CHECKOUT';
+ static NOT_CERTIFIED_CHECKIN = 'NOT_CERTIFIED_CHECKIN';
+ static READY_FOR_CERTIFICATION = 'READY_FOR_CERTIFICATION';
+ }
+
+ export class DistributionStatus {
+ DISTRIBUTION_NOT_APPROVED = 'DISTRIBUTION_NOT_APPROVED';
+ DISTRIBUTION_APPROVED = 'DISTRIBUTION_APPROVED';
+ DISTRIBUTED = 'DISTRIBUTED';
+ DISTRIBUTION_REJECTED = 'DISTRIBUTION_REJECTED';
+ }
+
+ export class ArtifactGroupType {
+ static DEPLOYMENT = "DEPLOYMENT";
+ static INFORMATION = "INFORMATIONAL";
+ static SERVICE_API = "SERVICE_API";
+ }
+
+ export class ArtifactType {
+ static HEAT = "HEAT";
+ static VF_LICENSE = "VF_LICENSE";
+ static VENDOR_LICENSE = "VENDOR_LICENSE";
+ static THIRD_PARTY_RESERVED_TYPES = { WORKFLOW:"WORKFLOW",
+ NETWORK_CALL_FLOW:"NETWORK_CALL_FLOW",
+ AAI_SERVICE_MODEL:"AAI_SERVICE_MODEL",
+ AAI_VF_MODEL:"AAI_VF_MODEL",
+ AAI_VF_MODULE_MODEL:"AAI_VF_MODULE_MODEL",
+ AAI_VF_INSTANCE_MODEL:"AAI_VF_INSTANCE_MODEL"};
+ static TOSCA = { TOSCA_TEMPLATE:"TOSCA_TEMPLATE", TOSCA_CSAR:"TOSCA_CSAR"};
+ }
+
+ export class SEVERITY {
+ public static DEBUG = 'DEBUG';
+ public static INFO = 'INFO';
+ public static WARNING = 'WARNING';
+ public static ERROR = 'ERROR';
+ }
+
+ export class PROPERTY_TYPES {
+ public static STRING = 'string';
+ public static INTEGER = 'integer';
+ public static FLOAT = 'float';
+ public static BOOLEAN = 'boolean';
+ public static JSON = 'json';
+ public static MAP = 'map';
+ public static LIST = 'list';
+ }
+
+ export class SOURCES {
+ public static A_AND_AI = 'A&AI';
+ public static ORDER = 'Order';
+ public static RUNTIME = 'Runtime';
+ }
+
+ export class PROPERTY_DATA {
+ public static TYPES = [PROPERTY_TYPES.STRING, PROPERTY_TYPES.INTEGER, PROPERTY_TYPES.FLOAT, PROPERTY_TYPES.BOOLEAN, PROPERTY_TYPES.JSON, PROPERTY_TYPES.LIST, PROPERTY_TYPES.MAP];
+ public static SIMPLE_TYPES = [PROPERTY_TYPES.STRING, PROPERTY_TYPES.INTEGER, PROPERTY_TYPES.FLOAT, PROPERTY_TYPES.BOOLEAN, PROPERTY_TYPES.JSON];
+ public static SOURCES = [SOURCES.A_AND_AI, SOURCES.ORDER, SOURCES.RUNTIME];
+ }
+
+ export class PROPERTY_VALUE_CONSTRAINTS {
+ public static MAX_LENGTH = 100;
+ public static JSON_MAX_LENGTH = 4096;
+ }
+
+ export class Role {
+ public static ADMIN = 'ADMIN';
+ public static DESIGNER = 'DESIGNER';
+ public static PRODUCT_STRATEGIST = 'PRODUCT_STRATEGIST';
+ public static PRODUCT_MANAGER = 'PRODUCT_MANAGER';
+ public static TESTER = 'TESTER';
+ public static OPS = 'OPS';
+ public static GOVERNOR = 'GOVERNOR';
+ }
+
+ export enum FormState{
+ CREATE,
+ UPDATE,
+ IMPORT,
+ VIEW
+ }
+
+ export class WorkspaceMode {
+ public static CREATE = 'create';
+ public static EDIT = 'edit';
+ public static IMPORT = 'import';
+ public static VIEW = 'view';
+ }
+
+ export class ImagesUrl {
+ public static RESOURCE_ICONS = '/styles/images/resource-icons/';
+ public static SERVICE_ICONS = '/styles/images/service-icons/';
+ public static SELECTED_UCPE_INSTANCE = '/styles/images/resource-icons/selectedUcpeInstance.png';
+ public static SELECTED_CP_INSTANCE = '/styles/images/resource-icons/selectedCPInstance.png';
+ public static SELECTED_VL_INSTANCE = '/styles/images/resource-icons/selectedVLInstance.png';
+ public static CANVAS_PLUS_ICON = '/styles/images/resource-icons/canvasPlusIcon.png';
+ public static MODULE_ICON = '/styles/images/resource-icons/module.png';
+ public static OPEN_MODULE_ICON = '/styles/images/resource-icons/openModule.png';
+ public static OPEN_MODULE_HOVER_ICON = '/styles/images/resource-icons/openModuleHover.png';
+ public static CLOSE_MODULE_ICON = '/styles/images/resource-icons/closeModule.png';
+ public static CLOSE_MODULE_HOVER_ICON = '/styles/images/resource-icons/closeModuleHover.png';
+ }
+
+ export class ModalType {
+ static STANDARD = 'standard';
+ static ERROR = 'error';
+ static ALERT = 'alert';
+ }
+
+ export class GraphColors {
+ public static NOT_CERTIFIED_LINK = 'rgb(218,31,61)';
+ public static VL_LINK = 'rgb(216,216,216)';
+ public static ACTIVE_LINK = '#30bdf2';
+ public static BASE_LINK = 'rgb(55,55,55)';
+ public static NODE_BACKGROUND_COLOR = 'rgba(46, 162, 157, 0.24)';
+ public static NODE_SHADOW_COLOR = 'rgba(198, 230, 228, 0.7)';
+ public static NODE_OVERLAPPING_BACKGROUND_COLOR = 'rgba(179, 10, 60, 0.24)';
+ public static NODE_OVERLAPPING_SHADOW_COLOR = 'rgba(236, 194, 206, 0.7)';
+ public static NODE_UCPE_CP = '#9063cd';
+ public static NODE_UCPE = '#fbfbfb';
+ public static NODE_SELECTED_BORDER_COLOR = '#30bdf2';
+ }
+
+ export class GraphTransactionLogText {
+ public static REMOVE_TEMP_LINK = "remove tempLink";
+ public static DELETE_LINK = "delete link";
+ public static ADD_LINK = "delete link";
+ public static ADD_NODE = "adding node";
+ }
+
+ export class GraphUIObjects {
+ public static LINK_MENU_HEIGHT = 420;
+ public static TOP_HEADER_HEIGHT = 200;
+ public static TOOLTIP_OFFSET_X = 50;
+ public static TOOLTIP_OFFSET_Y = 145;
+ public static TOOLTIP_LINK_OFFSET_X = 35;
+ public static TOOLTIP_LINK_OFFSET_Y = 75;
+ public static MENU_LINK_VL_HEIGHT_OFFSET = 250;
+ public static MENU_LINK_VL_WIDTH_OFFSET = 200;
+ public static MENU_LINK_SIMPLE_HEIGHT_OFFSET = 180;
+ public static MENU_LINK_SIMPLE_WIDTH_OFFSET = 130;
+ public static DIAGRAM_RIGHT_WIDTH_OFFSET = 248;
+ public static DIAGRAM_HEADER_OFFSET = 103;
+ public static DIAGRAM_PALETTE_WIDTH_OFFSET = 247;
+
+ }
+
+ export class States {
+ public static WORKSPACE_GENERAL = 'workspace.general';
+ public static WORKSPACE_ICONS = 'workspace.icons';
+ public static WORKSPACE_ACTIVITY_LOG = 'workspace.activity_log';
+ public static WORKSPACE_DEPLOYMENT_ARTIFACTS = 'workspace.deployment_artifacts';
+ public static WORKSPACE_PROPERTIES = 'workspace.properties';
+ public static WORKSPACE_SERVICE_INPUTS = 'workspace.service_inputs';
+ public static WORKSPACE_RESOURCE_INPUTS = 'workspace.resource_inputs';
+ public static WORKSPACE_ATTRIBUTES = 'workspace.attributes';
+ public static WORKSPACE_HIERARCHY = 'workspace.hierarchy';
+ public static WORKSPACE_INFORMATION_ARTIFACTS = 'workspace.information_artifacts';
+ public static WORKSPACE_TOSCA_ARTIFACTS = 'workspace.tosca_artifacts';
+ public static WORKSPACE_COMPOSITION = 'workspace.composition';
+ public static WORKSPACE_NETWORK_CALL_FLOW = 'workspace.network_call_flow';
+ public static WORKSPACE_MANAGEMENT_WORKFLOW = 'workspace.management_workflow';
+ public static WORKSPACE_DEPLOYMENT = 'workspace.deployment';
+ public static WORKSPACE_DISTRIBUTION = 'workspace.distribution';
+ public static WORKSPACE_REQUIREMENTS_AND_CAPABILITIES = 'workspace.reqAndCap';
+ }
+
+ export class EVENTS {
+ static RESOURCE_LEFT_PALETTE_UPDATE_EVENT = "resourceLeftPanelUpdateEvent";
+ static SERVICE_LEFT_PALETTE_UPDATE_EVENT = "serviceLeftPanelUpdateEvent";
+ static PRODUCT_LEFT_PALETTE_UPDATE_EVENT = "productLeftPanelUdateEvent";
+ static VL_LEFT_PALETTE_UPDATE_EVENT = "vlLeftPanelUdateEvent";
+ static ON_CSAR_LOADING = "onCsarLoading";
+ static DOWNLOAD_ARTIFACT_FINISH_EVENT = "downloadArtifactFinishEvent";
+ static ON_WORKSPACE_SAVE_BUTTON_CLICK = "onWorkspaceSaveButtonClick";
+ static ON_WORKSPACE_SAVE_BUTTON_SUCCESS = "onWorkspaceSaveButtonSuccess";
+ static ON_WORKSPACE_SAVE_BUTTON_ERROR = "onWorkspaceSaveButtonError";
+
+ //Loader events
+ static SHOW_LOADER_EVENT = "showLoaderEvent";
+ static HIDE_LOADER_EVENT = "hideLoaderEvent";
+ }
+
+ export class GRAPH_EVENTS {
+ static ON_NODE_SELECTED = "onNodeSelected";
+ static ON_GRAPH_BACKGROUND_CLICKED = "onGraphBackgroundClicked";
+ static ON_PALETTE_COMPONENT_HOVER_IN = 'onPaletteComponentHoverIn';
+ static ON_PALETTE_COMPONENT_HOVER_OUT = 'onPaletteComponentHoverOut';
+ static ON_PALETTE_COMPONENT_DRAG_START = 'onPaletteComponentDragStart';
+ static ON_PALETTE_COMPONENT_DRAG_ACTION = 'onPaletteComponentDragAction';
+ static ON_COMPONENT_INSTANCE_NAME_CHANGED = 'onComponentInstanceNameChanged';
+ static ON_DELETE_COMPONENT_INSTANCE = 'onDeleteComponentInstance';
+ static ON_DELETE_MULTIPLE_COMPONENTS = 'onDeleteMultipleComponents';
+ static ON_DELETE_EDGE = 'onDeleteEdge';
+ static ON_INSERT_NODE_TO_UCPE = 'onInsertNodeToUCPE';
+ static ON_REMOVE_NODE_FROM_UCPE = 'onRemoveNodeFromUCPE';
+ static ON_VERSION_CHANGED = 'onVersionChanged';
+ }
+
+}
diff --git a/catalog-ui/app/scripts/utils/dictionary/dictionary.ts b/catalog-ui/app/scripts/utils/dictionary/dictionary.ts
new file mode 100644
index 0000000000..ef9a1bc4ea
--- /dev/null
+++ b/catalog-ui/app/scripts/utils/dictionary/dictionary.ts
@@ -0,0 +1,257 @@
+/*-
+ * ============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=========================================================
+ */
+/**
+
+ This code was copy from collections.ts lib
+ https://github.com/basarat/typescript-collections
+**/
+
+module Sdc.Utils{
+ 'use strict';
+
+ // Used internally by dictionary
+ interface IDictionaryPair<K, V>{
+ key: K;
+ value: V;
+ }
+
+ export class Dictionary<K, V>{
+
+ /**
+ * Object holding the key-value pairs.
+ * @type {Object}
+ * @private
+ */
+ private table: { [key: string]: IDictionaryPair<K, V> };
+ //: [key: K] will not work since indices can only by strings in javascript and typescript enforces this.
+
+ /**
+ * Number of elements in the list.
+ * @type {number}
+ * @private
+ */
+ private nElements: number;
+
+ /**
+ * Function used to convert keys to strings.
+ * @type {function(Object):string}
+ * @private
+ */
+ private toStr: (key: K) => string;
+
+
+ /**
+ * Creates an empty dictionary.
+ * @class <p>Dictionaries map keys to values; each key can map to at most one value.
+ * This implementation accepts any kind of objects as keys.</p>
+ *
+ * <p>If the keys are custom objects a function which converts keys to unique
+ * strings must be provided. Example:</p>
+ * <pre>
+ * function petToString(pet) {
+ * return pet.name;
+ * }
+ * </pre>
+ * @constructor
+ * @param {function(Object):string=} toStrFunction optional function used
+ * to convert keys to strings. If the keys aren"t strings or if toString()
+ * is not appropriate, a custom function which receives a key and returns a
+ * unique string must be provided.
+ */
+ constructor(toStrFunction?: (key: K) => string) {
+ this.table = {};
+ this.nElements = 0;
+ this.toStr = toStrFunction || this.defaultToString;
+ }
+
+
+ /**
+ copy from angular.js isUndefined
+ */
+ private isUndefined = (value: any):boolean => {
+ return typeof value === 'undefined';
+ }
+
+ defaultToString = (item: any): string => {
+ return item.toString();
+ }
+
+ /**
+ * Returns the value to which this dictionary maps the specified key.
+ * Returns undefined if this dictionary contains no mapping for this key.
+ * @param {Object} key key whose associated value is to be returned.
+ * @return {*} the value to which this dictionary maps the specified key or
+ * undefined if the map contains no mapping for this key.
+ */
+ getValue = (key: K): V => {
+ let pair: IDictionaryPair<K, V> = this.table[this.toStr(key)];
+ if (this.isUndefined(pair)) {
+ return undefined;
+ }
+ return pair.value;
+ }
+
+
+ /**
+ * Associates the specified value with the specified key in this dictionary.
+ * If the dictionary previously contained a mapping for this key, the old
+ * value is replaced by the specified value.
+ * @param {Object} key key with which the specified value is to be
+ * associated.
+ * @param {Object} value value to be associated with the specified key.
+ * @return {*} previous value associated with the specified key, or undefined if
+ * there was no mapping for the key or if the key/value are undefined.
+ */
+ setValue = (key: K, value: V): V => {
+
+ if (this.isUndefined(key) || this.isUndefined(value)) {
+ return undefined;
+ }
+
+ let ret: V;
+ let k = this.toStr(key);
+ let previousElement: IDictionaryPair<K, V> = this.table[k];
+ if (this.isUndefined(previousElement)) {
+ this.nElements++;
+ ret = undefined;
+ } else {
+ ret = previousElement.value;
+ }
+ this.table[k] = {
+ key: key,
+ value: value
+ };
+ return ret;
+ }
+
+ /**
+ * Removes the mapping for this key from this dictionary if it is present.
+ * @param {Object} key key whose mapping is to be removed from the
+ * dictionary.
+ * @return {*} previous value associated with specified key, or undefined if
+ * there was no mapping for key.
+ */
+ remove = (key: K): V => {
+ let k = this.toStr(key);
+ let previousElement: IDictionaryPair<K, V> = this.table[k];
+ if (!this.isUndefined(previousElement)) {
+ delete this.table[k];
+ this.nElements--;
+ return previousElement.value;
+ }
+ return undefined;
+ }
+
+ /**
+ * Returns an array containing all of the keys in this dictionary.
+ * @return {Array} an array containing all of the keys in this dictionary.
+ */
+ keys = (): K[] => {
+ let array: K[] = [];
+ for (let name in this.table) {
+ if (this.table.hasOwnProperty(name)) {
+ let pair: IDictionaryPair<K, V> = this.table[name];
+ array.push(pair.key);
+ }
+ }
+ return array;
+ }
+
+ /**
+ * Returns an array containing all of the values in this dictionary.
+ * @return {Array} an array containing all of the values in this dictionary.
+ */
+ values = (): V[] => {
+ let array: V[] = [];
+ for (let name in this.table) {
+ if (this.table.hasOwnProperty(name)) {
+ let pair: IDictionaryPair<K, V> = this.table[name];
+ array.push(pair.value);
+ }
+ }
+ return array;
+ }
+
+ /**
+ * Executes the provided function once for each key-value pair
+ * present in this dictionary.
+ * @param {function(Object,Object):*} callback function to execute, it is
+ * invoked with two arguments: key and value. To break the iteration you can
+ * optionally return false.
+ */
+ forEach = (callback: (key: K, value: V) => any): void => {
+ for (let name in this.table) {
+ if (this.table.hasOwnProperty(name)) {
+ let pair: IDictionaryPair<K, V> = this.table[name];
+ let ret = callback(pair.key, pair.value);
+ if (ret === false) {
+ return;
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns true if this dictionary contains a mapping for the specified key.
+ * @param {Object} key key whose presence in this dictionary is to be
+ * tested.
+ * @return {boolean} true if this dictionary contains a mapping for the
+ * specified key.
+ */
+ containsKey = (key: K): boolean => {
+ return !this.isUndefined(this.getValue(key));
+ }
+
+ /**
+ * Removes all mappings from this dictionary.
+ * @this {Dictionary}
+ */
+ clear = () => {
+
+ this.table = {};
+ this.nElements = 0;
+ }
+
+ /**
+ * Returns the number of keys in this dictionary.
+ * @return {number} the number of key-value mappings in this dictionary.
+ */
+ size = (): number => {
+ return this.nElements;
+ }
+
+ /**
+ * Returns true if this dictionary contains no mappings.
+ * @return {boolean} true if this dictionary contains no mappings.
+ */
+ isEmpty = (): boolean => {
+ return this.nElements <= 0;
+ }
+
+ toString = (): string => {
+ let toret = "{";
+ this.forEach((k, v) => {
+ toret = toret + "\n\t" + k.toString() + " : " + v.toString();
+ });
+ return toret + "\n}";
+ }
+ } // End of dictionary
+
+}
diff --git a/catalog-ui/app/scripts/utils/file-utils.ts b/catalog-ui/app/scripts/utils/file-utils.ts
new file mode 100644
index 0000000000..db6251f199
--- /dev/null
+++ b/catalog-ui/app/scripts/utils/file-utils.ts
@@ -0,0 +1,73 @@
+/*-
+ * ============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.Utils {
+ export class FileUtils {
+
+ static '$inject' = [
+ '$window'
+ ];
+
+ constructor(private $window: any) {
+ }
+
+ public byteCharactersToBlob = (byteCharacters, contentType): any => {
+ contentType = contentType || '';
+ let sliceSize = 1024;
+ let bytesLength = byteCharacters.length;
+ let slicesCount = Math.ceil(bytesLength / sliceSize);
+ let byteArrays = new Array(slicesCount);
+
+ for (let sliceIndex = 0; sliceIndex < slicesCount; ++sliceIndex) {
+ let begin = sliceIndex * sliceSize;
+ let end = Math.min(begin + sliceSize, bytesLength);
+
+ let bytes = new Array(end - begin);
+ for (let offset = begin, i = 0; offset < end; ++i, ++offset) {
+ bytes[i] = byteCharacters[offset].charCodeAt(0);
+ }
+ byteArrays[sliceIndex] = new Uint8Array(bytes);
+ }
+ return new Blob(byteArrays, {type: contentType});
+ };
+
+ public base64toBlob = (base64Data, contentType): any => {
+ let byteCharacters = atob(base64Data);
+ return this.byteCharactersToBlob(byteCharacters, contentType);
+ };
+
+ public downloadFile = (blob, fileName): void=> {
+ let url = this.$window.URL.createObjectURL(blob);
+ let downloadLink = document.createElement("a");
+
+ downloadLink.setAttribute('href', url);
+ downloadLink.setAttribute('download', fileName);
+ document.body.appendChild(downloadLink);
+ downloadLink.click();
+
+ //time out for firefox
+ setTimeout(()=> {
+ document.body.removeChild(downloadLink);
+ this.$window.URL.revokeObjectURL(url);
+ }, 100);
+ }
+
+ }
+}
diff --git a/catalog-ui/app/scripts/utils/functions.ts b/catalog-ui/app/scripts/utils/functions.ts
new file mode 100644
index 0000000000..32bc9243cf
--- /dev/null
+++ b/catalog-ui/app/scripts/utils/functions.ts
@@ -0,0 +1,58 @@
+/*-
+ * ============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.Utils.Functions {
+
+ export class QueueUtils {
+
+ private executionQueue : any;
+
+ constructor(private $q:ng.IQService){
+ this.executionQueue = this.getDummyPromise();
+ }
+
+
+ private getDummyPromise = (): ng.IPromise<boolean> => {
+ let deferred : ng.IDeferred<boolean>= this.$q.defer();
+ deferred.resolve(true);
+ return deferred.promise;
+ };
+
+
+ private addMethodToQueue = (runMe:Function) : void => {
+ this.executionQueue = this.executionQueue.then(runMe, runMe);
+ };
+
+ addNonBlockingUIAction = (update:Function , releaseUIcallBack:Function) : void => {
+ releaseUIcallBack();
+ this.addMethodToQueue(update);
+ };
+
+ // The Method call is responsible for releasing the UI
+ addBlockingUIAction = ( blockingServerRequest : Function ):void => {
+ this.addMethodToQueue(blockingServerRequest);
+ };
+
+ addBlockingUIActionWithReleaseCallback = ( blockingServerRequest : Function, releaseUIcallBack:Function):void=>{
+ this.addMethodToQueue(blockingServerRequest);
+ this.addMethodToQueue(releaseUIcallBack);
+ };
+ }
+}
diff --git a/catalog-ui/app/scripts/utils/menu-handler.ts b/catalog-ui/app/scripts/utils/menu-handler.ts
new file mode 100644
index 0000000000..0f2b7de3be
--- /dev/null
+++ b/catalog-ui/app/scripts/utils/menu-handler.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.ts"/>
+module Sdc.Utils {
+
+ 'use strict';
+
+ export class MenuItem {
+ text: string;
+ callback: (...args: Array<any>) => ng.IPromise<boolean>;
+ state: string;
+ action: string;
+ params: Array<any>;
+ isDisabled: boolean;
+ disabledRoles: Array<string>;
+ blockedForTypes:Array<string>; // This item will not be shown for specific components types.
+
+ //TODO check if needed
+ confirmationModal:string; // Open confirmation modal (user should select "OK" or "Cancel"), and continue with the action.
+ emailModal:string; // Open email modal (user should fill email details), and continue with the action.
+ url:string; // Data added to menu item, in case the function need to use it, example: for function "changeLifecycleState", I need to pass also the state "CHECKOUT" that I want the state to change to.
+
+
+ constructor(text: string, callback: (...args: Array<any>) => ng.IPromise<boolean>, state:string, action:string, params?: Array<any>, blockedForTypes?:Array<string>) {
+ this.text = text;
+ this.callback = callback;
+ this.state = state;
+ this.action = action;
+ this.params = params;
+ this.blockedForTypes = blockedForTypes;
+ }
+ }
+
+ export class MenuItemGroup {
+ selectedIndex: number;
+ menuItems: Array<MenuItem>;
+ itemClick: boolean;
+
+ constructor(selectedIndex?:number, menuItems?: Array<MenuItem>, itemClick?: boolean) {
+ this.selectedIndex = selectedIndex;
+ this.menuItems = menuItems;
+ this.itemClick = itemClick;
+ }
+
+ public updateSelectedMenuItemText (newText: string) {
+ this.menuItems[this.selectedIndex].text = newText;
+ }
+ }
+
+
+ export class MenuHandler {
+
+ static '$inject' = [
+ 'sdcConfig',
+ 'sdcMenu',
+ 'ComponentFactory',
+ '$templateCache',
+ '$filter',
+ '$modal',
+ 'ModalsHandler',
+ '$state',
+ '$q'
+ ];
+
+ constructor(
+ private sdcConfig:Models.IAppConfigurtaion,
+ private sdcMenu:Models.IAppMenu,
+ private ComponentFactory: ComponentFactory,
+ private $templateCache:ng.ITemplateCacheService,
+ private $filter:ng.IFilterService,
+ private $modal:ng.ui.bootstrap.IModalService,
+ private ModalsHandler: ModalsHandler,
+ private $state:ng.ui.IStateService,
+ private $q:ng.IQService
+ ) {
+
+ }
+
+
+ generateBreadcrumbsModelFromComponents = (components: Array<Sdc.Models.Components.Component>, selected:Sdc.Models.Components.Component):MenuItemGroup => {
+ let result = new MenuItemGroup(0, [], false);
+ if (components) {
+
+ // Search the component in all components by uuid (and not uniqueid, gives access to an assets's minor versions).
+ let selectedItem = _.find(components, (item:Sdc.Models.Components.Component) => {
+ return item.uuid === selected.uuid;
+ });
+
+ // If not found search by invariantUUID
+ if(undefined == selectedItem){
+ selectedItem = _.find(components, (item:Sdc.Models.Components.Component) => {
+ //invariantUUID && Certified State matches between major versions
+ return item.invariantUUID === selected.invariantUUID && item.lifecycleState === Utils.Constants.ComponentState.CERTIFIED;
+ });
+ }
+
+ // If not found search by name (name is unique).
+ if(undefined == selectedItem){
+ selectedItem = _.find(components, (item:Sdc.Models.Components.Component) => {
+ return item.name === selected.name;
+ });
+ }
+
+ result.selectedIndex = components.indexOf(selectedItem);
+ components[result.selectedIndex] = selected;
+ let clickItemCallback = (component: Sdc.Models.Components.Component): ng.IPromise<boolean> => {
+ this.$state.go('workspace.general', {id: component.uniqueId, type:component.componentType.toLowerCase(), mode: Utils.Constants.WorkspaceMode.VIEW});
+ return this.$q.when(true);
+ };
+
+ components.forEach((component:Sdc.Models.Components.Component) => {
+ let menuItem = new MenuItem(
+ // component.name,
+ component.getComponentSubType() + ': ' + this.$filter('resourceName')(component.name),
+ clickItemCallback,
+ null,
+ null,
+ [component]
+ );
+ // menuItem.text = component.name;
+ result.menuItems.push(menuItem);
+ });
+ }
+ return result;
+ };
+
+ }
+}
diff --git a/catalog-ui/app/scripts/utils/modals-handler.ts b/catalog-ui/app/scripts/utils/modals-handler.ts
new file mode 100644
index 0000000000..f3e80a7a24
--- /dev/null
+++ b/catalog-ui/app/scripts/utils/modals-handler.ts
@@ -0,0 +1,275 @@
+/*-
+ * ============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 2/11/2016.
+ */
+/// <reference path="../references"/>
+module Sdc.Utils {
+
+ export interface IModalsHandler {
+
+ openViewerModal(component:Models.Components.Component):void;
+ openDistributionStatusModal(distribution: Models.Distribution,status:string):void;
+ openConfirmationModal (title:string, message:string, showComment:boolean, size?: string):ng.IPromise<any>;
+ openAlertModal (title:string, message:string, size?: string):ng.IPromise<any>;
+ openStandardModal (title:string, message:string, size?: string):ng.IPromise<any>;
+ openErrorModal (title:string, message:string, size?: string):ng.IPromise<any>;
+ openEmailModal(emailModel:ViewModels.IEmailModalModel) :ng.IPromise<any>;
+ openServerMessageModal(data:Sdc.ViewModels.IServerMessageModalModel): ng.IPromise<any>;
+ openClientMessageModal(data:Sdc.ViewModels.IClientMessageModalModel): ng.IPromise<ng.ui.bootstrap.IModalServiceInstance>;
+ openWizardArtifactModal(artifact: Models.ArtifactModel, component:Models.Components.Component): ng.IPromise<any>;
+ openWizard(componentType: Utils.Constants.ComponentType, component?:Models.Components.Component, importedFile?: any): ng.IPromise<any>;
+ }
+
+ export class ModalsHandler implements IModalsHandler{
+
+ static '$inject' = [
+ '$templateCache',
+ '$modal',
+ '$q'
+ ];
+
+ constructor(private $templateCache:ng.ITemplateCacheService,
+ private $modal:ng.ui.bootstrap.IModalService,
+ private $q:ng.IQService) {
+ }
+
+ openViewerModal = (component:Models.Components.Component):void => {
+
+ let modalOptions:ng.ui.bootstrap.IModalSettings = {
+ template: this.$templateCache.get('/app/scripts/view-models/component-viewer/component-viewer.html'),
+ controller: 'Sdc.ViewModels.ComponentViewerViewModel',
+ size: 'lg',
+ backdrop: 'static',
+ resolve: {
+ component: ():Models.Components.Component=> {
+ return component;
+ }
+ }
+ };
+ this.$modal.open(modalOptions);
+ };
+
+
+ openDistributionStatusModal = (distribution: Models.Distribution,status:string): ng.IPromise<any> => {
+ let deferred = this.$q.defer();
+ let modalOptions:ng.ui.bootstrap.IModalSettings = {
+ template: this.$templateCache.get('/app/scripts/view-models/workspace/tabs/distribution/disribution-status-modal/disribution-status-modal-view.html'),
+ controller: 'Sdc.ViewModels.DistributionStatusModalViewModel',
+ size: 'sdc-xl',
+ backdrop: 'static',
+ resolve: {
+ data: ():any => {
+ return {
+ 'distribution': distribution,
+ 'status': status
+ };
+ }
+ }
+ };
+ let modalInstance:ng.ui.bootstrap.IModalServiceInstance = this.$modal.open(modalOptions);
+ deferred.resolve(modalInstance.result);
+ return deferred.promise;
+ };
+
+
+
+ openAlertModal = (title:string, message:string, size?: string):ng.IPromise<any> => {
+ return this.openConfirmationModalBase(title, message, false, Utils.Constants.ModalType.ALERT, size);
+ };
+
+ openStandardModal = (title:string, message:string, size?: string):ng.IPromise<any> => {
+ return this.openConfirmationModalBase(title, message, false, Utils.Constants.ModalType.STANDARD, size);
+ };
+
+ openErrorModal = (title:string, message:string, size?: string):ng.IPromise<any> => {
+ return this.openConfirmationModalBase(title, message, false, Utils.Constants.ModalType.ERROR, size);
+ };
+
+ openConfirmationModal = (title:string, message:string, showComment:boolean, size?: string):ng.IPromise<any> => {
+ return this.openConfirmationModalBase(title, message, showComment, Utils.Constants.ModalType.STANDARD, size);
+ };
+
+ private openConfirmationModalBase = (title:string, message:string, showComment:boolean, type:Utils.Constants.ModalType, size?: string):ng.IPromise<any> => {
+ let deferred = this.$q.defer();
+ let modalOptions:ng.ui.bootstrap.IModalSettings = {
+ template: this.$templateCache.get('/app/scripts/view-models/modals/confirmation-modal/confirmation-modal-view.html'),
+ controller: 'Sdc.ViewModels.ConfirmationModalViewModel',
+ size: size? size:'sdc-sm',
+ backdrop: 'static',
+ resolve: {
+ confirmationModalModel: ():Sdc.ViewModels.IConfirmationModalModel => {
+ let model:Sdc.ViewModels.IConfirmationModalModel = {
+ title: title,
+ message: message,
+ showComment: showComment,
+ type: type
+ };
+ return model;
+ }
+ }
+ };
+
+ let modalInstance:ng.ui.bootstrap.IModalServiceInstance = this.$modal.open(modalOptions);
+ deferred.resolve(modalInstance.result);
+ return deferred.promise;
+ };
+
+ openEmailModal = (emailModel:ViewModels.IEmailModalModel):ng.IPromise<any> => {
+
+ let deferred = this.$q.defer();
+ let modalOptions:ng.ui.bootstrap.IModalSettings = {
+ template: this.$templateCache.get('/app/scripts/view-models/modals/email-modal/email-modal-view.html'),
+ controller: 'Sdc.ViewModels.EmailModalViewModel',
+ size: 'sdc-sm',
+ backdrop: 'static',
+ resolve: {
+ emailModalModel: ():ViewModels.IEmailModalModel => {
+ return emailModel;
+ }
+ }
+ };
+ let modalInstance:ng.ui.bootstrap.IModalServiceInstance = this.$modal.open(modalOptions);
+ deferred.resolve(modalInstance.result);
+ return deferred.promise;
+
+ };
+
+ openServerMessageModal = (data:Sdc.ViewModels.IServerMessageModalModel):ng.IPromise<any> => {
+ let deferred = this.$q.defer();
+ let modalOptions:ng.ui.bootstrap.IModalSettings = {
+ template: this.$templateCache.get('/app/scripts/view-models/modals/message-modal/message-server-modal/server-message-modal-view.html'),
+ controller: 'Sdc.ViewModels.ServerMessageModalViewModel',
+ size: 'sdc-sm',
+ backdrop: 'static',
+ resolve: {
+ serverMessageModalModel: ():Sdc.ViewModels.IServerMessageModalModel => {
+ return data;
+ }
+ }
+ };
+
+ let modalInstance:ng.ui.bootstrap.IModalServiceInstance = this.$modal.open(modalOptions);
+ deferred.resolve(modalInstance.result);
+ return deferred.promise;
+ };
+
+ openClientMessageModal = (data:Sdc.ViewModels.IClientMessageModalModel):ng.IPromise<any> => {
+ let deferred = this.$q.defer();
+ let modalOptions:ng.ui.bootstrap.IModalSettings = {
+ template: this.$templateCache.get('/app/scripts/view-models/modals/message-modal/message-client-modal/client-message-modal-view.html'),
+ controller: 'Sdc.ViewModels.ClientMessageModalViewModel',
+ size: 'sdc-sm',
+ backdrop: 'static',
+ resolve: {
+ clientMessageModalModel: ():Sdc.ViewModels.IClientMessageModalModel => {
+ return data;
+ }
+ }
+ };
+ let modalInstance:ng.ui.bootstrap.IModalServiceInstance = this.$modal.open(modalOptions);
+ deferred.resolve(modalInstance);
+ return deferred.promise;
+ };
+
+ openOnboadrdingModal = (okButtonText:string,currentCsarUUID?:string): ng.IPromise<any> => {
+ let deferred = this.$q.defer();
+ let modalOptions:ng.ui.bootstrap.IModalSettings = {
+ template: this.$templateCache.get('/app/scripts/view-models/modals/onboarding-modal/onboarding-modal-view.html'),
+ controller: 'Sdc.ViewModels.OnboardingModalViewModel',
+ size: 'sdc-xl',
+ backdrop: 'static',
+ resolve: {
+ okButtonText:():string=>{
+ return okButtonText;
+ },
+ currentCsarUUID:():string=>{
+ return currentCsarUUID||null;
+ }
+ }
+ };
+ let modalInstance:ng.ui.bootstrap.IModalServiceInstance = this.$modal.open(modalOptions);
+ deferred.resolve(modalInstance.result);
+ return deferred.promise;
+ };
+
+
+ openWizard = (componentType: Utils.Constants.ComponentType, component?:Models.Components.Component, importedFile?: any): ng.IPromise<any> => {
+ let deferred = this.$q.defer();
+ let template = this.$templateCache.get('/app/scripts/view-models/wizard/wizard-creation-base.html');
+
+ let controller:string;
+ if(component){
+ controller = 'Sdc.ViewModels.Wizard.EditWizardViewModel'; //Edit mode
+ } else {
+ if (importedFile){
+ controller = 'Sdc.ViewModels.Wizard.ImportWizardViewModel'; // Import Mode
+ } else {
+ controller = 'Sdc.ViewModels.Wizard.CreateWizardViewModel'; // Create Mode
+ }
+ }
+ let modalOptions:ng.ui.bootstrap.IModalSettings = {
+ template: template,
+ controller: controller,
+ size: 'sdc-xl',
+ backdrop: 'static',
+ keyboard: false,
+ resolve: {
+ data: ():any => {
+ return {
+ 'componentType': componentType,
+ 'component': component,
+ 'importFile':importedFile
+ };
+ }
+ }
+ };
+
+ let modalInstance:ng.ui.bootstrap.IModalServiceInstance = this.$modal.open(modalOptions);
+ deferred.resolve(modalInstance.result);
+ return deferred.promise;
+ };
+
+ openWizardArtifactModal = (artifact: Models.ArtifactModel, component:Models.Components.Component): ng.IPromise<any> => {
+ let deferred = this.$q.defer();
+ let viewModelsHtmlBasePath:string = '/app/scripts/view-models/';
+ let modalOptions:ng.ui.bootstrap.IModalSettings = {
+ template: this.$templateCache.get(viewModelsHtmlBasePath + 'wizard/artifact-form-step/artifact-form-step-view.html'),
+ controller: 'Sdc.ViewModels.Wizard.ArtifactResourceFormStepViewModel',
+ size: 'sdc-md',
+ backdrop: 'static',
+ keyboard: false,
+ resolve: {
+ artifact: ():Models.ArtifactModel => {
+ return artifact;
+ },
+ component: (): Models.Components.Component => {
+ return component;
+ }
+ }
+ };
+
+ let modalInstance:ng.ui.bootstrap.IModalServiceInstance = this.$modal.open(modalOptions);
+ deferred.resolve(modalInstance.result);
+ return deferred.promise;
+ };
+
+ }
+}
diff --git a/catalog-ui/app/scripts/utils/prototypes.ts b/catalog-ui/app/scripts/utils/prototypes.ts
new file mode 100644
index 0000000000..de961cfc4b
--- /dev/null
+++ b/catalog-ui/app/scripts/utils/prototypes.ts
@@ -0,0 +1,155 @@
+/*-
+ * ============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=========================================================
+ */
+interface String {
+ format(variables:Array<string>):string
+}
+
+interface Array<T> {
+ clean(o: T): Array<T>;
+}
+
+
+/**
+ * This function will replace the %<number> with strings (from array).
+ * Example: "Requested '%1' resource was not found.".format(["MyResource"]);
+ * Note: in case the array contains empty string the function will also remove the '' or the "".
+ */
+if (!String.hasOwnProperty("format")) {
+ String.prototype["format"] = function (variables:Array<string>) : string {
+
+ if (variables===null || variables===undefined || variables.length===0){
+ variables=[''];
+ }
+
+ for (let i=0;i<variables.length;i++){
+ if (variables[i]==='' || variables[i]===null){
+ variables[i]='--DELETE--';
+ }
+ }
+
+ let res = this.replace(/%(\d+)/g, function(_,m) {
+ return variables[--m];
+ });
+
+ res = res.replace(" '--DELETE--' "," ");
+ res = res.replace(" \"--DELETE--\" "," ");
+ res = res.replace("'--DELETE--'","");
+ res = res.replace("\"--DELETE--\"","");
+ res = res.replace("--DELETE--","");
+
+ return res;
+ };
+}
+
+if (!String.hasOwnProperty("capitalizeFirstLetter")) {
+ String.prototype["capitalizeFirstLetter"] = function() {
+ return this.charAt(0).toUpperCase() + this.slice(1);
+ };
+}
+
+if (!String.hasOwnProperty("replaceAll")) {
+ String.prototype["replaceAll"] = function (find:string, replace:string) : string {
+ return this.replace(new RegExp(find, 'g'), replace);
+ };
+}
+
+if (!Array.hasOwnProperty("clean")) {
+ Array.prototype.clean = function (deleteValue) {
+ for (let i = 0; i < this.length; i++) {
+ if (this[i] == deleteValue) {
+ this.splice(i, 1);
+ i--;
+ }
+ }
+ return this;
+ };
+}
+
+if (!Array.prototype.map) {
+ Array.prototype.map = function(callback, thisArg) {
+
+ let T, A, k;
+
+ if (this == null) {
+ throw new TypeError(" this is null or not defined");
+ }
+
+ // 1. Let O be the result of calling ToObject passing the |this| value as the argument.
+ let O = Object(this);
+
+ // 2. Let lenValue be the result of calling the Get internal method of O with the argument "length".
+ // 3. Let len be ToUint32(lenValue).
+ let len = O.length >>> 0;
+
+ // 4. If IsCallable(callback) is false, throw a TypeError exception.
+ // See: http://es5.github.com/#x9.11
+ if (typeof callback !== "function") {
+ throw new TypeError(callback + " is not a function");
+ }
+
+ // 5. If thisArg was supplied, let T be thisArg; else let T be undefined.
+ if (thisArg) {
+ T = thisArg;
+ }
+
+ // 6. Let A be a new array created as if by the expression new Array(len) where Array is
+ // the standard built-in constructor with that name and len is the value of len.
+ A = new Array(len);
+
+ // 7. Let k be 0
+ k = 0;
+
+ // 8. Repeat, while k < len
+ while(k < len) {
+
+ let kValue, mappedValue;
+
+ // a. Let Pk be ToString(k).
+ // This is implicit for LHS operands of the in operator
+ // b. Let kPresent be the result of calling the HasProperty internal method of O with argument Pk.
+ // This step can be combined with c
+ // c. If kPresent is true, then
+ if (k in O) {
+
+ // i. Let kValue be the result of calling the Get internal method of O with argument Pk.
+ kValue = O[ k ];
+
+ // ii. Let mappedValue be the result of calling the Call internal method of callback
+ // with T as the this value and argument list containing kValue, k, and O.
+ mappedValue = callback.call(T, kValue, k, O);
+
+ // iii. Call the DefineOwnProperty internal method of A with arguments
+ // Pk, Property Descriptor {Value: mappedValue, : true, Enumerable: true, Configurable: true},
+ // and false.
+
+ // In browsers that support Object.defineProperty, use the following:
+ // Object.defineProperty(A, Pk, { value: mappedValue, writable: true, enumerable: true, configurable: true });
+
+ // For best browser support, use the following:
+ A[ k ] = mappedValue;
+ }
+ // d. Increase k by 1.
+ k++;
+ }
+
+ // 9. return A
+ return A;
+ };
+}
diff --git a/catalog-ui/app/scripts/utils/validation-utils.ts b/catalog-ui/app/scripts/utils/validation-utils.ts
new file mode 100644
index 0000000000..7618e7d0e3
--- /dev/null
+++ b/catalog-ui/app/scripts/utils/validation-utils.ts
@@ -0,0 +1,173 @@
+/*-
+ * ============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.Utils {
+ class basePattern{
+ pattern:RegExp;
+ base:number;
+ constructor(pattern:RegExp, base:number){
+ this.pattern = pattern;
+ this.base = base;
+ }
+ }
+
+ export interface IMapRegex{
+ integer: RegExp;
+ boolean: RegExp;
+ float: RegExp;
+ string: RegExp;
+ }
+
+ export class ValidationUtils {
+
+ static '$inject' = [
+ 'IntegerNoLeadingZeroValidationPattern',
+ 'FloatValidationPattern',
+ 'CommentValidationPattern',
+ 'BooleanValidationPattern',
+ 'NumberValidationPattern',
+ 'LabelValidationPattern',
+ ];
+ private trueRegex : string = '[t][r][u][e]|[t]|[o][n]|[y]|[y][e][s]|[1]';
+ private falseRegex : string = '[f][a][l][s][e]|[f]|[o][f][f]|[n]|[n][o]|[0]';
+ private heatBooleanValidationPattern : RegExp = new RegExp( '^('+this.trueRegex+'|'+this.falseRegex+')$');
+
+
+ constructor(private IntegerNoLeadingZeroValidationPattern:RegExp,
+ private FloatValidationPattern:RegExp,
+ private CommentValidationPattern:RegExp,
+ private BooleanValidationPattern:RegExp,
+ private NumberValidationPattern:RegExp,
+ private LabelValidationPattern:RegExp) {}
+
+ public stripAndSanitize(text:string):string{
+ if(!text){
+ return null;
+ }
+ return text.replace(/\s+/g, ' ').replace(/%[A-Fa-f0-9]{2}/g, '').trim();
+ }
+
+ public getValidationPattern = (validationType:string , parameterType?:string) : RegExp => {
+ switch (validationType){
+ case 'integer':
+ return this.IntegerNoLeadingZeroValidationPattern;
+ case 'float':
+ return this.FloatValidationPattern;
+ case 'number':
+ return this.NumberValidationPattern;
+ case 'string':
+ return this.CommentValidationPattern;
+ case 'boolean':
+ {
+ //Bug Fix DE197437 [Patch]Mismatch between BE to FE regarding supported characters in Boolean filed
+ if( parameterType && parameterType === 'heat'){
+ return this.heatBooleanValidationPattern;
+ }
+ else{
+ return this.BooleanValidationPattern;
+ }
+
+ }
+
+ case 'label':
+ return this.LabelValidationPattern;
+ case 'category':
+ return this.LabelValidationPattern;
+ default :
+ return null;
+ }
+ };
+
+ public getPropertyListPatterns():IMapRegex {
+ return {
+ integer: /^(0|[-+]?[1-9][0-9]*|[-+]?0x[0-9a-fA-F]+|[-+]?0o[0-7]+)(,?(0|[-+]?[1-9][0-9]*|[-+]?0x[0-9a-fA-F]+|[-+]?0o[0-7]+))*$/,
+ string: /^"[\u0000-\u0021\u0023-\u00BF]+"(\s*,?\s*"[\u0000-\u0021\u0023-\u00BF]+")*$/,
+ boolean: /^([Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee])(,?([Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee]))*$/,
+ float: /^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?(,?[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?f?)*$/
+ };
+ }
+ public getPropertyMapPatterns():IMapRegex {
+ return {
+ integer: /^"\w+"\s*:\s?(0|[-+]?[1-9][0-9]*|[-+]?0x[0-9a-fA-F]+|[-+]?0o[0-7]+)+(\s*,?\s*"\w+"\s?:\s?(0|[-+]?[1-9][0-9]*|[-+]?0x[0-9a-fA-F]+|[-+]?0o[0-7]+)+)*$/,
+ string: /^"\w+"\s?:\s?"[\u0000-\u0021\u0023-\u00BF]*"(\s*,?\s*"\w+"\s?:\s?"[\u0000-\u0021\u0023-\u00BF]*")*$/,
+ boolean: /^"\w+"\s?:\s?([Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee])(\s*,?\s*"\w+"\s?:\s?([Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee]))*$/,
+ float: /^"\w+"\s?:\s?[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?f?(\s*,?\s*"\w+"\s?:\s?[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?f?)*$/
+ };
+ }
+ public validateUniqueKeys(viewValue:string):boolean {
+ if(!viewValue) {
+ return true; //allow empty value
+ }
+
+ let json:string = "{" + viewValue.replace(/\s\s+/g, ' ') + "}";
+ try{
+ let obj:any = JSON.parse(json);
+ /*
+ //Method #1 : check json string length before & after parsing
+ let newJson:string = JSON.stringify(obj);
+ if (newJson.length < json.length) {
+ return false;
+ }*/
+
+ //Method #2 : check how many times we can find "KEY": in json string
+ let result:boolean = true;
+ Object.keys(obj).forEach((key:string) => {
+ result = result && json.split('"' + key + '":').length === 2;
+ });
+ return result;
+
+ }catch(e){
+ return false; //not a valid JSON
+ }
+
+ //return true;
+ }
+
+ public validateJson = (json:string):boolean => {
+ try{
+ JSON.parse(json);
+ return true;
+ }catch(err){
+ console.log('invalid json');
+ return false;
+ }
+ };
+
+ public validateIntRange = (value:string):boolean => {
+
+ let base8 = new basePattern(/^([-+]?0o[0-7]+)$/, 8);
+ let base10 = new basePattern(/^(0|[-+]?[1-9][0-9]*)$/, 10);
+ let base16 = new basePattern(/^([-+]?0x[0-9a-fA-F]+)$/, 16);
+
+ let min:number = -0x80000000;
+ let max:number = 0x7fffffff;
+ let intPatterns:Array<basePattern> = [base8, base10, base16];
+ let matchedBase = _.find(intPatterns, (item)=> {
+ return item.pattern.test(value);
+ });
+
+ let parsed:number = parseInt(value.replace('o',''), matchedBase.base);
+ if(parsed){
+ return min <= parsed && max >= parsed;
+ }
+ }
+ }
+}
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;
+ }
+ }
+ }
+}