diff options
Diffstat (limited to 'catalog-ui/app/scripts')
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 hh:mm a' : 'UTC'}} 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>​{{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}} %</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="​{{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 hh:mm a' : 'UTC'}} 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('<', '<').replace('>', '>') : ''; + 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> + <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> + <span data-ng-show="sortBy === 'lastUpdateDate'" class="w-sdc-catalog-sort-arrow" data-ng-class="{'down': reverse, 'up':!reverse}"></span> + | + <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> + <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}} {{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"> </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"><!– autoplay loop muted –> + <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> | <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="​{{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="​{{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="​{{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="​{{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="​{{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="​{{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="​{{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}} 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)}} 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)}} 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}} + <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}}"> {{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}} + <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}}"> {{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 <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> {{component.creationDate | date:'MM/dd/yyyy'}}, {{component.creatorFullName}} | <b>Modifed:</b> {{component.lastUpdateDate | date:'MM/dd/yyyy'}} | <b>UUID:</b> {{component.uuid}}<b> Invariant UUID:</b> {{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; + } + } + } +} |