diff options
Diffstat (limited to 'catalog-ui/src/app')
533 files changed, 45113 insertions, 0 deletions
diff --git a/catalog-ui/src/app/_favicon.png b/catalog-ui/src/app/_favicon.png Binary files differnew file mode 100644 index 0000000000..6e9f04df69 --- /dev/null +++ b/catalog-ui/src/app/_favicon.png diff --git a/catalog-ui/src/app/app.ts b/catalog-ui/src/app/app.ts new file mode 100644 index 0000000000..7f85bb9eb3 --- /dev/null +++ b/catalog-ui/src/app/app.ts @@ -0,0 +1,837 @@ +'use strict'; + +//import 'restangular'; +//import 'angular-ui-router'; +import "reflect-metadata"; +import 'ng-infinite-scroll'; +import './modules/filters.ts'; +import './modules/utils.ts'; +import './modules/directive-module.ts'; +import './modules/service-module'; +import './modules/view-model-module.ts'; + +import { + IUserResourceClass, + DataTypesService, + LeftPaletteLoaderService, + EcompHeaderService, + CookieService, + ConfigurationUiService, + CacheService, + IUserResource, + SdcVersionService, + ICategoryResourceClass, + EntityService +} from "./services"; +import {forwardRef} from '@angular/core'; +import {UpgradeAdapter} from '@angular/upgrade'; +import {CHANGE_COMPONENT_CSAR_VERSION_FLAG, States} from "./utils"; +import {IAppConfigurtaion, IAppMenu, IMainCategory, Resource, IHostedApplication} from "./models"; +import {ComponentFactory} from "./utils/component-factory"; +import {ModalsHandler} from "./utils/modals-handler"; +import {downgradeComponent} from "@angular/upgrade/static"; + +import {AppModule} from './ng2/app.module'; +import {PropertiesAssignmentComponent} from "./ng2/pages/properties-assignment/properties-assignment.page.component"; +import {Component} from "./models/components/component"; +import {ComponentServiceNg2} from "./ng2/services/component-services/component.service"; +import {ComponentMetadata} from "./models/component-metadata"; +import {Categories} from "./models/categories"; + + +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'; + +// Load configuration according to environment. +declare var __ENV__:string; +let sdcConfig:IAppConfigurtaion; +let sdcMenu:IAppMenu; +let pathPrefix:string = ''; +if (__ENV__ === 'dev') { + sdcConfig = require('./../../configurations/dev.js'); +} else if (__ENV__ === 'prod') { + sdcConfig = require('./../../configurations/prod.js'); + pathPrefix = 'sdc1/'; +} else { + console.log("ERROR: Environment configuration not found!"); +} +sdcMenu = require('./../../configurations/menu.js'); + +let dependentModules:Array<string> = [ + 'ui.router', + 'ui.bootstrap', + 'ui.bootstrap.tpls', + 'ngDragDrop', + 'ui-notification', + 'ngResource', + 'ngSanitize', + 'naif.base64', + 'base64', + 'uuid4', + 'checklist-model', + 'angular.filter', + 'pascalprecht.translate', + '720kb.tooltips', + 'restangular', + 'angular-clipboard', + 'angularResizable', + 'infinite-scroll', + viewModelsModuleName, + directivesModuleName, + servicesModuleName, + filtersModuleName, + utilsModuleName +]; + +// ===================== Hosted applications section ==================== +// Define here new hosted apps +let hostedApplications:Array<IHostedApplication> = [ + { + "moduleName": "dcaeApp", + "navTitle": "DCAE", + "defaultState": 'dcae.app.home', + "state": { + "name": "dcae", + "url": "/dcae", + "relativeHtmlPath": 'dcae-app/dcae-app-view.html', + "controllerName": '.DcaeAppViewModel' + } + } +]; + +// Check if module exists (in case the javascript was not loaded). +let isModuleExists = (moduleName:string):boolean => { + try { + angular.module(moduleName); + dependentModules.push(moduleName); + return true; + } catch (e) { + console.log('Module ' + moduleName + ' does not exists'); + return false; + } +}; + +// Check which hosted applications exists +_.each(hostedApplications, (hostedApp)=> { + if (isModuleExists(hostedApp.moduleName)) { + hostedApp['exists'] = true; + } +}); +// ===================== Hosted applications section ==================== + +export const ng1appModule:ng.IModule = angular.module(moduleName, dependentModules); +angular.module('sdcApp').directive('propertiesAssignment', downgradeComponent({component: PropertiesAssignmentComponent}) as angular.IDirectiveFactory); + +ng1appModule.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' + }); + + $translateProvider.useStaticFilesLoader({ + prefix: pathPrefix + 'assets/languages/', + langKey: '', + suffix: '.json?d=' + (new Date()).getTime() + }); + $translateProvider.useSanitizeValueStrategy('escaped'); + $translateProvider.preferredLanguage('en_US'); + + $httpProvider.interceptors.push('Sdc.Services.HeaderInterceptor'); + $httpProvider.interceptors.push('Sdc.Services.HttpErrorInterceptor'); + $urlRouterProvider.otherwise('welcome'); + + $stateProvider.state( + 'dashboard', { + url: '/dashboard?show&folder', + templateUrl: "./view-models/dashboard/dashboard-view.html", + controller: viewModelsModuleName + '.DashboardViewModel', + } + ); + + $stateProvider.state( + 'welcome', { + url: '/welcome', + templateUrl: "./view-models/welcome/welcome-view.html", + controller: viewModelsModuleName + '.WelcomeViewModel' + } + ); + + let componentsParam:Array<any> = ['$stateParams', 'Sdc.Services.EntityService', 'Sdc.Services.CacheService', ($stateParams:any, EntityService:EntityService, cacheService:CacheService) => { + if (cacheService.get('breadcrumbsComponents')) { + return cacheService.get('breadcrumbsComponents'); + } else { + return EntityService.getCatalog(); + } + }]; + + $stateProvider.state( + 'workspace', { + url: '/workspace/:id/:type/', + params: {'importedFile': null, 'componentCsar': null, 'resourceType': null, 'disableButtons': null}, + templateUrl: './view-models/workspace/workspace-view.html', + controller: viewModelsModuleName + '.WorkspaceViewModel', + resolve: { + injectComponent: ['$stateParams', 'ComponentFactory', 'ComponentServiceNg2', function ($stateParams, ComponentFactory:ComponentFactory, ComponentServiceNg2:ComponentServiceNg2) { + if ($stateParams.id) { + return ComponentFactory.getComponentWithMetadataFromServer($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) { + (<Resource>emptyComponent).importedFile = $stateParams.importedFile; + } + return emptyComponent; + } + }], + components: componentsParam + } + } + ); + + $stateProvider.state( + States.WORKSPACE_GENERAL, { + url: 'general', + parent: 'workspace', + controller: viewModelsModuleName + '.GeneralViewModel', + templateUrl: './view-models/workspace/tabs/general/general-view.html', + data: {unsavedChanges: false, bodyClass: 'general'} + } + ); + // + $stateProvider.state( + States.WORKSPACE_ICONS, { + url: 'icons', + parent: 'workspace', + controller: viewModelsModuleName + '.IconsViewModel', + templateUrl: './view-models/workspace/tabs/icons/icons-view.html', + data: {unsavedChanges: false, bodyClass: 'icons'} + + } + ); + + $stateProvider.state( + States.WORKSPACE_ACTIVITY_LOG, { + url: 'activity_log', + parent: 'workspace', + controller: viewModelsModuleName + '.ActivityLogViewModel', + templateUrl: './view-models/workspace/tabs/activity-log/activity-log.html', + data: {unsavedChanges: false} + } + ); + + $stateProvider.state( + States.WORKSPACE_DEPLOYMENT_ARTIFACTS, { + url: 'deployment_artifacts', + parent: 'workspace', + controller: viewModelsModuleName + '.DeploymentArtifactsViewModel', + templateUrl: './view-models/workspace/tabs/deployment-artifacts/deployment-artifacts-view.html', + data: { + bodyClass: 'deployment_artifacts' + } + } + ); + + $stateProvider.state( + States.WORKSPACE_HIERARCHY, { + url: 'hierarchy', + parent: 'workspace', + controller: viewModelsModuleName + '.ProductHierarchyViewModel', + templateUrl: './view-models/workspace/tabs/product-hierarchy/product-hierarchy-view.html', + data: {unsavedChanges: false} + + } + ); + + $stateProvider.state( + States.WORKSPACE_INFORMATION_ARTIFACTS, { + url: 'information_artifacts', + parent: 'workspace', + controller: viewModelsModuleName + '.InformationArtifactsViewModel', + templateUrl: './view-models/workspace/tabs/information-artifacts/information-artifacts-view.html', + data: { + bodyClass: 'information_artifacts' + } + } + ); + + $stateProvider.state( + States.WORKSPACE_TOSCA_ARTIFACTS, { + url: 'tosca_artifacts', + parent: 'workspace', + controller: viewModelsModuleName + '.ToscaArtifactsViewModel', + templateUrl: './view-models/workspace/tabs/tosca-artifacts/tosca-artifacts-view.html', + data: { + bodyClass: 'tosca_artifacts' + } + } + ); + + $stateProvider.state( + States.WORKSPACE_PROPERTIES, { + url: 'properties', + parent: 'workspace', + controller: viewModelsModuleName + '.PropertiesViewModel', + templateUrl: './view-models/workspace/tabs/properties/properties-view.html', + data: { + bodyClass: 'properties' + } + } + ); + + $stateProvider.state( + States.WORKSPACE_SERVICE_INPUTS, { + url: 'service_inputs', + parent: 'workspace', + controller: viewModelsModuleName + '.ServiceInputsViewModel', + templateUrl: './view-models/workspace/tabs/inputs/service-input/service-inputs-view.html', + data: { + bodyClass: 'workspace-inputs' + } + } + ); + + $stateProvider.state( + States.WORKSPACE_PROPERTIES_ASSIGNMENT, { + url: 'properties_assignment', + params: {'component': null}, + template: '<properties-assignment></properties-assignment>', + parent: 'workspace', + resolve: { + componentData: ['injectComponent', '$stateParams', function (injectComponent:Component, $stateParams) { + //injectComponent.componentService = null; // this is for not passing the service so no one will use old api and start using new api + $stateParams.component = injectComponent; + return injectComponent; + }], + }, + data: { + bodyClass: 'properties-assignment' + } + } + ); + + $stateProvider.state( + States.WORKSPACE_RESOURCE_INPUTS, { + url: 'resource_inputs', + parent: 'workspace', + controller: viewModelsModuleName + '.ResourceInputsViewModel', + templateUrl: './view-models/workspace/tabs/inputs/resource-input/resource-inputs-view.html', + data: { + bodyClass: 'workspace-inputs' + } + } + ); + + $stateProvider.state( + States.WORKSPACE_ATTRIBUTES, { + url: 'attributes', + parent: 'workspace', + controller: viewModelsModuleName + '.AttributesViewModel', + templateUrl: './view-models/workspace/tabs/attributes/attributes-view.html', + data: { + bodyClass: 'attributes' + } + } + ); + + $stateProvider.state( + States.WORKSPACE_REQUIREMENTS_AND_CAPABILITIES, { + url: 'req_and_capabilities', + parent: 'workspace', + controller: viewModelsModuleName + '.ReqAndCapabilitiesViewModel', + templateUrl: './view-models/workspace/tabs/req-and-capabilities/req-and-capabilities-view.html', + data: { + bodyClass: 'attributes' + } + } + ); + + + $stateProvider.state( + States.WORKSPACE_MANAGEMENT_WORKFLOW, { + parent: 'workspace', + url: 'management_workflow', + templateUrl: './view-models/workspace/tabs/management-workflow/management-workflow-view.html', + controller: viewModelsModuleName + '.ManagementWorkflowViewModel' + } + ); + + $stateProvider.state( + States.WORKSPACE_NETWORK_CALL_FLOW, { + parent: 'workspace', + url: 'network_call_flow', + templateUrl: './view-models/workspace/tabs/network-call-flow/network-call-flow-view.html', + controller: viewModelsModuleName + '.NetworkCallFlowViewModel' + } + ); + + $stateProvider.state( + States.WORKSPACE_DISTRIBUTION, { + parent: 'workspace', + url: 'distribution', + templateUrl: './view-models/workspace/tabs/distribution/distribution-view.html', + controller: viewModelsModuleName + '.DistributionViewModel' + } + ); + + $stateProvider.state( + States.WORKSPACE_COMPOSITION, { + url: 'composition/', + parent: 'workspace', + controller: viewModelsModuleName + '.CompositionViewModel', + templateUrl: './view-models/workspace/tabs/composition/composition-view.html', + data: { + bodyClass: 'composition' + } + } + ); + + // $stateProvider.state( + // States.WORKSPACE_NG2, { + // url: 'ng2/', + // component: downgradeComponent({component: NG2Example2Component}), //viewModelsModuleName + '.NG2Example', + // templateUrl: './ng2/view-ng2/ng2.example2/ng2.example2.component.html' + // } + // ); + + $stateProvider.state( + States.WORKSPACE_DEPLOYMENT, { + url: 'deployment/', + parent: 'workspace', + templateUrl: './view-models/workspace/tabs/deployment/deployment-view.html', + controller: viewModelsModuleName + '.DeploymentViewModel', + data: { + bodyClass: 'composition' + } + } + ); + + $stateProvider.state( + 'workspace.composition.details', { + url: 'details', + parent: 'workspace.composition', + templateUrl: './view-models/workspace/tabs/composition/tabs/details/details-view.html', + controller: viewModelsModuleName + '.DetailsViewModel' + } + ); + + $stateProvider.state( + 'workspace.composition.properties', { + url: 'properties', + parent: 'workspace.composition', + templateUrl: './view-models/workspace/tabs/composition/tabs/properties-and-attributes/properties-view.html', + controller: viewModelsModuleName + '.ResourcePropertiesViewModel' + } + ); + + $stateProvider.state( + 'workspace.composition.artifacts', { + url: 'artifacts', + parent: 'workspace.composition', + templateUrl: './view-models/workspace/tabs/composition/tabs/artifacts/artifacts-view.html', + controller: viewModelsModuleName + '.ResourceArtifactsViewModel' + } + ); + + $stateProvider.state( + 'workspace.composition.relations', { + url: 'relations', + parent: 'workspace.composition', + templateUrl: './view-models/workspace/tabs/composition/tabs/relations/relations-view.html', + controller: viewModelsModuleName + '.RelationsViewModel' + } + ); + + $stateProvider.state( + 'workspace.composition.structure', { + url: 'structure', + parent: 'workspace.composition', + templateUrl: './view-models/workspace/tabs/composition/tabs/structure/structure-view.html', + controller: viewModelsModuleName + '.StructureViewModel' + } + ); + $stateProvider.state( + 'workspace.composition.lifecycle', { + url: 'lifecycle', + parent: 'workspace.composition', + templateUrl: './view-models/workspace/tabs/composition/tabs/artifacts/artifacts-view.html', + controller: viewModelsModuleName + '.ResourceArtifactsViewModel' + } + ); + + $stateProvider.state( + 'workspace.composition.api', { + url: 'api', + parent: 'workspace.composition', + templateUrl: './view-models/workspace/tabs/composition/tabs/artifacts/artifacts-view.html', + controller: viewModelsModuleName + '.ResourceArtifactsViewModel' + } + ); + $stateProvider.state( + 'workspace.composition.deployment', { + url: 'deployment', + parent: 'workspace.composition', + templateUrl: './view-models/workspace/tabs/composition/tabs/artifacts/artifacts-view.html', + controller: viewModelsModuleName + '.ResourceArtifactsViewModel' + } + ); + + $stateProvider.state( + 'adminDashboard', { + url: '/adminDashboard', + templateUrl: './view-models/admin-dashboard/admin-dashboard-view.html', + controller: viewModelsModuleName + '.AdminDashboardViewModel', + permissions: ['ADMIN'] + } + ); + + $stateProvider.state( + 'onboardVendor', { + url: '/onboardVendor', + templateUrl: './view-models/onboard-vendor/onboard-vendor-view.html', + controller: viewModelsModuleName + '.OnboardVendorViewModel'//, + } + ); + + // Build the states for all hosted apps dynamically + _.each(hostedApplications, (hostedApp)=> { + if (hostedApp.exists) { + $stateProvider.state( + hostedApp.state.name, { + url: hostedApp.state.url, + templateUrl: './view-models/dcae-app/dcae-app-view.html', + controller: viewModelsModuleName + hostedApp.state.controllerName + } + ); + } + }); + + $stateProvider.state( + 'catalog', { + url: '/catalog', + templateUrl: './view-models/catalog/catalog-view.html', + controller: viewModelsModuleName + '.CatalogViewModel', + resolve: { + auth: ["$q", "Sdc.Services.UserResourceService", ($q:any, userResourceService:IUserResourceClass) => { + let userInfo:IUserResource = userResourceService.getLoggedinUser(); + if (userInfo) { + return $q.when(userInfo); + } else { + return $q.reject({authenticated: false}); + } + }] + } + } + ); + + $stateProvider.state( + 'support', { + url: '/support', + templateUrl: './view-models/support/support-view.html', + controller: viewModelsModuleName + '.SupportViewModel' + } + ); + + $stateProvider.state( + 'error-403', { + url: '/error-403', + templateUrl: "./view-models/modals/error-modal/error-403-view.html", + controller: viewModelsModuleName + '.ErrorViewModel' + } + ); + + tooltipsConfigProvider.options({ + side: 'bottom', + delay: '600', + class: 'tooltip-custom', + lazy: 0, + try: 0 + }); + + } +]); + +ng1appModule.value('ValidationPattern', /^[\s\w\&_.:-]{1,1024}$/); +ng1appModule.value('ComponentNameValidationPattern', /^(?=.*[^. ])[\s\w\&_.:-]{1,1024}$/); //DE250513 - same as ValidationPattern above, plus requirement that name not consist of dots and/or spaces alone. +ng1appModule.value('PropertyNameValidationPattern', /^[a-zA-Z0-9_:-]{1,50}$/);// DE210977 +ng1appModule.value('TagValidationPattern', /^[\s\w_.-]{1,50}$/); +ng1appModule.value('VendorValidationPattern', /^[\x20-\x21\x23-\x29\x2B-\x2E\x30-\x39\x3B\x3D\x40-\x5B\x5D-\x7B\x7D-\xFF]{1,25}$/); +ng1appModule.value('ContactIdValidationPattern', /^[\s\w-]{1,50}$/); +ng1appModule.value('UserIdValidationPattern', /^[\s\w-]{1,50}$/); +ng1appModule.value('ProjectCodeValidationPattern', /^[\s\w-]{5,50}$/); +ng1appModule.value('LabelValidationPattern', /^[\sa-zA-Z0-9+-]{1,25}$/); +ng1appModule.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})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/); +ng1appModule.value('IntegerValidationPattern', /^(([-+]?\d+)|([-+]?0x[0-9a-fA-F]+))$/); +ng1appModule.value('IntegerNoLeadingZeroValidationPattern', /^(0|[-+]?[1-9][0-9]*|[-+]?0x[0-9a-fA-F]+|[-+]?0o[0-7]+)$/); +ng1appModule.value('FloatValidationPattern', /^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?f?$/); +ng1appModule.value('NumberValidationPattern', /^((([-+]?\d+)|([-+]?0x[0-9a-fA-F]+))|([-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?))$/); +ng1appModule.value('KeyValidationPattern', /^[\s\w-]{1,50}$/); +ng1appModule.value('CommentValidationPattern', /^[\u0000-\u00BF]*$/); +ng1appModule.value('BooleanValidationPattern', /^([Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee])$/); +ng1appModule.value('MapKeyValidationPattern', /^[\w]{1,50}$/); + +ng1appModule.constant('sdcConfig', sdcConfig); +ng1appModule.constant('sdcMenu', sdcMenu); + +ng1appModule.run([ + '$http', + 'Sdc.Services.CacheService', + 'Sdc.Services.CookieService', + 'Sdc.Services.ConfigurationUiService', + 'Sdc.Services.UserResourceService', + 'Sdc.Services.CategoryResourceService', + 'Sdc.Services.SdcVersionService', + '$state', + '$rootScope', + '$location', + 'sdcMenu', + 'ModalsHandler', + 'Sdc.Services.EcompHeaderService', + 'LeftPaletteLoaderService', + 'Sdc.Services.DataTypesService', + 'AngularJSBridge', + ($http:ng.IHttpService, + cacheService:CacheService, + cookieService:CookieService, + ConfigurationUi:ConfigurationUiService, + UserResourceClass:IUserResourceClass, + categoryResourceService:ICategoryResourceClass, + sdcVersionService:SdcVersionService, + $state:ng.ui.IStateService, + $rootScope:ng.IRootScopeService, + $location:ng.ILocationService, + sdcMenu:IAppMenu, + ModalsHandler:ModalsHandler, + ecompHeaderService:EcompHeaderService, + LeftPaletteLoaderService:LeftPaletteLoaderService, + DataTypesService:DataTypesService, + AngularJSBridge):void => { + + //handle cache data - version + let initAsdcVersion:Function = ():void => { + + let onFailed = (response) => { + console.info('onFailed initAsdcVersion', response); + cacheService.set('version', 'N/A'); + }; + + let onSuccess = (version:any) => { + 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((categories: Categories):void => { + cacheService.set('serviceCategories', categories.serviceCategories); + cacheService.set('resourceCategories', categories.resourceCategories); + cacheService.set('productCategories', categories.productCategories); + }, onError); + }; + + // Add hosted applications to sdcConfig + sdcConfig.hostedApplications = hostedApplications; + + //handle http config + $http.defaults.withCredentials = true; + $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w'; + $http.defaults.headers.common[cookieService.getUserIdSuffix()] = cookieService.getUserId(); + + initAsdcVersion(); + initConfigurationUi(); + // initLeftPalette(); + DataTypesService.initDataTypes(); + + //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(CHANGE_COMPONENT_CSAR_VERSION_FLAG)) { + cacheService.remove(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: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.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/src/app/directives/capabilities-and-requirements/capabilities-requirements-list.less b/catalog-ui/src/app/directives/capabilities-and-requirements/capabilities-requirements-list.less new file mode 100644 index 0000000000..0860261b97 --- /dev/null +++ b/catalog-ui/src/app/directives/capabilities-and-requirements/capabilities-requirements-list.less @@ -0,0 +1,113 @@ +.i-sdc-designer-sidebar-capabilities-requirements { + + .b_7; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + &.description { + margin-top: 28px; + white-space: normal; + word-wrap: break-word; + } + + .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; + max-width: 265px; + + .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/src/app/directives/capabilities-and-requirements/capability/capabilities-list-directive.ts b/catalog-ui/src/app/directives/capabilities-and-requirements/capability/capabilities-list-directive.ts new file mode 100644 index 0000000000..e71478fa4d --- /dev/null +++ b/catalog-ui/src/app/directives/capabilities-and-requirements/capability/capabilities-list-directive.ts @@ -0,0 +1,40 @@ +/** + * Created by ob0695 on 5/9/2017. + */ +/** + * Created by obarda on 1/8/2017. + */ +'use strict'; +import {CapabilitiesGroup} from "app/models"; + +export interface ICapabilitiesListScope extends ng.IScope { + capabilities:CapabilitiesGroup; +} + + +export class CapabilitiesListDirective implements ng.IDirective { + + constructor() { + + } + + scope = { + capabilities: '=', + }; + + restrict = 'E'; + replace = true; + template = ():string => { + return require('./capabilities-list-view.html'); + }; + + link = (scope:ICapabilitiesListScope, element:any, $attr:any) => { + + }; + + public static factory = ()=> { + return new CapabilitiesListDirective(); + }; +} + +CapabilitiesListDirective.factory.$inject = []; diff --git a/catalog-ui/src/app/directives/capabilities-and-requirements/capability/capabilities-list-view.html b/catalog-ui/src/app/directives/capabilities-and-requirements/capability/capabilities-list-view.html new file mode 100644 index 0000000000..c86a6df785 --- /dev/null +++ b/catalog-ui/src/app/directives/capabilities-and-requirements/capability/capabilities-list-view.html @@ -0,0 +1,14 @@ +<div class="i-sdc-designer-sidebar-capabilities-requirements"> + <div class="i-sdc-designer-sidebar-section-content-item-relations-group"> + <div class="i-sdc-designer-sidebar-section-content-item-relations" + data-ng-repeat="capability in capabilities 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}} </div> + <div class="i-sdc-designer-sidebar-section-content-item-relations-details-desc" sdc-smart-tooltip> + {{capability.type}} + </div> + </div> + </div> + </div> +</div> + diff --git a/catalog-ui/src/app/directives/capabilities-and-requirements/requirement/requirements-list-directive.ts b/catalog-ui/src/app/directives/capabilities-and-requirements/requirement/requirements-list-directive.ts new file mode 100644 index 0000000000..9863b5d271 --- /dev/null +++ b/catalog-ui/src/app/directives/capabilities-and-requirements/requirement/requirements-list-directive.ts @@ -0,0 +1,71 @@ +/** + * Created by ob0695 on 5/9/2017. + */ +/** + * Created by ob0695 on 5/9/2017. + */ +/** + * Created by obarda on 1/8/2017. + */ +'use strict'; +import {RequirementsGroup, Component} from "app/models"; + +export interface IRequirementsListScope extends ng.IScope { + + requirements:RequirementsGroup; + currentComponent: Component; +} + + +export class RequirementsListDirective implements ng.IDirective { + + constructor(private $filter: ng.IFilterService) { + + } + + scope = { + requirements: '=', + component: '=' + }; + + restrict = 'E'; + replace = true; + template = ():string => { + return require('./requirements-list-view.html'); + }; + + link = (scope:IRequirementsListScope) => { + + scope.isInstanceSelected = () : boolean => { + return scope.component && scope.component.selectedInstance != undefined && scope.component.selectedInstance != null; + } + + scope.getRelation = (requirement:any):any => { + if (scope.isInstanceSelected() && scope.component.componentInstancesRelations) { + let relationItem = _.filter(scope.component.componentInstancesRelations, (relation:any) => { + return relation.fromNode === scope.component.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')(scope.component.componentInstances[_.map + (scope.component.componentInstances, "uniqueId").indexOf(relationItem[0].toNode)].name) + }; + } + } + return null; + }; + + }; + + public static factory = ($filter: ng.IFilterService)=> { + return new RequirementsListDirective($filter); + }; +} + +RequirementsListDirective.factory.$inject = ['$filter']; diff --git a/catalog-ui/src/app/directives/capabilities-and-requirements/requirement/requirements-list-view.html b/catalog-ui/src/app/directives/capabilities-and-requirements/requirement/requirements-list-view.html new file mode 100644 index 0000000000..a6487fe72a --- /dev/null +++ b/catalog-ui/src/app/directives/capabilities-and-requirements/requirement/requirements-list-view.html @@ -0,0 +1,19 @@ +<div class="i-sdc-designer-sidebar-capabilities-requirements"> + <div class="i-sdc-designer-sidebar-section-content-item-relations-group"> + <div class="i-sdc-designer-sidebar-section-content-item-relations" + data-ng-repeat="requirement in requirements 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}} </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> diff --git a/catalog-ui/src/app/directives/clicked-outside/clicked-outside-directive.ts b/catalog-ui/src/app/directives/clicked-outside/clicked-outside-directive.ts new file mode 100644 index 0000000000..009e104740 --- /dev/null +++ b/catalog-ui/src/app/directives/clicked-outside/clicked-outside-directive.ts @@ -0,0 +1,110 @@ +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/src/app/directives/custom-validation/custom-validation.ts b/catalog-ui/src/app/directives/custom-validation/custom-validation.ts new file mode 100644 index 0000000000..e35a747e1b --- /dev/null +++ b/catalog-ui/src/app/directives/custom-validation/custom-validation.ts @@ -0,0 +1,32 @@ +'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/src/app/directives/download-artifact/download-artifact.ts b/catalog-ui/src/app/directives/download-artifact/download-artifact.ts new file mode 100644 index 0000000000..7c817935cf --- /dev/null +++ b/catalog-ui/src/app/directives/download-artifact/download-artifact.ts @@ -0,0 +1,125 @@ +'use strict'; +import {IFileDownload, Component, ArtifactModel} from "app/models"; +import {EventListenerService, CacheService} from "app/services"; +import {EVENTS, FileUtils} from "app/utils"; + +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:ArtifactModel; + component:Component; + instance:boolean; + download:Function; + showLoader:boolean; + downloadIconClass:string; + updateDownloadIcon:Function; +} + +export class DownloadArtifactDirective implements ng.IDirective { + + constructor(private $window:any, private cacheService:CacheService, private EventListenerService:EventListenerService, private fileUtils:FileUtils) { + } + + scope = { + artifact: '=', + component: '=', + instance: '=', + showLoader: '=', + downloadIconClass: '@' + }; + 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(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(EVENTS.DOWNLOAD_ARTIFACT_FINISH_EVENT + scope.artifact.uniqueId); + } + }; + + + //replace the loader to download icon + scope.updateDownloadIcon = () => { + element[0].className = scope.downloadIconClass || DOWNLOAD_CSS_CLASSES.DOWNLOAD_ICON; + }; + + + initDownloadLoader(); + + scope.download = (artifact:ArtifactModel):void => { + + let onFaild = (response):void => { + console.info('onFaild', response); + removeDownloadedFileLoader(); + }; + + let onSuccess = (data: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: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(EVENTS.DOWNLOAD_ARTIFACT_FINISH_EVENT + scope.artifact.uniqueId); + } + }); + + }; + + public static factory = ($window:any, cacheService:CacheService, EventListenerService:EventListenerService, fileUtils:FileUtils)=> { + return new DownloadArtifactDirective($window, cacheService, EventListenerService, fileUtils); + }; + +} + +DownloadArtifactDirective.factory.$inject = ['$window', 'Sdc.Services.CacheService', 'EventListenerService', 'FileUtils']; diff --git a/catalog-ui/src/app/directives/ecomp-footer/ecomp-footer.html b/catalog-ui/src/app/directives/ecomp-footer/ecomp-footer.html new file mode 100644 index 0000000000..a50731443d --- /dev/null +++ b/catalog-ui/src/app/directives/ecomp-footer/ecomp-footer.html @@ -0,0 +1,8 @@ +<div class="footer-wrapper"> + <div class="divider-footer-bottom"> + <div class="footer-copyright-text"> + <p> + </p> + </div> + </div> +</div> diff --git a/catalog-ui/src/app/directives/ecomp-footer/ecomp-footer.less b/catalog-ui/src/app/directives/ecomp-footer/ecomp-footer.less new file mode 100644 index 0000000000..4c54a11fec --- /dev/null +++ b/catalog-ui/src/app/directives/ecomp-footer/ecomp-footer.less @@ -0,0 +1,47 @@ + +.footer-wrapper { + position: fixed; + bottom: 0px; + left: 0px; + width: 100%; + background-color: #222222; + color: #ffffff; + font-family: "Arial"; + font-size: 11px; + height: @footer_height; +} + +.footer-wrapper a { + color: #ffffff; + margin: 0; + padding: 0; + text-decoration: underline; +} + +.divider-footer-bottom { + margin: 0 auto; + width: 1020px; +} + +.footer-copyright-text { + display: inline-block; + width:612px; + margin-top: 18px; + margin-left: 50px; + text-align: left; + vertical-align: top; +} + +.footer-logo1 { + display: inline-block; + margin-top: 20px; + margin-left: 124px; +} + +.footer-logo2 { + margin-top: 22px; + margin-left: 9px; + display: inline-block; + font-size: 24px; + vertical-align: top; +} diff --git a/catalog-ui/src/app/directives/ecomp-footer/ecomp-footer.ts b/catalog-ui/src/app/directives/ecomp-footer/ecomp-footer.ts new file mode 100644 index 0000000000..f104926fc6 --- /dev/null +++ b/catalog-ui/src/app/directives/ecomp-footer/ecomp-footer.ts @@ -0,0 +1,33 @@ +'use strict'; +import {IAppConfigurtaion} from "app/models"; + +export interface IEcompFooterDirectiveScope extends ng.IScope { + +} + +export class EcompFooterDirective implements ng.IDirective { + + constructor(private sdcConfig:IAppConfigurtaion) { + + } + + public replace = true; + public restrict = 'E'; + + public scope = {}; + + template = ():string => { + return require('./ecomp-footer.html'); + }; + + link = (scope:IEcompFooterDirectiveScope, $elem:JQuery, attr:any) => { + + }; + + public static factory = (sdcConfig:IAppConfigurtaion)=> { + return new EcompFooterDirective(sdcConfig); + }; + +} + +EcompFooterDirective.factory.$inject = ['sdcConfig']; diff --git a/catalog-ui/src/app/directives/ecomp-header/ecomp-header.html b/catalog-ui/src/app/directives/ecomp-header/ecomp-header.html new file mode 100644 index 0000000000..67799420f4 --- /dev/null +++ b/catalog-ui/src/app/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" translate="PROJECT_TITLE"><span class="sdc-ecomp-logo"></span></a> + <a class="sdc-ecomp-header-title" data-ng-if="clickableLogo==='false'"><span class="sdc-ecomp-logo" translate="PROJECT_TITLE"></span></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/src/app/directives/ecomp-header/ecomp-header.less b/catalog-ui/src/app/directives/ecomp-header/ecomp-header.less new file mode 100644 index 0000000000..8fa80c4237 --- /dev/null +++ b/catalog-ui/src/app/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("/assets/styles/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/src/app/directives/ecomp-header/ecomp-header.ts b/catalog-ui/src/app/directives/ecomp-header/ecomp-header.ts new file mode 100644 index 0000000000..ba96e80484 --- /dev/null +++ b/catalog-ui/src/app/directives/ecomp-header/ecomp-header.ts @@ -0,0 +1,217 @@ +'use strict'; +import {IAppConfigurtaion, User, IUser} from "app/models"; +import {IUserResourceClass, IUserResource} from "app/services"; + +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:IUser; +} + +export class EcompHeaderDirective implements ng.IDirective { + + constructor(private $http:ng.IHttpService, + private sdcConfig:IAppConfigurtaion, + private UserResourceClass:IUserResourceClass) { + + } + + scope = { + menuData: '=', + version: '@', + clickableLogo: '@?' + }; + + public replace = true; + public restrict = 'E'; + public controller = EcompHeaderController; + + template = ():string => { + return 'src/app/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: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 User(user); + }); + } else { + $scope.user = new 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 = ($http:ng.IHttpService, + sdcConfig:IAppConfigurtaion, + UserResourceClass:IUserResourceClass)=> { + return new EcompHeaderDirective($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 = ['$http', 'sdcConfig', 'Sdc.Services.UserResourceService']; + + + + + diff --git a/catalog-ui/src/app/directives/edit-name-popover/edit-module-name-popover.html b/catalog-ui/src/app/directives/edit-name-popover/edit-module-name-popover.html new file mode 100644 index 0000000000..d90c52d9a6 --- /dev/null +++ b/catalog-ui/src/app/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/src/app/directives/edit-name-popover/edit-name-popover-directive.ts b/catalog-ui/src/app/directives/edit-name-popover/edit-name-popover-directive.ts new file mode 100644 index 0000000000..b99f11fcf9 --- /dev/null +++ b/catalog-ui/src/app/directives/edit-name-popover/edit-name-popover-directive.ts @@ -0,0 +1,77 @@ +'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 ValidationPattern:RegExp,private $templateCache:ng.ITemplateCacheService) { + } + + scope = { + direction: "@?", + module: "=", + header: "@?", + onSave: "&" + }; + + link = (scope:IEditNamePopoverDirectiveScope) => { + if (!scope.direction) { + scope.direction = 'top'; + } + + scope.originalName = ''; + this.$templateCache.put("edit-module-name-popover.html", require('./edit-module-name-popover.html')); + scope.templateUrl = "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 require('./edit-name-popover-view.html'); + }; + + public static factory = (ValidationPattern:RegExp,$templateCache:ng.ITemplateCacheService)=> { + return new EditNamePopoverDirective(ValidationPattern,$templateCache); + } +} + +EditNamePopoverDirective.factory.$inject = ['ValidationPattern','$templateCache']; diff --git a/catalog-ui/src/app/directives/edit-name-popover/edit-name-popover-view.html b/catalog-ui/src/app/directives/edit-name-popover/edit-name-popover-view.html new file mode 100644 index 0000000000..23340ccee2 --- /dev/null +++ b/catalog-ui/src/app/directives/edit-name-popover/edit-name-popover-view.html @@ -0,0 +1 @@ +<div uib-popover-template="templateUrl" popover-class="edit-name-popover" popover-title="{{header}}" popover-placement="{{direction}}" popover-is-open="isOpen" popover-append-to-body="true"></div> diff --git a/catalog-ui/src/app/directives/edit-name-popover/edit-name-popover.less b/catalog-ui/src/app/directives/edit-name-popover/edit-name-popover.less new file mode 100644 index 0000000000..55dcd80a27 --- /dev/null +++ b/catalog-ui/src/app/directives/edit-name-popover/edit-name-popover.less @@ -0,0 +1,74 @@ +.edit-name-popover{ + max-width: none; + width: 310px; + left: initial !important; + right: 10px; + z-index: 100; + + .arrow { + left: 95% !important; + border-width: 7px; + bottom: -8px !important; + } +} + +.popover { + 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; + } + + .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/src/app/directives/elements/checkbox/checkbox.html b/catalog-ui/src/app/directives/elements/checkbox/checkbox.html new file mode 100644 index 0000000000..1e25408188 --- /dev/null +++ b/catalog-ui/src/app/directives/elements/checkbox/checkbox.html @@ -0,0 +1,14 @@ +<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" + checklist-change="sdcChecklistChange()" + /> + + <span sdc-smart-tooltip class="tlv-checkbox-label" data-tests-id="{{elemId}}">{{text}}</span> +</label> diff --git a/catalog-ui/src/app/directives/elements/checkbox/checkbox.less b/catalog-ui/src/app/directives/elements/checkbox/checkbox.less new file mode 100644 index 0000000000..0747a680a9 --- /dev/null +++ b/catalog-ui/src/app/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/src/app/directives/elements/checkbox/checkbox.ts b/catalog-ui/src/app/directives/elements/checkbox/checkbox.ts new file mode 100644 index 0000000000..ec0be8ab07 --- /dev/null +++ b/catalog-ui/src/app/directives/elements/checkbox/checkbox.ts @@ -0,0 +1,43 @@ +'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 $filter:ng.IFilterService) { + } + + public replace = true; + public restrict = 'E'; + public transclude = false; + + scope = { + elemId: '@', + text: '@', + disabled: '=', + sdcChecklistModel: '=', + sdcChecklistValue: '=', + sdcChecklistChange: '&' + }; + + template = ():string => { + return require('./checkbox.html'); + }; + + public link = (scope:ICheckboxElementScope, $elem:ng.IAugmentedJQuery, $attrs:angular.IAttributes) => { + + }; + + public static factory = ($filter:ng.IFilterService)=> { + return new CheckboxElementDirective($filter); + }; + +} + +CheckboxElementDirective.factory.$inject = ['$filter']; diff --git a/catalog-ui/src/app/directives/elements/radiobutton/radiobutton.html b/catalog-ui/src/app/directives/elements/radiobutton/radiobutton.html new file mode 100644 index 0000000000..b31fae5d73 --- /dev/null +++ b/catalog-ui/src/app/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/src/app/directives/elements/radiobutton/radiobutton.less b/catalog-ui/src/app/directives/elements/radiobutton/radiobutton.less new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/catalog-ui/src/app/directives/elements/radiobutton/radiobutton.less diff --git a/catalog-ui/src/app/directives/elements/radiobutton/radiobutton.ts b/catalog-ui/src/app/directives/elements/radiobutton/radiobutton.ts new file mode 100644 index 0000000000..1384a82674 --- /dev/null +++ b/catalog-ui/src/app/directives/elements/radiobutton/radiobutton.ts @@ -0,0 +1,48 @@ +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 $filter:ng.IFilterService) { + } + + public replace = true; + public restrict = 'E'; + public transclude = false; + + scope = { + elemId: '@', + elemName: '@', + text: '@', + sdcModel: '=', + value: '@', + disabled: '=', + onValueChange: '&' + }; + + template = ():string => { + return require('./radiobutton.html'); + }; + + public link = (scope:IRadiobuttonElementScope, $elem:ng.IAugmentedJQuery, $attrs:angular.IAttributes) => { + //$elem.removeAttr("id") + //console.log(scope.sdcChecklistValue); + }; + + public static factory = ($filter:ng.IFilterService)=> { + return new RadiobuttonElementDirective($filter); + }; + +} + +RadiobuttonElementDirective.factory.$inject = ['$filter']; diff --git a/catalog-ui/src/app/directives/ellipsis/ellipsis-directive.html b/catalog-ui/src/app/directives/ellipsis/ellipsis-directive.html new file mode 100644 index 0000000000..31fa06adda --- /dev/null +++ b/catalog-ui/src/app/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/src/app/directives/ellipsis/ellipsis-directive.less b/catalog-ui/src/app/directives/ellipsis/ellipsis-directive.less new file mode 100644 index 0000000000..d8dfdbb73b --- /dev/null +++ b/catalog-ui/src/app/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/src/app/directives/ellipsis/ellipsis-directive.ts b/catalog-ui/src/app/directives/ellipsis/ellipsis-directive.ts new file mode 100644 index 0000000000..8fbbd4d486 --- /dev/null +++ b/catalog-ui/src/app/directives/ellipsis/ellipsis-directive.ts @@ -0,0 +1,57 @@ +'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() { + } + + scope = { + ellipsis: '=', + moreClass: '@', + maxChars: '=' + }; + + replace = false; + restrict = 'A'; + template = ():string => { + return require('./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 = ()=> { + return new EllipsisDirective(); + }; + +} + +EllipsisDirective.factory.$inject = []; diff --git a/catalog-ui/src/app/directives/events/on-last-repeat/on-last-repeat.ts b/catalog-ui/src/app/directives/events/on-last-repeat/on-last-repeat.ts new file mode 100644 index 0000000000..84014159f0 --- /dev/null +++ b/catalog-ui/src/app/directives/events/on-last-repeat/on-last-repeat.ts @@ -0,0 +1,40 @@ +'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/src/app/directives/export-json-to-excel/export-json-to-excel.html b/catalog-ui/src/app/directives/export-json-to-excel/export-json-to-excel.html new file mode 100644 index 0000000000..55696b33fa --- /dev/null +++ b/catalog-ui/src/app/directives/export-json-to-excel/export-json-to-excel.html @@ -0,0 +1,7 @@ +<span data-ng-click="export()" + class="sprite-new download-btn hand" + uib-tooltip="Download" + tooltip-class="uib-custom-tooltip" + tooltip-placement="bottom" + data-tests-id="download-excel"></span> + diff --git a/catalog-ui/src/app/directives/export-json-to-excel/export-json-to-excel.ts b/catalog-ui/src/app/directives/export-json-to-excel/export-json-to-excel.ts new file mode 100644 index 0000000000..deb1c94936 --- /dev/null +++ b/catalog-ui/src/app/directives/export-json-to-excel/export-json-to-excel.ts @@ -0,0 +1,61 @@ +'use strict'; +import {FileUtils} from "app/utils"; + +export interface IJsonExportExcelScope extends ng.IScope { + initExportExcelData:Function; + export:()=>void; +} + +export class JsonExportExcelDirective implements ng.IDirective { + + constructor(private fileUtils:FileUtils) { + } + + scope = { + initExportExcelData: '&'//get function that init and returns Models.ExportExcel + }; + + public restrict = 'E'; + replace = true; + template = ():string => { + return require('./export-json-to-excel.html'); + }; + + private jsonToTableText = (headers:string, dataObj:Array<Object>):string=> { + let tableText = headers + "\n"; + _.each(dataObj, (rowData:any) => { + tableText += _.values(rowData).join() + "\n"; + }); + return tableText; + }; + + link = (scope:IJsonExportExcelScope, $elem:any) => { + scope.export = ():void => { + let exportExcelData = scope.initExportExcelData(); + exportExcelData.fileName = !!exportExcelData.fileName ? exportExcelData.fileName : 'export-excel'; + + let headers = exportExcelData.tableHeaders.join(","); + let tableData = ""; + if (exportExcelData.groupByField) { + _.each(_.groupBy(exportExcelData.dataObj, exportExcelData.groupByField), (groupData:Array<Object>) => { + tableData += this.jsonToTableText(headers, groupData); + tableData += '\n'; + }); + } else { + tableData = this.jsonToTableText(headers, exportExcelData.dataObj); + } + + let blob = new Blob([exportExcelData.metaData.join('\n\n') + '\n\n' + tableData], {type: "text/csv;charset=utf-8"}); + + return this.fileUtils.downloadFile(blob, exportExcelData.fileName + '.csv'); + }; + + }; + + public static factory = (fileUtils:FileUtils)=> { + return new JsonExportExcelDirective( fileUtils); + }; + +} + +JsonExportExcelDirective.factory.$inject = ['FileUtils']; diff --git a/catalog-ui/src/app/directives/file-opener/file-opener.html b/catalog-ui/src/app/directives/file-opener/file-opener.html new file mode 100644 index 0000000000..38f82554e9 --- /dev/null +++ b/catalog-ui/src/app/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/src/app/directives/file-opener/file-opener.ts b/catalog-ui/src/app/directives/file-opener/file-opener.ts new file mode 100644 index 0000000000..95e43262f5 --- /dev/null +++ b/catalog-ui/src/app/directives/file-opener/file-opener.ts @@ -0,0 +1,54 @@ +'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 $compile:ng.ICompileService) { + } + + scope = { + onFileUpload: '&', + testsId: '@', + extensions: '@' + }; + + restrict = 'AE'; + replace = true; + template = ():string => { + return require('./file-opener.html'); + }; + + link = (scope:IFileOpenerScope, element:any) => { + + scope.onFileSelect = () => { + scope.onFileUpload({file: scope.importFile}); + element.html('app/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 = ($compile:ng.ICompileService)=> { + return new FileOpenerDirective($compile); + }; + +} + +FileOpenerDirective.factory.$inject = ['$compile']; diff --git a/catalog-ui/src/app/directives/file-type/file-type.ts b/catalog-ui/src/app/directives/file-type/file-type.ts new file mode 100644 index 0000000000..11795724d0 --- /dev/null +++ b/catalog-ui/src/app/directives/file-type/file-type.ts @@ -0,0 +1,45 @@ +'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/src/app/directives/file-upload/file-upload.html b/catalog-ui/src/app/directives/file-upload/file-upload.html new file mode 100644 index 0000000000..a69340d0ba --- /dev/null +++ b/catalog-ui/src/app/directives/file-upload/file-upload.html @@ -0,0 +1,23 @@ +<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)" + on-after-validate="onAfterValidate" + 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/src/app/directives/file-upload/file-upload.less b/catalog-ui/src/app/directives/file-upload/file-upload.less new file mode 100644 index 0000000000..1c4b010853 --- /dev/null +++ b/catalog-ui/src/app/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/src/app/directives/file-upload/file-upload.ts b/catalog-ui/src/app/directives/file-upload/file-upload.ts new file mode 100644 index 0000000000..63a6f08062 --- /dev/null +++ b/catalog-ui/src/app/directives/file-upload/file-upload.ts @@ -0,0 +1,130 @@ +/** + * Created by obarda on 1/27/2016. + */ +'use strict'; +import {IAppConfigurtaion} from "app/models"; + +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; + onAfterValidate():void; + setEmptyError(element):void; + validateField(field:any):boolean; + cancel():void; +} + + +export class FileUploadDirective implements ng.IDirective { + + constructor(private sdcConfig:IAppConfigurtaion) { + } + + scope = { + fileModel: '=', + formElement: '=', + extensions: '@', + elementDisabled: '@', + elementName: '@', + elementRequired: '@', + onFileChangedInDirective: '=?', + defaultText: '=', + }; + + restrict = 'E'; + replace = true; + template = ():string => { + return require('./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; + } + + }; + + // Prevent case-sensitivity in the upload-file accept parameter + // Workaround for github issue: https://github.com/adonespitogo/angular-base64-upload/issues/81 + scope.onAfterValidate = () => { + if (!scope.formElement[scope.elementName].$valid && scope.extensions) { + let uploadfileExtension:string = scope.fileModel.filename.split('.').pop().toLowerCase(); + if (scope.extensions.split(',').indexOf(uploadfileExtension) > -1) { + scope.formElement[scope.elementName].$setValidity('accept', true); + } + } + // Adding fix for cases when we're changing file type for upload from file that requires certain + // extensions to a file that don't requires any extensions + if (!scope.formElement[scope.elementName].$valid && scope.formElement[scope.elementName].$error.accept && scope.extensions === "") { + scope.formElement[scope.elementName].$setValidity('accept', true); + } + }; + + // 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 = (sdcConfig:IAppConfigurtaion)=> { + return new FileUploadDirective(sdcConfig); + }; + +} + +FileUploadDirective.factory.$inject = [ 'sdcConfig']; diff --git a/catalog-ui/src/app/directives/graphs-v2/asset-popover/asset-popover.html b/catalog-ui/src/app/directives/graphs-v2/asset-popover/asset-popover.html new file mode 100644 index 0000000000..659ff7014f --- /dev/null +++ b/catalog-ui/src/app/directives/graphs-v2/asset-popover/asset-popover.html @@ -0,0 +1,11 @@ +<div class="assetPopover" ng-class="assetPopoverObj.menuSide" ng-style="{left: assetPopoverObj.menuPosition.x, top: assetPopoverObj.menuPosition.y}"> + <div class="display-name-tooltip" >{{assetPopoverObj.displayName}}</div> + + <div class="assetMenu"> + <!--<div class="sprite-new expand-asset-icon" uib-tooltip="Open" tooltip-class="uib-custom-tooltip" tooltip-placement="{{tooltipSide}}"></div>--> + <div class="sprite-new view-info-icon" uib-tooltip="Information" tooltip-class="uib-custom-tooltip" tooltip-placement="{{assetPopoverObj.menuSide}}"></div> + <div class="sprite-new cp-icon" uib-tooltip="Connection Points" tooltip-class="uib-custom-tooltip" tooltip-placement="{{assetPopoverObj.menuSide}}"></div> + <div class="sprite-new vl-icon" uib-tooltip="Links" tooltip-class="uib-custom-tooltip" tooltip-placement="{{assetPopoverObj.menuSide}}"></div> + <div class="sprite-new trash-icon" uib-tooltip="Delete" tooltip-class="uib-custom-tooltip" tooltip-placement="{{assetPopoverObj.menuSide}}" ng-click="deleteAsset()" data-ng-class="{'disabled-icon': assetPopoverObj.isViewOnly}"></div> + </div> +</div> diff --git a/catalog-ui/src/app/directives/graphs-v2/asset-popover/asset-popover.less b/catalog-ui/src/app/directives/graphs-v2/asset-popover/asset-popover.less new file mode 100644 index 0000000000..44de4dfed1 --- /dev/null +++ b/catalog-ui/src/app/directives/graphs-v2/asset-popover/asset-popover.less @@ -0,0 +1,64 @@ +.assetPopover { + font-family: omnes-regular,sans-serif; + font-size: 13px; + width:230px; + padding:0 15px; + position:absolute; + display:flex; + flex-direction:column; + align-items:flex-start; + + &.left { + align-items:flex-end; + + .uib-custom-tooltip { + margin-left:-10px; + } + } + + .display-name-tooltip { + + border:solid 1px @main_color_p; + color: @main_color_p; + padding:5px 10px; + width:200px; + margin-bottom:10px; + border-radius: 2px; + background-color: rgba(80, 99, 113, 0.8); + box-shadow: 0px 3px 7.44px 0.56px rgba(0, 0, 0, 0.33); + } + + .uib-custom-tooltip { + margin-left:20px; + font-family: omnes-regular,sans-serif; + font-size: 13px; + } + + .assetMenu { + + border-radius: 2px; + border: solid 1px @main_color_p; + background-color: rgba(234, 234, 234, 0.7); + box-shadow: 0px 3px 7.44px 0.56px rgba(0, 0, 0, 0.33); + display:flex; + flex-direction: column; + justify-content: center; + align-items:center; + + .sprite-new { + border-bottom:solid 1px #CCC; + &:hover:not(.disabled-icon) { + .hand; + } + &:active:not(.disabled-icon) { + background-color: @main_color_a; + border-bottom-color: @main_color_a; + } + &.trash-icon { + border-bottom: none; + } + } + + + } +} diff --git a/catalog-ui/src/app/directives/graphs-v2/asset-popover/asset-popover.ts b/catalog-ui/src/app/directives/graphs-v2/asset-popover/asset-popover.ts new file mode 100644 index 0000000000..c560161d6e --- /dev/null +++ b/catalog-ui/src/app/directives/graphs-v2/asset-popover/asset-popover.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========================================================= + */ + +'use strict'; +import {AssetPopoverObj} from "app/models"; + +export interface IAssetPopoverScope extends ng.IScope { + assetPopoverObj:AssetPopoverObj; + deleteAsset:Function; +} + +export class AssetPopoverDirective implements ng.IDirective { + + constructor() { + } + + scope = { + assetPopoverObj: '=', + deleteAsset: '&' + }; + + restrict = 'E'; + replace = true; + template = ():string => { + return require('app/directives/graphs-v2/asset-popover/asset-popover.html'); + }; + + link = (scope:IAssetPopoverScope, element:JQuery, $attr:ng.IAttributes) => { + + }; + + public static factory = ()=> { + return new AssetPopoverDirective(); + }; +} + +AssetPopoverDirective.factory.$inject = []; + diff --git a/catalog-ui/src/app/directives/graphs-v2/common/common-graph-utils.ts b/catalog-ui/src/app/directives/graphs-v2/common/common-graph-utils.ts new file mode 100644 index 0000000000..0b02173e9a --- /dev/null +++ b/catalog-ui/src/app/directives/graphs-v2/common/common-graph-utils.ts @@ -0,0 +1,372 @@ +import {CommonNodeBase, CompositionCiLinkBase, RelationshipModel, Relationship, CompositionCiNodeBase, NodesFactory, LinksFactory} from "app/models"; +import {GraphUIObjects} from "app/utils"; +/** + * Created by obarda on 12/21/2016. + */ +export class CommonGraphUtils { + + constructor(private NodesFactory:NodesFactory, private LinksFactory: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:CommonNodeBase, position?:Cy.Position):Cy.CollectionElements { + + let 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: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: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<RelationshipModel>) { + + if (instancesRelations) { + _.forEach(instancesRelations, (relationshipModel:RelationshipModel) => { + _.forEach(relationshipModel.relationships, (relationship: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; + } + + /** + * Generic function that can be used for any html elements overlaid on canvas + * Returns the html position of a node on canvas, including left palette and header offsets. Option to pass in additional offset to add to return position. + * @param node + * @param additionalOffset + * @returns {Cy.Position} + + public getNodePositionWithOffset = (node:Cy.CollectionFirstNode, additionalOffset?:Cy.Position): Cy.Position => { + if(!additionalOffset) additionalOffset = {x: 0, y:0}; + + let nodePosition = node.renderedPosition(); + let posWithOffset:Cy.Position = { + x: nodePosition.x + GraphUIObjects.DIAGRAM_PALETTE_WIDTH_OFFSET + additionalOffset.x, + y: nodePosition.y + GraphUIObjects.COMPOSITION_HEADER_OFFSET + additionalOffset.y + }; + return posWithOffset; + };*/ + + /** + * 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<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/src/app/directives/graphs-v2/common/style/component-instances-nodes-style.ts b/catalog-ui/src/app/directives/graphs-v2/common/style/component-instances-nodes-style.ts new file mode 100644 index 0000000000..971dabafe8 --- /dev/null +++ b/catalog-ui/src/app/directives/graphs-v2/common/style/component-instances-nodes-style.ts @@ -0,0 +1,256 @@ +import {GraphColors} from "app/utils/constants"; +/** + * Created by obarda on 12/18/2016. + */ +export class ComponentInstanceNodesStyle { + + public static 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': 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': 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': 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': 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': GraphColors.NODE_BACKGROUND_COLOR, + 'overlay-opacity': 0 + } + }, + { + selector: '.ucpe-cp', + css: { + 'background-color': 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': 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': 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': 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": GraphColors.NODE_SELECTED_BORDER_COLOR, + 'shape': 'rectangle' + } + }, + { + selector: 'edge:selected', + css: { + 'line-color': GraphColors.ACTIVE_LINK + + } + }, + { + selector: 'edge:active', + css: { + 'overlay-opacity': 0 + } + } + ] + } + + public static getBasicNodeHanlde = () => { + return { + positionX: "center", + positionY: "top", + offsetX: 15, + offsetY: -20, + color: "#27a337", + type: "default", + single: false, + nodeTypeNames: ["basic-node"], + imageUrl: '/assets/styles/images/resource-icons/' + 'canvasPlusIcon.png', + lineWidth: 2, + lineStyle: 'dashed' + + } + } + + public static getBasicSmallNodeHandle = () => { + return { + positionX: "center", + positionY: "top", + offsetX: 3, + offsetY: -25, + color: "#27a337", + type: "default", + single: false, + nodeTypeNames: ["basic-small-node"], + imageUrl: '/assets/styles/images/resource-icons/' + 'canvasPlusIcon.png', + lineWidth: 2, + lineStyle: 'dashed' + } + } + + public static getUcpeCpNodeHandle = () => { + return { + positionX: "center", + positionY: "center", + offsetX: -8, + offsetY: -10, + color: "#27a337", + type: "default", + single: false, + nodeTypeNames: ["ucpe-cp-node"], + imageUrl: '/assets/styles/images/resource-icons/' + 'canvasPlusIcon.png', + lineWidth: 2, + lineStyle: 'dashed' + } + } +} diff --git a/catalog-ui/src/app/directives/graphs-v2/common/style/module-node-style.ts b/catalog-ui/src/app/directives/graphs-v2/common/style/module-node-style.ts new file mode 100644 index 0000000000..0c92c90538 --- /dev/null +++ b/catalog-ui/src/app/directives/graphs-v2/common/style/module-node-style.ts @@ -0,0 +1,83 @@ +import {GraphColors} from "app/utils"; +export class ModulesNodesStyle { + + public static 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": 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': GraphColors.BASE_LINK, + } + }, + { + selector: '.vl-link:selected', + css: { + 'line-color': GraphColors.VL_LINK, + } + }, + { + selector: '.cy-expand-collapse-collapsed-node:selected', + css: { + "border-color": GraphColors.NODE_SELECTED_BORDER_COLOR, + 'border-opacity': 1, + 'border-style': 'solid', + 'border-width': 2 + } + }, + { + selector: '.module-node:selected', + css: { + "border-color": GraphColors.NODE_SELECTED_BORDER_COLOR, + 'border-opacity': 1 + } + }, + { + selector: '.dummy-node', + css: { + 'width': 20, + 'height': 20 + } + }, + ] + } +} diff --git a/catalog-ui/src/app/directives/graphs-v2/composition-graph/composition-graph.directive.ts b/catalog-ui/src/app/directives/graphs-v2/composition-graph/composition-graph.directive.ts new file mode 100644 index 0000000000..db03aa53fb --- /dev/null +++ b/catalog-ui/src/app/directives/graphs-v2/composition-graph/composition-graph.directive.ts @@ -0,0 +1,539 @@ +import { + MatchBase, + LinkMenu, + ComponentInstance, + LeftPaletteComponent, + Component, + RelationMenuDirectiveObj, + CompositionCiNodeBase, + CompositionCiNodeVl, + NodesFactory/*, + AssetPopoverObj*/ +} from "app/models"; +import {ComponentInstanceFactory, ComponentFactory, GRAPH_EVENTS, GraphColors} from "app/utils"; +import {EventListenerService, LoaderService} from "app/services"; +import {CompositionGraphLinkUtils} from "./utils/composition-graph-links-utils"; +import {CompositionGraphGeneralUtils} from "./utils/composition-graph-general-utils"; +import {CompositionGraphNodesUtils} from "./utils/composition-graph-nodes-utils"; +import {CommonGraphUtils} from "../common/common-graph-utils"; +import {MatchCapabilitiesRequirementsUtils} from "./utils/match-capability-requierment-utils"; +import {CompositionGraphPaletteUtils} from "./utils/composition-graph-palette-utils"; +import {ComponentInstanceNodesStyle} from "../common/style/component-instances-nodes-style"; +import {CytoscapeEdgeEditation} from 'third-party/cytoscape.js-edge-editation/CytoscapeEdgeEditation.js'; +import {ComponentServiceNg2} from "../../../ng2/services/component-services/component.service"; +import {ComponentGenericResponse} from "../../../ng2/services/responses/component-generic-response"; + +interface ICompositionGraphScope extends ng.IScope { + + component:Component; + isLoading: boolean; + isViewOnly:boolean; + // Link menu - create link menu + relationMenuDirectiveObj:RelationMenuDirectiveObj; + isLinkMenuOpen:boolean; + createLinkFromMenu:(chosenMatch:MatchBase, vl:Component)=>void; + + //modify link menu - for now only delete menu + relationMenuTimeout:ng.IPromise<any>; + linkMenuObject: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(); + /*//asset popover menu + assetPopoverObj:AssetPopoverObj; + assetPopoverOpen:boolean; + hideAssetPopover():void; + deleteNode(nodeId:string):void;*/ +} + +export class CompositionGraph implements ng.IDirective { + private _cy:Cy.Instance; + private _currentlyCLickedNodePosition:Cy.Position; + // private $document:JQuery = $(document); + private dragElement:JQuery; + private dragComponent:ComponentInstance; + + constructor(private $q:ng.IQService, + private $log:ng.ILogService, + private $timeout:ng.ITimeoutService, + private NodesFactory:NodesFactory, + private CompositionGraphLinkUtils:CompositionGraphLinkUtils, + private GeneralGraphUtils:CompositionGraphGeneralUtils, + private ComponentInstanceFactory:ComponentInstanceFactory, + private NodesGraphUtils:CompositionGraphNodesUtils, + private eventListenerService:EventListenerService, + private ComponentFactory:ComponentFactory, + private LoaderService:LoaderService, + private commonGraphUtils:CommonGraphUtils, + private matchCapabilitiesRequirementsUtils:MatchCapabilitiesRequirementsUtils, + private CompositionGraphPaletteUtils:CompositionGraphPaletteUtils, + private ComponentServiceNg2: ComponentServiceNg2) { + + } + + restrict = 'E'; + template = require('./composition-graph.html'); + scope = { + component: '=', + isViewOnly: '=' + }; + + link = (scope:ICompositionGraphScope, el:JQuery) => { + + this.loadGraph(scope, el); + + if(scope.component.componentInstances && scope.component.componentInstancesRelations) { + this.loadGraphData(scope); + } else { + //when we don't have the data we register to on graph load event + this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_COMPOSITION_GRAPH_DATA_LOADED, () => { + this.loadGraphData(scope); + }); + } + scope.$on('$destroy', () => { + this._cy.destroy(); + _.forEach(GRAPH_EVENTS, (event) => { + this.eventListenerService.unRegisterObserver(event); + }); + }); + + }; + + private loadGraphData = (scope:ICompositionGraphScope) => { + this.initGraphNodes(scope.component.componentInstances, scope.isViewOnly); + this.commonGraphUtils.initGraphLinks(this._cy, scope.component.componentInstancesRelations); + this.commonGraphUtils.initUcpeChildren(this._cy); + } + + private loadGraph = (scope:ICompositionGraphScope, el:JQuery) => { + + let graphEl = el.find('.sdc-composition-graph-wrapper'); + this.initGraph(graphEl, scope.isViewOnly); + 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: ComponentInstanceNodesStyle.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, (leftPaletteComponent:LeftPaletteComponent) => { + this.$log.info(`composition-graph::registerEventServiceEvents:: palette hover on component: ${leftPaletteComponent.uniqueId}`); + + let nodesData = this.NodesGraphUtils.getAllNodesData(this._cy.nodes()); + let nodesLinks = this.GeneralGraphUtils.getAllCompositionCiLinks(this._cy); + + if (this.GeneralGraphUtils.componentRequirementsAndCapabilitiesCaching.containsKey(leftPaletteComponent.uniqueId)) { + let cacheComponent = this.GeneralGraphUtils.componentRequirementsAndCapabilitiesCaching.getValue(leftPaletteComponent.uniqueId); + let filteredNodesData = this.matchCapabilitiesRequirementsUtils.findByMatchingCapabilitiesToRequirements(cacheComponent, nodesData, nodesLinks); + + this.matchCapabilitiesRequirementsUtils.highlightMatchingComponents(filteredNodesData, this._cy); + this.matchCapabilitiesRequirementsUtils.fadeNonMachingComponents(filteredNodesData, nodesData, this._cy); + + return; + } + + //----------------------- ORIT TO FIX------------------------// + + this.ComponentServiceNg2.getCapabilitiesAndRequirements(leftPaletteComponent.componentType, leftPaletteComponent.uniqueId).subscribe((response: ComponentGenericResponse) => { + + let component = this.ComponentFactory.createEmptyComponent(leftPaletteComponent.componentType); + component.uniqueId = component.uniqueId; + component.capabilities = response.capabilities; + component.requirements = response.requirements; + this.GeneralGraphUtils.componentRequirementsAndCapabilitiesCaching.setValue(leftPaletteComponent.uniqueId, component); + let filteredNodesData = this.matchCapabilitiesRequirementsUtils.findByMatchingCapabilitiesToRequirements(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.CompositionGraphPaletteUtils.onComponentDrag(this._cy, event, this.dragElement, this.dragComponent); + + }); + + this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_COMPONENT_INSTANCE_NAME_CHANGED, (component:ComponentInstance) => { + + let selectedNode = this._cy.getElementById(component.uniqueId); + selectedNode.data().componentInstance.name = component.name; + selectedNode.data('name', component.name); //used for tooltip + selectedNode.data('displayName', selectedNode.data().getDisplayName()); //abbreviated + + }); + + this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_DELETE_COMPONENT_INSTANCE, (componentInstance: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 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:Component) => { + scope.component = component; + this.loadGraphData(scope); + }); + + + scope.createLinkFromMenu = (chosenMatch:MatchBase):void => { + scope.isLinkMenuOpen = false; + this.CompositionGraphLinkUtils.createLinkFromMenu(this._cy, chosenMatch, 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); + } + }; + + /* + scope.hideAssetPopover = ():void => { + + this.commonGraphUtils.safeApply(scope, () => { + scope.assetPopoverOpen = false; + scope.assetPopoverObj = null; + }); + }; + + scope.deleteNode = (nodeId:string):void => { + if (!scope.isViewOnly) { + this.NodesGraphUtils.confirmDeleteNode(nodeId, this._cy, scope.component); + //scope.hideAssetPopover(); + } + };*/ + } + + 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); + } + //scope.hideAssetPopover(); + }); + + 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': GraphColors.NODE_BACKGROUND_COLOR}); + } else { + event.cyTarget.style({'overlay-color': 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('mouseover', 'node', (event:Cy.EventObject) => { + if (!this._cy.scratch('_edge_editation_highlights')) { + this.commonGraphUtils.safeApply(scope, () => { + this.showNodePopoverMenu(scope, event.cyTarget[0]); + }); + } + }); + + this._cy.on('mouseout', 'node', (event:Cy.EventObject) => { + scope.hideAssetPopover(); + });*/ + this._cy.on('handlemouseover', (event, payload) => { + + if (payload.node.grabbed() /* || this._cy.scratch('_edge_editation_highlights') === true*/) { //no need to add opacity while we are dragging and hovering othe nodes- or if opacity was already calculated for these 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.scratch()._edge_editation_highlights = true; + scope.hideAssetPopover();*/ + }); + + this._cy.on('handlemouseout', () => { + if (this._cy.scratch('_edge_editation_highlights') === true) { + this._cy.removeScratch('_edge_editation_highlights'); + 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(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(GRAPH_EVENTS.ON_NODE_SELECTED, event.cyTarget.data().componentInstance); + //open node popover menu + //this.showNodePopoverMenu(scope, event.cyTarget[0]); + }); + } + + if (isUcpe) { + this._cy.nodes('.ucpe-cp').lock(); + event.cyTarget.style('opacity', 1); + } + + } + }); + + this._cy.on('boxselect', 'node', (event:Cy.EventObject) => { + this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_NODE_SELECTED, event.cyTarget.data().componentInstance); + }); + } + + /* + private showNodePopoverMenu = (scope:ICompositionGraphScope, node:Cy.CollectionNodes) => { + + scope.assetPopoverObj = this.NodesGraphUtils.createAssetPopover(this._cy, node, scope.isViewOnly); + scope.assetPopoverOpen = true; + + };*/ + private openModifyLinkMenu = (scope:ICompositionGraphScope, linkMenuObject:LinkMenu, timeOutInMilliseconds?:number) => { + + this.commonGraphUtils.safeApply(scope, () => { + scope.linkMenuObject = linkMenuObject; + }); + + scope.relationMenuTimeout = this.$timeout(() => { + scope.hideRelationMenu(); + }, timeOutInMilliseconds ? timeOutInMilliseconds : 6000); + }; + + private initGraphNodes(componentInstances:ComponentInstance[], isViewOnly:boolean) { + + if (!isViewOnly) { //Init nodes handle extension - enable dynamic links + setTimeout(()=> { + let handles = new CytoscapeEdgeEditation; + handles.init(this._cy, 18); + handles.registerHandle(ComponentInstanceNodesStyle.getBasicNodeHanlde()); + handles.registerHandle(ComponentInstanceNodesStyle.getBasicSmallNodeHandle()); + handles.registerHandle(ComponentInstanceNodesStyle.getUcpeCpNodeHandle()); + }, 0); + } + + _.each(componentInstances, (instance) => { + let compositionGraphNode: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.CompositionGraphPaletteUtils.addNodeFromPalette(this._cy, event, scope.component); + }; + + 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; + } + } + + public static factory = ($q, + $log, + $timeout, + NodesFactory, + LinksGraphUtils, + GeneralGraphUtils, + ComponentInstanceFactory, + NodesGraphUtils, + EventListenerService, + ComponentFactory, + LoaderService, + CommonGraphUtils, + MatchCapabilitiesRequirementsUtils, + CompositionGraphPaletteUtils, + ComponentServiceNg2) => { + return new CompositionGraph( + $q, + $log, + $timeout, + NodesFactory, + LinksGraphUtils, + GeneralGraphUtils, + ComponentInstanceFactory, + NodesGraphUtils, + EventListenerService, + ComponentFactory, + LoaderService, + CommonGraphUtils, + MatchCapabilitiesRequirementsUtils, + CompositionGraphPaletteUtils, + ComponentServiceNg2); + } +} + +CompositionGraph.factory.$inject = [ + '$q', + '$log', + '$timeout', + 'NodesFactory', + 'CompositionGraphLinkUtils', + 'CompositionGraphGeneralUtils', + 'ComponentInstanceFactory', + 'CompositionGraphNodesUtils', + 'EventListenerService', + 'ComponentFactory', + 'LoaderService', + 'CommonGraphUtils', + 'MatchCapabilitiesRequirementsUtils', + 'CompositionGraphPaletteUtils', + 'ComponentServiceNg2' +]; diff --git a/catalog-ui/src/app/directives/graphs-v2/composition-graph/composition-graph.html b/catalog-ui/src/app/directives/graphs-v2/composition-graph/composition-graph.html new file mode 100644 index 0000000000..1e69d3384a --- /dev/null +++ b/catalog-ui/src/app/directives/graphs-v2/composition-graph/composition-graph.html @@ -0,0 +1,23 @@ +<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> + Delete + </div> + +</div> + +<!--<asset-popover ng-if="assetPopoverOpen" asset-popover-obj="assetPopoverObj" delete-asset="deleteNode(assetPopoverObj.nodeId)"></asset-popover>--> diff --git a/catalog-ui/src/app/directives/graphs-v2/composition-graph/composition-graph.less b/catalog-ui/src/app/directives/graphs-v2/composition-graph/composition-graph.less new file mode 100644 index 0000000000..56c8b5529d --- /dev/null +++ b/catalog-ui/src/app/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); + } +} diff --git a/catalog-ui/src/app/directives/graphs-v2/composition-graph/utils/composition-graph-general-utils.ts b/catalog-ui/src/app/directives/graphs-v2/composition-graph/utils/composition-graph-general-utils.ts new file mode 100644 index 0000000000..1303e7a894 --- /dev/null +++ b/catalog-ui/src/app/directives/graphs-v2/composition-graph/utils/composition-graph-general-utils.ts @@ -0,0 +1,244 @@ +import {ComponentInstance, Component, MatchReqToCapability, MatchBase, CompositionCiLinkBase, CompositionCiNodeUcpeCp} from "app/models"; +import {QueueUtils, Dictionary, GraphUIObjects} from "app/utils"; +import {LoaderService} from "app/services"; +import {MatchCapabilitiesRequirementsUtils} from "./match-capability-requierment-utils"; +import {CommonGraphUtils} from "../../common/common-graph-utils"; + + +export class CompositionGraphGeneralUtils { + + public componentRequirementsAndCapabilitiesCaching = new Dictionary<string, Component>(); + protected static graphUtilsUpdateQueue:QueueUtils; + + constructor(private $q:ng.IQService, + private LoaderService:LoaderService, + private commonGraphUtils:CommonGraphUtils, + private matchCapabilitiesRequirementsUtils:MatchCapabilitiesRequirementsUtils) { + CompositionGraphGeneralUtils.graphUtilsUpdateQueue = new 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).height() < menuPosition.y + GraphUIObjects.LINK_MENU_HEIGHT + $(document.getElementsByClassName('sdc-composition-graph-wrapper')).offset().top) { // if position menu is overflow bottom + menuPosition.y = $(document.body).height() - GraphUIObjects.TOP_HEADER_HEIGHT - 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 {MatchReqToCapability} + */ + public canBeHostedOn(cy:Cy.Instance, fromUcpeInstance:ComponentInstance, toComponentInstance:ComponentInstance):MatchReqToCapability { + + let matches:Array<MatchBase> = this.matchCapabilitiesRequirementsUtils.getMatchedRequirementsCapabilities(fromUcpeInstance, toComponentInstance, this.getAllCompositionCiLinks(cy)); + let hostedOnMatch:MatchBase = _.find(matches, (match:MatchReqToCapability) => { + return match.requirement.capability.toLowerCase() === 'tosca.capabilities.container'; + }); + + return <MatchReqToCapability>hostedOnMatch; + }; + + + /** + * Checks whether node can be dropped into UCPE + * @param cy + * @param nodeToInsert + * @param ucpeNode + * @returns {boolean} + */ + private isValidDropInsideUCPE(cy:Cy.Instance, nodeToInsert:ComponentInstance, ucpeNode:ComponentInstance):boolean { + + let hostedOnMatch: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: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 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 { + let 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<CompositionCiLinkBase> => { + return _.map(cy.edges("[isSdcElement]"), (edge:Cy.CollectionEdges) => { + return edge.data(); + }); + }; + + + /** + * Get Graph Utils server queue + * @returns {QueueUtils} + */ + public getGraphUtilsServerUpdateQueue():QueueUtils { + return CompositionGraphGeneralUtils.graphUtilsUpdateQueue; + } + ; + + /** + * + * @param blockAction - true/false if this is a block action + * @param instances + * @param component + */ + public pushMultipleUpdateComponentInstancesRequestToQueue = (blockAction:boolean, instances:Array<ComponentInstance>, component: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:Component, blockAction:boolean, updatedInstance: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']; diff --git a/catalog-ui/src/app/directives/graphs-v2/composition-graph/utils/composition-graph-links-utils.ts b/catalog-ui/src/app/directives/graphs-v2/composition-graph/utils/composition-graph-links-utils.ts new file mode 100644 index 0000000000..314c761edd --- /dev/null +++ b/catalog-ui/src/app/directives/graphs-v2/composition-graph/utils/composition-graph-links-utils.ts @@ -0,0 +1,271 @@ +/** + * Created by obarda on 6/28/2016. + */ +import {GraphUIObjects, ComponentInstanceFactory, ResourceType} from "app/utils"; +import {LeftPaletteLoaderService, LoaderService} from "app/services"; +import { + NodeUcpe, + CompositionCiNodeVf, + MatchReqToCapability, + MatchBase, + MatchReqToReq, + ComponentInstance, + CompositionCiNodeBase, + RelationshipModel, + RelationMenuDirectiveObj, + CapabilitiesGroup, + LinksFactory, + NodesFactory, + RequirementsGroup, + Component, + Relationship, + Capability, + LinkMenu, + Point, + CompositionCiLinkBase +} from "app/models"; +import {CommonGraphUtils} from "../../common/common-graph-utils"; +import {CompositionGraphGeneralUtils} from "./composition-graph-general-utils"; +import {MatchCapabilitiesRequirementsUtils} from "./match-capability-requierment-utils"; + +export class CompositionGraphLinkUtils { + + constructor(private linksFactory:LinksFactory, + private loaderService:LoaderService, + private generalGraphUtils:CompositionGraphGeneralUtils, + private commonGraphUtils:CommonGraphUtils, + private matchCapabilitiesRequirementsUtils:MatchCapabilitiesRequirementsUtils) { + } + + + /** + * 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: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:CompositionCiLinkBase, cy:Cy.Instance, component:Component):void => { + + this.loaderService.showLoader('composition-graph'); + + let onSuccess:(response:RelationshipModel) => void = (relation: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') + ); + }; + + private createSimpleLink = (match:MatchReqToCapability, cy:Cy.Instance, component:Component):void => { + let newRelation:RelationshipModel = match.matchToRelationModel(); + let linkObg:CompositionCiLinkBase = this.linksFactory.createGraphLink(cy, newRelation, newRelation.relationships[0]); + this.createLink(linkObg, cy, component); + }; + + public createLinkFromMenu = (cy:Cy.Instance, chosenMatch:MatchBase, component:Component):void => { + + if (chosenMatch) { + if (chosenMatch && chosenMatch instanceof 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<MatchBase>} + */ + public filterUcpeLinks(fromNode:CompositionCiNodeBase, toNode:CompositionCiNodeBase, matchesArray:Array<MatchBase>):any { + + let matchLink:Array<MatchBase>; + + if (fromNode.isUcpePart) { + matchLink = _.filter(matchesArray, (match:MatchBase) => { + return match.isOwner(fromNode.id); + }); + } + + if (toNode.isUcpePart) { + matchLink = _.filter(matchesArray, (match: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):RelationMenuDirectiveObj { + + if (!this.commonGraphUtils.nodeLocationsCompatible(cy, fromNode, toNode)) { + return null; + } + let linkModel:Array<CompositionCiLinkBase> = this.generalGraphUtils.getAllCompositionCiLinks(cy); + + let possibleRelations:Array<MatchBase> = this.matchCapabilitiesRequirementsUtils.getMatchedRequirementsCapabilities(fromNode.data().componentInstance, + toNode.data().componentInstance, linkModel); + + //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 RelationMenuDirectiveObj(fromNode.data(), toNode.data(), 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: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:Component, cy:Cy.Instance, ucpeNode:NodeUcpe, vfNode:CompositionCiNodeVf):void => { + let hostedOnMatch:MatchReqToCapability = this.generalGraphUtils.canBeHostedOn(cy, ucpeNode.componentInstance, vfNode.componentInstance); + /* create relation */ + let newRelation = new RelationshipModel(); + newRelation.fromNode = ucpeNode.id; + newRelation.toNode = vfNode.id; + + let link: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 {Point} + */ + public calculateLinkMenuPosition(event, elementWidth, elementHeight):Point { + let point:Point = new 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 {LinkMenu} + */ + public getModifyLinkMenu(link:Cy.CollectionFirstEdge, event:Cy.EventObject):LinkMenu { + let point:Point = this.calculateLinkMenuPosition(event, GraphUIObjects.MENU_LINK_VL_WIDTH_OFFSET, GraphUIObjects.MENU_LINK_VL_HEIGHT_OFFSET); + let menu:LinkMenu = new LinkMenu(point, true, link); + return menu; + }; + +} + + +CompositionGraphLinkUtils.$inject = [ + 'LinksFactory', + 'LoaderService', + 'CompositionGraphGeneralUtils', + 'CommonGraphUtils', + 'MatchCapabilitiesRequirementsUtils' +]; diff --git a/catalog-ui/src/app/directives/graphs-v2/composition-graph/utils/composition-graph-nodes-utils.ts b/catalog-ui/src/app/directives/graphs-v2/composition-graph/utils/composition-graph-nodes-utils.ts new file mode 100644 index 0000000000..96afc8a4ea --- /dev/null +++ b/catalog-ui/src/app/directives/graphs-v2/composition-graph/utils/composition-graph-nodes-utils.ts @@ -0,0 +1,266 @@ +import {Component, NodesFactory, ComponentInstance, CompositionCiNodeVl,IAppMenu,AssetPopoverObj} from "app/models"; +import {EventListenerService, LoaderService} from "app/services"; +import {GRAPH_EVENTS,ModalsHandler,GraphUIObjects} from "app/utils"; +import {CompositionGraphGeneralUtils} from "./composition-graph-general-utils"; +import {CommonGraphUtils} from "../../common/common-graph-utils"; +/** + * Created by obarda on 11/9/2016. + */ +export class CompositionGraphNodesUtils { + constructor(private NodesFactory:NodesFactory, private $log:ng.ILogService, + private GeneralGraphUtils:CompositionGraphGeneralUtils, + private commonGraphUtils:CommonGraphUtils, + private eventListenerService:EventListenerService, + private loaderService:LoaderService /*, + private sdcMenu: IAppMenu, + private ModalsHandler: ModalsHandler*/) { + + } + + /** + * 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:Component, nodeToDelete:Cy.CollectionNodes):void { + + this.loaderService.showLoader('composition-graph'); + let onSuccess:(response:ComponentInstance) => void = (response: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(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 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') + ); + + }; + +/* + public confirmDeleteNode = (nodeId:string, cy:Cy.Instance, component:Component) => { + let node:Cy.CollectionNodes = cy.getElementById(nodeId); + let onOk = ():void => { + this.deleteNode(cy, component, node); + }; + + let componentInstance:ComponentInstance = node.data().componentInstance; + let state = "deleteInstance"; + let title:string = this.sdcMenu.alertMessages[state].title; + let message:string = this.sdcMenu.alertMessages[state].message.format([componentInstance.name]); + + this.ModalsHandler.openAlertModal(title, message).then(onOk); + };*/ + /** + * 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 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(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:Component, cy:Cy.Instance, node:Cy.CollectionNodes):Cy.CollectionNodes => { + if (node.data() instanceof CompositionCiNodeVl) { + return; + } + + let connectedVLsToDelete:Cy.CollectionNodes = cy.collection(); + _.forEach(node.neighborhood('node'), (connectedNode) => { + + //Find all neighboring nodes that are VLs + if (connectedNode.data() instanceof 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: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<ComponentInstance> = new Array<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<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(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(GRAPH_EVENTS.ON_INSERT_NODE_TO_UCPE, node, ucpeContainer, true); + } + } + /** + * Gets the position for the asset popover menu + * Then, check if right edge of menu would overlap horizontal screen edge (palette offset + canvas width - right panel) + * Then, check if bottom edge of menu would overlap the vertical end of the canvas. + * @param cy + * @param node + * @returns {Cy.Position} + + public createAssetPopover = (cy: Cy.Instance, node:Cy.CollectionFirstNode, isViewOnly:boolean):AssetPopoverObj => { + + let menuOffset:Cy.Position = { x: node.renderedWidth() / 2, y: -(node.renderedWidth() / 2) };// getNodePositionWithOffset returns central point of node. First add node.renderedWidth()/2 to get its to border. + let menuPosition:Cy.Position = this.commonGraphUtils.getNodePositionWithOffset(node, menuOffset); + let menuSide:string = 'right'; + + if(menuPosition.x + GraphUIObjects.COMPOSITION_NODE_MENU_WIDTH >= cy.width() + GraphUIObjects.DIAGRAM_PALETTE_WIDTH_OFFSET - GraphUIObjects.COMPOSITION_RIGHT_PANEL_OFFSET){ + menuPosition.x -= menuOffset.x * 2 + GraphUIObjects.COMPOSITION_NODE_MENU_WIDTH; //menu position already includes offset to the right. Therefore, subtract double offset so we have same distance from node for menu on left + menuSide = 'left'; + } + + if(menuPosition.y + GraphUIObjects.COMPOSITION_NODE_MENU_HEIGHT >= cy.height()){ + menuPosition.y = menuPosition.y - GraphUIObjects.COMPOSITION_NODE_MENU_HEIGHT - menuOffset.y * 2; + } + + return new AssetPopoverObj(node.data().id, node.data().name, menuPosition, menuSide, isViewOnly); + }; + */ + + } + + + CompositionGraphNodesUtils.$inject = ['NodesFactory', '$log', 'CompositionGraphGeneralUtils', 'CommonGraphUtils', 'EventListenerService', 'LoaderService' /*, 'sdcMenu', 'ModalsHandler'*/] + diff --git a/catalog-ui/src/app/directives/graphs-v2/composition-graph/utils/composition-graph-palette-utils.ts b/catalog-ui/src/app/directives/graphs-v2/composition-graph/utils/composition-graph-palette-utils.ts new file mode 100644 index 0000000000..83bf747501 --- /dev/null +++ b/catalog-ui/src/app/directives/graphs-v2/composition-graph/utils/composition-graph-palette-utils.ts @@ -0,0 +1,163 @@ +import {EventListenerService, LoaderService} from "app/services"; +import {CapabilitiesGroup, NodesFactory, ComponentInstance, Component, CompositionCiNodeBase, RequirementsGroup} from "app/models"; +import {ComponentFactory, ComponentInstanceFactory, GRAPH_EVENTS, GraphUIObjects} from "app/utils"; +import {CompositionGraphGeneralUtils} from "./composition-graph-general-utils"; +import {CommonGraphUtils} from "../../common/common-graph-utils"; +import 'angular-dragdrop'; +import {LeftPaletteComponent} from "../../../../models/components/displayComponent"; + +export class CompositionGraphPaletteUtils { + + constructor(private ComponentFactory:ComponentFactory, + private $filter:ng.IFilterService, + private loaderService:LoaderService, + private generalGraphUtils:CompositionGraphGeneralUtils, + private componentInstanceFactory:ComponentInstanceFactory, + private nodesFactory:NodesFactory, + private commonGraphUtils:CommonGraphUtils, + private eventListenerService:EventListenerService) { + } + + /** + * Calculate the dragged element (html element) position on canvas + * @param cy + * @param event + * @param position + * @returns {Cy.BoundingBox} + * @private + */ + private _getNodeBBox(cy:Cy.Instance, event:IDragDropEvent, position?:Cy.Position) { + let bbox = <Cy.BoundingBox>{}; + if (!position) { + position = this.commonGraphUtils.getCytoscapeNodePosition(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; + } + + /** + * Create the component instance, update data from parent component in the left palette and notify on_insert_to_ucpe if component was dragg into ucpe + * @param cy + * @param fullComponent + * @param event + * @param component + */ + private _createComponentInstanceOnGraphFromPaletteComponent(cy:Cy.Instance, fullComponent:LeftPaletteComponent, event:IDragDropEvent, component:Component) { + + let componentInstanceToCreate:ComponentInstance = this.componentInstanceFactory.createComponentInstanceFromComponent(fullComponent); + let cytoscapePosition:Cy.Position = this.commonGraphUtils.getCytoscapeNodePosition(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:ComponentInstance):void => { + + this.loaderService.hideLoader('composition-graph'); + + createInstance.name = this.$filter('resourceName')(createInstance.name); + createInstance.requirements = new RequirementsGroup(createInstance.requirements); + createInstance.capabilities = new CapabilitiesGroup(createInstance.capabilities); + createInstance.componentVersion = fullComponent.version; + createInstance.icon = fullComponent.icon; + createInstance.setInstanceRC(); + + let newNode:CompositionCiNodeBase = this.nodesFactory.createNode(createInstance); + let cyNode:Cy.CollectionNodes = this.commonGraphUtils.addComponentInstanceNodeToGraph(cy, newNode); + + //check if node was dropped into a UCPE + let ucpe:Cy.CollectionElements = this.commonGraphUtils.isInUcpe(cy, cyNode.boundingbox()); + if (ucpe.length > 0) { + this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_INSERT_NODE_TO_UCPE, cyNode, ucpe, false); + } + this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_CREATE_COMPONENT_INSTANCE); + + }; + + this.loaderService.showLoader('composition-graph'); + + // Create the component instance on server + this.generalGraphUtils.getGraphUtilsServerUpdateQueue().addBlockingUIAction(() => { + component.createComponentInstance(componentInstanceToCreate).then(onSuccessCreatingInstance, onFailedCreatingInstance); + }); + } + + /** + * Thid function applay red/green background when component dragged from palette + * @param cy + * @param event + * @param dragElement + * @param dragComponent + */ + public onComponentDrag(cy:Cy.Instance, event:IDragDropEvent, dragElement:JQuery, dragComponent:ComponentInstance) { + + if (event.clientX < GraphUIObjects.DIAGRAM_PALETTE_WIDTH_OFFSET || event.clientY < GraphUIObjects.DIAGRAM_HEADER_OFFSET) { //hovering over palette. Dont bother computing validity of drop + dragElement.removeClass('red'); + return; + } + + let offsetPosition = { + x: event.clientX - GraphUIObjects.DIAGRAM_PALETTE_WIDTH_OFFSET, + y: event.clientY - GraphUIObjects.DIAGRAM_HEADER_OFFSET + }; + let bbox = this._getNodeBBox(cy, event, offsetPosition); + + if (this.generalGraphUtils.isPaletteDropValid(cy, bbox, dragComponent)) { + dragElement.removeClass('red'); + } else { + dragElement.addClass('red'); + } + } + + /** + * This function is called when after dropping node on canvas + * Check if the capability & requirements fulfilled and if not get from server + * @param cy + * @param event + * @param component + */ + public addNodeFromPalette(cy:Cy.Instance, event:IDragDropEvent, component:Component) { + this.loaderService.showLoader('composition-graph'); + + let draggedComponent:LeftPaletteComponent = event.dataTransfer.component; + + if (this.generalGraphUtils.componentRequirementsAndCapabilitiesCaching.containsKey(draggedComponent.uniqueId)) { + let fullComponent = this.generalGraphUtils.componentRequirementsAndCapabilitiesCaching.getValue(draggedComponent.uniqueId); + draggedComponent.capabilities = fullComponent.capabilities; + draggedComponent.requirements = fullComponent.requirements; + this._createComponentInstanceOnGraphFromPaletteComponent(cy, draggedComponent, event, component); + + } else { + + this.ComponentFactory.getComponentFromServer(draggedComponent.getComponentSubType(), draggedComponent.uniqueId) + .then((fullComponent:Component) => { + draggedComponent.capabilities = fullComponent.capabilities; + draggedComponent.requirements = fullComponent.requirements; + this._createComponentInstanceOnGraphFromPaletteComponent(cy, draggedComponent, event, component); + }); + } + } +} + + +CompositionGraphPaletteUtils.$inject = [ + 'ComponentFactory', + '$filter', + 'LoaderService', + 'CompositionGraphGeneralUtils', + 'ComponentInstanceFactory', + 'NodesFactory', + 'CommonGraphUtils', + 'EventListenerService' +]; diff --git a/catalog-ui/src/app/directives/graphs-v2/composition-graph/utils/match-capability-requierment-utils.ts b/catalog-ui/src/app/directives/graphs-v2/composition-graph/utils/match-capability-requierment-utils.ts new file mode 100644 index 0000000000..0e21f033be --- /dev/null +++ b/catalog-ui/src/app/directives/graphs-v2/composition-graph/utils/match-capability-requierment-utils.ts @@ -0,0 +1,259 @@ +import {Requirement, CompositionCiLinkBase, ComponentInstance, CapabilitiesGroup, RequirementsGroup, MatchReqToCapability, MatchBase, + MatchReqToReq,CompositionCiNodeBase, Component, Capability} from "app/models"; +/** + * Created by obarda on 1/1/2017. + */ + +export class MatchCapabilitiesRequirementsUtils { + + constructor() { + } + + public static linkable(requirement1:Requirement, requirement2:Requirement, vlCapability: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<CompositionCiLinkBase>):boolean { + return _.some(links, { + 'relation': { + 'fromNode': fromNodeId, + 'relationships': [{ + 'requirementOwnerId': requirement.ownerId, + 'requirement': requirement.name, + 'relationship': { + 'type': requirement.relationship + } + } + ] + } + }); + }; + + private static isMatch(requirement:Requirement, capability: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:RequirementsGroup, + requirements2:RequirementsGroup, + capabilities:CapabilitiesGroup, + links:Array<CompositionCiLinkBase>, + fromId:string, + toId:string, + vlCapability?:Capability):Array<MatchBase> { + let matches:Array<MatchBase> = new Array<MatchBase>(); + _.forEach(requirements1, (requirementValue:Array<Requirement>, key) => { + _.forEach(requirementValue, (requirement:Requirement) => { + if (requirement.name !== "dependency" && !MatchCapabilitiesRequirementsUtils.requirementFulfilled(fromId, requirement, links)) { + _.forEach(capabilities, (capabilityValue:Array<Capability>, key) => { + _.forEach(capabilityValue, (capability:Capability) => { + if (MatchCapabilitiesRequirementsUtils.isMatch(requirement, capability)) { + let match:MatchReqToCapability = new MatchReqToCapability(requirement, capability, true, fromId, toId); + matches.push(match); + } + }); + }); + if (vlCapability) { + _.forEach(requirements2, (requirement2Value:Array<Requirement>, key) => { + _.forEach(requirement2Value, (requirement2:Requirement) => { + if (!MatchCapabilitiesRequirementsUtils.requirementFulfilled(toId, requirement2, links) && MatchCapabilitiesRequirementsUtils.linkable(requirement, requirement2, vlCapability)) { + let match:MatchReqToReq = new MatchReqToReq(requirement, requirement2, true, fromId, toId); + matches.push(match); + } + }); + }); + } + } + }); + }); + return matches; + } + + private getToFromMatches(requirements:RequirementsGroup, capabilities:CapabilitiesGroup, links:Array<CompositionCiLinkBase>, fromId:string, toId:string):Array<MatchReqToCapability> { + let matches:Array<MatchReqToCapability> = []; + _.forEach(requirements, (requirementValue:Array<Requirement>, key) => { + _.forEach(requirementValue, (requirement:Requirement) => { + if (requirement.name !== "dependency" && !MatchCapabilitiesRequirementsUtils.requirementFulfilled(toId, requirement, links)) { + _.forEach(capabilities, (capabilityValue:Array<Capability>, key) => { + _.forEach(capabilityValue, (capability:Capability) => { + if (MatchCapabilitiesRequirementsUtils.isMatch(requirement, capability)) { + let match:MatchReqToCapability = new MatchReqToCapability(requirement, capability, false, toId, fromId); + matches.push(match); + } + }); + }); + } + }); + }); + return matches; + } + + public getMatchedRequirementsCapabilities(fromComponentInstance:ComponentInstance, + toComponentInstance:ComponentInstance, + links:Array<CompositionCiLinkBase>, + vl?:Component):Array<MatchBase> {//TODO allow for VL array + let linkCapability; + if (vl) { + let linkCapabilities:Array<Capability> = vl.capabilities.findValueByKey('linkable'); + if (linkCapabilities) { + linkCapability = linkCapabilities[0]; + } + } + let fromToMatches:Array<MatchBase> = this.getFromToMatches(fromComponentInstance.requirements, + toComponentInstance.requirements, + toComponentInstance.capabilities, + links, + fromComponentInstance.uniqueId, + toComponentInstance.uniqueId, + linkCapability); + let toFromMatches:Array<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:Component, + nodeDataArray:Array<CompositionCiNodeBase>, + links:Array<CompositionCiLinkBase>, + vl?: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: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<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 = []; diff --git a/catalog-ui/src/app/directives/graphs-v2/deployment-graph/deployment-graph.directive.ts b/catalog-ui/src/app/directives/graphs-v2/deployment-graph/deployment-graph.directive.ts new file mode 100644 index 0000000000..5ad6570013 --- /dev/null +++ b/catalog-ui/src/app/directives/graphs-v2/deployment-graph/deployment-graph.directive.ts @@ -0,0 +1,122 @@ +import {Component, Module, NodesFactory, ComponentInstance} from "app/models"; +import {ComponentInstanceFactory} from "app/utils"; +import {DeploymentGraphGeneralUtils} from "./deployment-utils/deployment-graph-general-utils"; +import {CommonGraphUtils} from "../common/common-graph-utils"; +import {ComponentInstanceNodesStyle} from "../common/style/component-instances-nodes-style"; +import {ModulesNodesStyle} from "../common/style/module-node-style"; +import {GRAPH_EVENTS} from "app/utils"; +import {EventListenerService} from "app/services"; +import 'cytoscape-expand-collapse'; + +interface IDeploymentGraphScope extends ng.IScope { + component:Component; +} + +export class DeploymentGraph implements ng.IDirective { + private _cy:Cy.Instance; + + constructor(private NodesFactory:NodesFactory, private commonGraphUtils:CommonGraphUtils, + private deploymentGraphGeneralUtils:DeploymentGraphGeneralUtils, private ComponentInstanceFactory:ComponentInstanceFactory, private eventListenerService:EventListenerService) { + } + + restrict = 'E'; + template = require('./deployment-graph.html'); + scope = { + component: '=', + isViewOnly: '=' + }; + + link = (scope:IDeploymentGraphScope, el:JQuery) => { + + if (scope.component.isResource()) { + if (scope.component.componentInstances && scope.component.componentInstancesRelations && scope.component.groups) { + this.loadGraph(scope, el); + } else { + this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_DEPLOYMENT_GRAPH_DATA_LOADED, () => { + this.loadGraph(scope, el); + }); + } + } + }; + + + public initGraphNodes = (cy:Cy.Instance, component:Component):void => { + if (component.groups) { // Init module nodes + _.each(component.groups, (groupModule:Module) => { + let moduleNode = this.NodesFactory.createModuleNode(groupModule); + this.commonGraphUtils.addNodeToGraph(cy, moduleNode); + + }); + } + _.each(component.componentInstances, (instance: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: ComponentInstanceNodesStyle.getCompositionGraphStyle().concat(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: '/assets/styles/images/resource-icons/' + 'closeModule.png', + collapseCueImage: '/assets/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(); + this.registerGraphEvents(); + }; + + public static factory = (NodesFactory:NodesFactory, CommonGraphUtils:CommonGraphUtils, DeploymentGraphGeneralUtils:DeploymentGraphGeneralUtils, ComponentInstanceFactory:ComponentInstanceFactory, EventListenerService:EventListenerService) => { + return new DeploymentGraph(NodesFactory, CommonGraphUtils, DeploymentGraphGeneralUtils, ComponentInstanceFactory, EventListenerService) + } +} + +DeploymentGraph.factory.$inject = ['NodesFactory', 'CommonGraphUtils', 'DeploymentGraphGeneralUtils', 'ComponentInstanceFactory', 'EventListenerService']; diff --git a/catalog-ui/src/app/directives/graphs-v2/deployment-graph/deployment-graph.html b/catalog-ui/src/app/directives/graphs-v2/deployment-graph/deployment-graph.html new file mode 100644 index 0000000000..56c2d8b200 --- /dev/null +++ b/catalog-ui/src/app/directives/graphs-v2/deployment-graph/deployment-graph.html @@ -0,0 +1,2 @@ +<div class="sdc-deployment-graph-wrapper" ng-class="{'view-only':isViewOnly}"> +</div> diff --git a/catalog-ui/src/app/directives/graphs-v2/deployment-graph/deployment-graph.less b/catalog-ui/src/app/directives/graphs-v2/deployment-graph/deployment-graph.less new file mode 100644 index 0000000000..f83ee8a891 --- /dev/null +++ b/catalog-ui/src/app/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); + } +} diff --git a/catalog-ui/src/app/directives/graphs-v2/deployment-graph/deployment-utils/deployment-graph-general-utils.ts b/catalog-ui/src/app/directives/graphs-v2/deployment-graph/deployment-utils/deployment-graph-general-utils.ts new file mode 100644 index 0000000000..368455cb24 --- /dev/null +++ b/catalog-ui/src/app/directives/graphs-v2/deployment-graph/deployment-utils/deployment-graph-general-utils.ts @@ -0,0 +1,22 @@ +import {Module} from "app/models"; +/** + * Created by obarda on 12/21/2016. + */ + +export class DeploymentGraphGeneralUtils { + + constructor() { + + } + + public findInstanceModule = (groupsArray:Array<Module>, componentInstanceId:string):string => { + let parentGroup:Module = _.find(groupsArray, (group:Module) => { + return _.find(group.members, (member) => { + return member === componentInstanceId; + }); + }); + return parentGroup ? parentGroup.uniqueId : ""; + }; +} + +DeploymentGraphGeneralUtils.$inject = []; diff --git a/catalog-ui/src/app/directives/graphs-v2/image-creator/image-creator.service.ts b/catalog-ui/src/app/directives/graphs-v2/image-creator/image-creator.service.ts new file mode 100644 index 0000000000..1bafb2f32b --- /dev/null +++ b/catalog-ui/src/app/directives/graphs-v2/image-creator/image-creator.service.ts @@ -0,0 +1,45 @@ +'use strict'; +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; + } +} diff --git a/catalog-ui/src/app/directives/graphs-v2/palette/interfaces/i-dragdrop-event.d.ts b/catalog-ui/src/app/directives/graphs-v2/palette/interfaces/i-dragdrop-event.d.ts new file mode 100644 index 0000000000..26c042611c --- /dev/null +++ b/catalog-ui/src/app/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/src/app/directives/graphs-v2/palette/palette.directive.ts b/catalog-ui/src/app/directives/graphs-v2/palette/palette.directive.ts new file mode 100644 index 0000000000..4bfbe5270e --- /dev/null +++ b/catalog-ui/src/app/directives/graphs-v2/palette/palette.directive.ts @@ -0,0 +1,349 @@ +import { + Component, + IAppMenu, + LeftPanelModel, + NodesFactory, + LeftPaletteComponent, + CompositionCiNodeBase, + ComponentInstance +} from "app/models"; +import {CompositionGraphGeneralUtils} from "../composition-graph/utils/composition-graph-general-utils"; +import {EventListenerService} from "app/services"; +import {ResourceType, GRAPH_EVENTS, EVENTS, ComponentInstanceFactory, ModalsHandler} from "app/utils"; +import 'angular-dragdrop'; +import {LeftPaletteLoaderService} from "../../../services/components/utils/composition-left-palette-service"; + +interface IPaletteScope { + components:Array<LeftPaletteComponent>; + currentComponent:Component; + model:any; + displaySortedCategories:any; + expandedSection:string; + dragElement:JQuery; + dragbleNode:{ + event:JQueryEventObject, + components:LeftPaletteComponent, + ui:any + } + + sectionClick:(section:string)=>void; + searchComponents:(searchText:string)=>void; + onMouseOver:(displayComponent:LeftPaletteComponent)=>void; + onMouseOut:(displayComponent:LeftPaletteComponent)=>void; + dragStartCallback:(event:JQueryEventObject, ui, displayComponent:LeftPaletteComponent)=>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: LeftPaletteLoaderService, + private sdcConfig, + private ComponentFactory, + private ComponentInstanceFactory:ComponentInstanceFactory, + private NodesFactory:NodesFactory, + private CompositionGraphGeneralUtils:CompositionGraphGeneralUtils, + private EventListenerService:EventListenerService, + private sdcMenu:IAppMenu, + private ModalsHandler:ModalsHandler) { + + } + + private fetchingComponentFromServer:boolean = false; + private nodeHtmlSubstitute:JQuery; + + scope = { + currentComponent: '=', + isViewOnly: '=', + isLoading: '=' + }; + restrict = 'E'; + template = require('./palette.html'); + + link = (scope:IPaletteScope, el:JQuery) => { + this.nodeHtmlSubstitute = $('<div class="node-substitute"><span></span><img /></div>'); + el.append(this.nodeHtmlSubstitute); + this.registerEventListenerForLeftPalette(scope); + // this.LeftPaletteLoaderService.loadLeftPanel(scope.currentComponent.componentType); + + this.initComponents(scope); + this.initEvents(scope); + this.initDragEvents(scope); + this._initExpandedSection(scope, ''); + el.on('$destroy', ()=> { + //remove listener of download event + this.unRegisterEventListenerForLeftPalette(scope); + }); + }; + + private registerEventListenerForLeftPalette = (scope:IPaletteScope):void => { + if (scope.currentComponent.isResource()) { + this.EventListenerService.registerObserverCallback(EVENTS.RESOURCE_LEFT_PALETTE_UPDATE_EVENT, () => { + this.updateLeftPanelDisplay(scope); + }); + } + if (scope.currentComponent.isService()) { + this.EventListenerService.registerObserverCallback(EVENTS.SERVICE_LEFT_PALETTE_UPDATE_EVENT, () => { + this.updateLeftPanelDisplay(scope); + }); + } + if (scope.currentComponent.isProduct()) { + this.EventListenerService.registerObserverCallback(EVENTS.PRODUCT_LEFT_PALETTE_UPDATE_EVENT, () => { + this.updateLeftPanelDisplay(scope); + }); + } + }; + + private unRegisterEventListenerForLeftPalette = (scope:IPaletteScope):void => { + if (scope.currentComponent.isResource()) { + this.EventListenerService.unRegisterObserver(EVENTS.RESOURCE_LEFT_PALETTE_UPDATE_EVENT); + } + if (scope.currentComponent.isService()) { + this.EventListenerService.unRegisterObserver(EVENTS.SERVICE_LEFT_PALETTE_UPDATE_EVENT); + } + if (scope.currentComponent.isProduct()) { + this.EventListenerService.unRegisterObserver(EVENTS.PRODUCT_LEFT_PALETTE_UPDATE_EVENT); + } + }; + + private leftPanelResourceFilter(resourcesNotAbstract:Array<LeftPaletteComponent>, resourceFilterTypes:Array<string>):Array<LeftPaletteComponent> { + let filterResources = _.filter(resourcesNotAbstract, (component) => { + return resourceFilterTypes.indexOf(component.getComponentSubType()) > -1; + }); + return filterResources; + } + + private initLeftPanel(leftPanelComponents:Array<LeftPaletteComponent>, resourceFilterTypes:Array<string>):LeftPanelModel { + let leftPanelModel = new 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 initEvents(scope:IPaletteScope) { + /** + * + * @param section + */ + scope.sectionClick = (section:string) => { + if (section === scope.expandedSection) { + scope.expandedSection = ''; + return; + } + scope.expandedSection = section; + }; + + scope.onMouseOver = (displayComponent:LeftPaletteComponent) => { + 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: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: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(); + this.updateLeftPanelDisplay(scope); + } + + private updateLeftPanelDisplay(scope:IPaletteScope) { + 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:LeftPaletteComponent):void => { + if (scope.isLoading || !scope.isDragable || scope.isViewOnly) { + return; + } + + let component = _.find(this.LeftPaletteLoaderService.getLeftPanelComponentsForDisplay(scope.currentComponent.componentType), (componentFullData:LeftPaletteComponent) => { + 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:LeftPaletteComponent = _.find(this.LeftPaletteLoaderService.getLeftPanelComponentsForDisplay(scope.currentComponent.componentType), + (fullComponent:LeftPaletteComponent) => { + return (<any>angular.element(e.currentTarget).scope()).component.uniqueId === fullComponent.uniqueId; + }); + let componentInstance:ComponentInstance = this.ComponentInstanceFactory.createComponentInstanceFromComponent(dragComponent); + let node: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<LeftPaletteComponent>, subcategoryKey) { + let filteredResources = []; + angular.forEach(subcategory, function (component:LeftPaletteComponent) { + + 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, + ModalsHandler) => { + return new Palette($log, + LeftPaletteLoaderService, + sdcConfig, + ComponentFactory, + ComponentInstanceFactory, + NodesFactory, + CompositionGraphGeneralUtils, + EventListenerService, + sdcMenu, + ModalsHandler); + }; +} + +Palette.factory.$inject = [ + '$log', + 'LeftPaletteLoaderService', + 'sdcConfig', + 'ComponentFactory', + 'ComponentInstanceFactory', + 'NodesFactory', + 'CompositionGraphGeneralUtils', + 'EventListenerService', + 'sdcMenu', + 'ModalsHandler' +]; diff --git a/catalog-ui/src/app/directives/graphs-v2/palette/palette.html b/catalog-ui/src/app/directives/graphs-v2/palette/palette.html new file mode 100644 index 0000000000..4b123e5777 --- /dev/null +++ b/catalog-ui/src/app/directives/graphs-v2/palette/palette.html @@ -0,0 +1,57 @@ +<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}} + </div> + </div> + </div> + </div> + </div> +</div> diff --git a/catalog-ui/src/app/directives/graphs-v2/palette/palette.less b/catalog-ui/src/app/directives/graphs-v2/palette/palette.less new file mode 100644 index 0000000000..85657a43a5 --- /dev/null +++ b/catalog-ui/src/app/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/src/app/directives/graphs-v2/relation-menu/relation-menu.html b/catalog-ui/src/app/directives/graphs-v2/relation-menu/relation-menu.html new file mode 100644 index 0000000000..a0a9e4af27 --- /dev/null +++ b/catalog-ui/src/app/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/src/app/directives/graphs-v2/relation-menu/relation-menu.less b/catalog-ui/src/app/directives/graphs-v2/relation-menu/relation-menu.less new file mode 100644 index 0000000000..dea814dbec --- /dev/null +++ b/catalog-ui/src/app/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/src/app/directives/graphs-v2/relation-menu/relation-menu.ts b/catalog-ui/src/app/directives/graphs-v2/relation-menu/relation-menu.ts new file mode 100644 index 0000000000..b05385b668 --- /dev/null +++ b/catalog-ui/src/app/directives/graphs-v2/relation-menu/relation-menu.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========================================================= + */ +'use strict' +import {MatchBase, RelationMenuDirectiveObj} from "app/models"; +import {Component} from "../../../models/components/component"; + +export interface IRelationMenuScope extends ng.IScope { + relationMenuDirectiveObj:RelationMenuDirectiveObj; + createRelation:Function; + isLinkMenuOpen:boolean; + hideRelationMatch:Function; + cancel:Function; + + saveRelation(); + showMatch(arr1:Array<MatchBase>, arr2:Array<MatchBase>):boolean; + hasMatchesToShow(matchesObj:MatchBase, selectedMatch:Array<MatchBase>); + updateSelectionText():void; + +} + + +export class RelationMenuDirective implements ng.IDirective { + + constructor(private $filter:ng.IFilterService) { + } + + scope = { + relationMenuDirectiveObj: '=', + isLinkMenuOpen: '=', + createRelation: '&', + cancel: '&' + }; + + restrict = 'E'; + replace = true; + template = ():string => { + return require('./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:MatchBase = chosenMatches[0]; + scope.createRelation()(chosenMatch); + }; + + + scope.hideRelationMatch = () => { + scope.isLinkMenuOpen = false; + scope.cancel(); + }; + + //to show options in link menu + scope.showMatch = (arr1:Array<MatchBase>, arr2:Array<MatchBase>):boolean => { + return !arr1 || !arr2 || _.intersection(arr1, arr2).length > 0; + }; + + //to show requirements/capabilities title + scope.hasMatchesToShow = (matchesObj:MatchBase, selectedMatch:Array<MatchBase>):boolean => { + let result:boolean = false; + _.forEach(matchesObj, (matchesArr:Array<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 = ($filter:ng.IFilterService)=> { + return new RelationMenuDirective($filter); + }; +} + +RelationMenuDirective.factory.$inject = ['$filter']; diff --git a/catalog-ui/src/app/directives/info-tooltip/info-tooltip.html b/catalog-ui/src/app/directives/info-tooltip/info-tooltip.html new file mode 100644 index 0000000000..5c2bdcf5f1 --- /dev/null +++ b/catalog-ui/src/app/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/src/app/directives/info-tooltip/info-tooltip.less b/catalog-ui/src/app/directives/info-tooltip/info-tooltip.less new file mode 100644 index 0000000000..cc3e3dc1ec --- /dev/null +++ b/catalog-ui/src/app/directives/info-tooltip/info-tooltip.less @@ -0,0 +1,42 @@ +.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 4px 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; + font-weight: normal; + text-align: left; + line-height: normal; + } + } +} diff --git a/catalog-ui/src/app/directives/info-tooltip/info-tooltip.ts b/catalog-ui/src/app/directives/info-tooltip/info-tooltip.ts new file mode 100644 index 0000000000..63ecdc6091 --- /dev/null +++ b/catalog-ui/src/app/directives/info-tooltip/info-tooltip.ts @@ -0,0 +1,37 @@ +/** + * Created by rcohen on 9/25/2016. + */ +'use strict'; + +export interface IInfoTooltipScope extends ng.IScope { + infoMessageTranslate:string; + direction:string; +} + + +export class InfoTooltipDirective implements ng.IDirective { + + constructor() { + } + + scope = { + infoMessageTranslate: '@', + direction: '@'//get 'right' or 'left', the default is 'right' + }; + + restrict = 'E'; + replace = true; + template = ():string => { + return require('./info-tooltip.html'); + }; + + link = (scope:IInfoTooltipScope, element:any, $attr:any) => { + scope.direction = scope.direction || 'right'; + }; + + public static factory = ()=> { + return new InfoTooltipDirective(); + }; +} + +InfoTooltipDirective.factory.$inject = []; diff --git a/catalog-ui/src/app/directives/inputs-and-properties/inputs/input-row-directive.ts b/catalog-ui/src/app/directives/inputs-and-properties/inputs/input-row-directive.ts new file mode 100644 index 0000000000..221c20d08f --- /dev/null +++ b/catalog-ui/src/app/directives/inputs-and-properties/inputs/input-row-directive.ts @@ -0,0 +1,43 @@ +/** + * Created by obarda on 1/8/2017. + */ +'use strict'; + +export interface IInputRowDirective extends ng.IScope { + showDeleteIcon:boolean; +} + + +export class InputRowDirective implements ng.IDirective { + + constructor() { + + } + + scope = { + instanceInputsMap: '=', + input: '=', + instanceName: '=', + instanceId: '=', + isViewOnly: '=', + deleteInput: '&', + onNameClicked: '&', + onCheckboxClicked: '&' + }; + + restrict = 'E'; + replace = true; + template = ():string => { + return require('./input-row-view.html'); + }; + + link = (scope:IInputRowDirective, element:any, $attr:any) => { + scope.showDeleteIcon = $attr.deleteInput ? true : false; + }; + + public static factory = ()=> { + return new InputRowDirective(); + }; +} + +InputRowDirective.factory.$inject = []; diff --git a/catalog-ui/src/app/directives/inputs-and-properties/inputs/input-row-view.html b/catalog-ui/src/app/directives/inputs-and-properties/inputs/input-row-view.html new file mode 100644 index 0000000000..872a26bc27 --- /dev/null +++ b/catalog-ui/src/app/directives/inputs-and-properties/inputs/input-row-view.html @@ -0,0 +1,43 @@ +<div class="input-row"> + <div class="title-text" ng-click="onNameClicked(input)" data-tests-id="inputName_{{input.name}}"> + {{input.name}} + </div> + <div class="flex-container"> + <div class="expand-collapse-inputs-table-icon"></div> + <div class="flex-item"> + <div class="no-overflow"> + <span class="title-text">Description:</span> + <span tooltips tooltip-content="{{input.description}}" data-tests-id="inputDescription_{{input.name}}">{{input.description}}</span> + </div> + <div class="text" tooltips tooltip-content="{{input.defaultValue}}"> + <span class="title-text">Default Value:</span> + <span data-tests-id="inputDefaultValue_{{input.name}}">{{input.defaultValue}}</span> + </div> + </div> + <div class="flex-item "> + <div class="text"> + <span class="title-text">VF Instance:</span> + <span tooltips tooltip-content="{{instanceName}}" data-tests-id="inputInstanceName_{{input.name}}">{{instanceName}}</span> + </div> + <div class="text"> + <span class="title-text">Type:</span> + <span tooltips tooltip-content="{{input.type}}" data-tests-id="inputType_{{input.name}}">{{input.type}} </span> + </div> + </div> + <sdc-checkbox ng-if="instanceInputsMap" + class="input-check-box" + disabled="input.isAlreadySelected || isViewOnly" + sdc-checklist-model="instanceInputsMap[instanceId]" + sdc-checklist-value="input" + sdc-checklist-change="onCheckboxClicked()" + data-tests-id="inputsCheckbox_{{input.name}}" + data-ng-click=" $event.stopPropagation()"></sdc-checkbox> + + <div class="delete" ng-if="showDeleteIcon"> + <span class="sprite-new delete-icon remove-input-icon" + data-ng-class="{'disabled': isViewOnly || input.isDeleteDisabled}" + data-ng-click="deleteInput(input); $event.stopPropagation();" + data-tests-id="deleteInput_{{input.name}}"></span> + </div> + </div> +</div> diff --git a/catalog-ui/src/app/directives/inputs-and-properties/inputs/input-row.less b/catalog-ui/src/app/directives/inputs-and-properties/inputs/input-row.less new file mode 100644 index 0000000000..fa79b45c3c --- /dev/null +++ b/catalog-ui/src/app/directives/inputs-and-properties/inputs/input-row.less @@ -0,0 +1,54 @@ +&.expanded { + .input-row { + background-color: @tlv_color_v; + } +} + +.input-row { + padding-left: 45px; + background: @tlv_color_t; + border: @main_color_o solid 1px; + align-items: center; + .hand; + margin: 1px 0px 1px 0px; + + &.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 35px 0 35px; + } + + .expand-collapse-inputs-table-icon { + margin-top: 15px; + } + + .delete { + width: 50px; + padding: 0; + position: relative; + } + + .remove-input-icon { + position: absolute; + top: 12px; + right: 18px; + } + + .remove-input-icon:hover { + .delete-icon-hover; + } +} + +.input-row:hover { + .bg_j; +} diff --git a/catalog-ui/src/app/directives/inputs-and-properties/properties/property-row-directive.ts b/catalog-ui/src/app/directives/inputs-and-properties/properties/property-row-directive.ts new file mode 100644 index 0000000000..a9ff71cda5 --- /dev/null +++ b/catalog-ui/src/app/directives/inputs-and-properties/properties/property-row-directive.ts @@ -0,0 +1,42 @@ +/** + * Created by obarda on 1/8/2017. + */ +'use strict'; + +export interface IPropertyRowDirective extends ng.IScope { + onNameClicked:Function; + isClickable:boolean; +} + +export class PropertyRowDirective implements ng.IDirective { + + constructor() { + + } + + scope = { + property: '=', + instanceName: '=', + instanceId: '=', + instancePropertiesMap: '=', + onNameClicked: '&', + onCheckboxClicked: '&' + }; + + restrict = 'E'; + replace = true; + template = ():string => { + return require('./property-row-view.html'); + }; + + link = (scope:IPropertyRowDirective, element:any, $attr:any) => { + scope.isClickable = $attr.onNameClicked ? true : false; + }; + + public static factory = ()=> { + return new PropertyRowDirective(); + }; + +} + +PropertyRowDirective.factory.$inject = []; diff --git a/catalog-ui/src/app/directives/inputs-and-properties/properties/property-row-view.html b/catalog-ui/src/app/directives/inputs-and-properties/properties/property-row-view.html new file mode 100644 index 0000000000..ff82a8b685 --- /dev/null +++ b/catalog-ui/src/app/directives/inputs-and-properties/properties/property-row-view.html @@ -0,0 +1,26 @@ +<div class="property-row flex-container"> + <div class="flex-item text property-name-container"> + <span class="title-blue-text property-name-text " data-ng-class="{'hand': isClickable}" data-tests-id="propertyName" data-ng-click="onNameClicked(property)" tooltips tooltip-content="{{property.name}}">{{property.name}}</span> + </div> + <div class="flex-item text property-name-container"> + <span class="text instance-name-text" data-tests-id="instanceName" tooltips tooltip-content="{{instanceName}}">{{instanceName}}</span> + </div> + <div class="type-schema-container"> + <div class="text"> + <span tooltips tooltip-content="propertyName_{{property.type}}" data-tests-id="propertyType">{{property.type}}</span> + </div> + </div> + <div class="type-schema-container"> + <div class="text"> + <span tooltips tooltip-content="{{property.schema.property.type}}" data-tests-id="propertySchema">{{property.schema.property.type}} </span> + </div> + </div> + <sdc-checkbox ng-if="instancePropertiesMap" + class="property-check-box" + disabled="property.isAlreadySelected || isViewOnly" + sdc-checklist-model="instancePropertiesMap[instanceId]" + sdc-checklist-value="property" + sdc-checklist-change="onCheckboxClicked()" + data-tests-id="propertyCheckbox_{{property.name}}" + data-ng-click=" $event.stopPropagation()"></sdc-checkbox> +</div> diff --git a/catalog-ui/src/app/directives/inputs-and-properties/properties/property-row-view.less b/catalog-ui/src/app/directives/inputs-and-properties/properties/property-row-view.less new file mode 100644 index 0000000000..e25427bda2 --- /dev/null +++ b/catalog-ui/src/app/directives/inputs-and-properties/properties/property-row-view.less @@ -0,0 +1,35 @@ +.property-row { + border-bottom: 1px solid @border_color_d; + padding-left: 10px; + .property-name-container { + display: flex; + flex-grow: 3; + } + + .type-schema-container { + flex-grow: 2; + border-left: 1px solid @border_color_d; + text-align: left; + line-height: 30px; + text-transform: capitalize; + width: 10px; + } + + .property-check-box { + padding-right: 10px; + margin-top: 9px; + } +} + +.property-row:hover { + background-color: @func_color_r; +} + +&.expanded { + .flex-container { + .expand-collapse-inputs-table-icon { + transform: rotate(180deg); + left: 0px; + } + } +} diff --git a/catalog-ui/src/app/directives/invalid-characters/invalid-characters.ts b/catalog-ui/src/app/directives/invalid-characters/invalid-characters.ts new file mode 100644 index 0000000000..20ed71e53f --- /dev/null +++ b/catalog-ui/src/app/directives/invalid-characters/invalid-characters.ts @@ -0,0 +1,51 @@ +'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/src/app/directives/layout/top-nav/top-nav.html b/catalog-ui/src/app/directives/layout/top-nav/top-nav.html new file mode 100644 index 0000000000..60d3d63855 --- /dev/null +++ b/catalog-ui/src/app/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" translate="PROJECT_TITLE"></a> + <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/src/app/directives/layout/top-nav/top-nav.less b/catalog-ui/src/app/directives/layout/top-nav/top-nav.less new file mode 100644 index 0000000000..65021bdc4d --- /dev/null +++ b/catalog-ui/src/app/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/src/app/directives/layout/top-nav/top-nav.ts b/catalog-ui/src/app/directives/layout/top-nav/top-nav.ts new file mode 100644 index 0000000000..c7208a909a --- /dev/null +++ b/catalog-ui/src/app/directives/layout/top-nav/top-nav.ts @@ -0,0 +1,141 @@ +'use strict'; +import {IAppConfigurtaion, IHostedApplication, IUserProperties} from "app/models"; +import {IUserResourceClass} from "app/services"; +import {MenuItemGroup, MenuItem} from "app/utils"; + +export interface ITopNavScope extends ng.IScope { + topLvlSelectedIndex:number; + hideSearch:boolean; + searchBind:any; + menuModel:Array<MenuItemGroup>; + + topLvlMenu:MenuItemGroup; + goToState(state:string, params:Array<any>):ng.IPromise<boolean>; + menuItemClick:Function; + user:IUserProperties; + version:string; +} + + +export class TopNavDirective implements ng.IDirective { + + constructor(private $filter:ng.IFilterService, + private $state:ng.ui.IStateService, + private $q:ng.IQService, + private userResourceService:IUserResourceClass, + private sdcConfig:IAppConfigurtaion) { + } + + public replace = true; + public restrict = 'E'; + public transclude = false; + + + scope = { + topLvlSelectedIndex: '@?', + hideSearch: '=', + searchBind: '=', + version: '@', + notificationIconCallback: '=', + menuModel: '=?', + }; + + template = ():string => { + return require('./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: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: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<MenuItem> = [ + new MenuItem(this.$filter('translate')("TOP_MENU_HOME_BUTTON"), null, "dashboard", "goToState", null, null), + new 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 MenuItem(this.$filter('translate')("TOP_MENU_ON_BOARD_BUTTON"), null, "onboardVendor", "goToState", null, null)); + _.each(this.sdcConfig.hostedApplications, (hostedApp:IHostedApplication)=> { + if (hostedApp.exists) { + tmpArray.push(new MenuItem(hostedApp.navTitle, null, hostedApp.defaultState, "goToState", null, null)); + } + }); + } + + scope.topLvlMenu = new 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:MenuItemGroup, item: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 = ($filter:ng.IFilterService, $state:ng.ui.IStateService, $q:ng.IQService, userResourceService:IUserResourceClass, sdcConfig:IAppConfigurtaion)=> { + return new TopNavDirective($filter, $state, $q, userResourceService, sdcConfig); + }; + +} + +TopNavDirective.factory.$inject = ['$filter', '$state', '$q', 'Sdc.Services.UserResourceService', 'sdcConfig']; diff --git a/catalog-ui/src/app/directives/layout/top-progress/top-progress.html b/catalog-ui/src/app/directives/layout/top-progress/top-progress.html new file mode 100644 index 0000000000..ab2c8e364e --- /dev/null +++ b/catalog-ui/src/app/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/src/app/directives/layout/top-progress/top-progress.less b/catalog-ui/src/app/directives/layout/top-progress/top-progress.less new file mode 100644 index 0000000000..ce70bd7378 --- /dev/null +++ b/catalog-ui/src/app/directives/layout/top-progress/top-progress.less @@ -0,0 +1,60 @@ +.top-progress { + text-align: left; + + .sdc-progress-title { + .n_12_r; + height: 17px; + display: block; + + .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/src/app/directives/layout/top-progress/top-progress.ts b/catalog-ui/src/app/directives/layout/top-progress/top-progress.ts new file mode 100644 index 0000000000..06beb43ea1 --- /dev/null +++ b/catalog-ui/src/app/directives/layout/top-progress/top-progress.ts @@ -0,0 +1,36 @@ +'use strict'; + +export interface ITopProgressScope extends ng.IScope { + progressValue:number; + progressMessage:string; +} + +export class TopProgressDirective implements ng.IDirective { + + constructor() { + } + + public replace = true; + public restrict = 'E'; + public transclude = false; + + scope = { + progressValue: '=', + progressMessage: '=' + }; + + template = ():string => { + return require('./top-progress.html'); + }; + + public link = (scope:ITopProgressScope, $elem:ng.IAugmentedJQuery, $attrs:angular.IAttributes) => { + + }; + + public static factory = ()=> { + return new TopProgressDirective(); + }; + +} + +TopProgressDirective.factory.$inject = []; diff --git a/catalog-ui/src/app/directives/loader/loader-directive.html b/catalog-ui/src/app/directives/loader/loader-directive.html new file mode 100644 index 0000000000..e40b059a57 --- /dev/null +++ b/catalog-ui/src/app/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/src/app/directives/loader/loader-directive.less b/catalog-ui/src/app/directives/loader/loader-directive.less new file mode 100644 index 0000000000..ae0b41aab1 --- /dev/null +++ b/catalog-ui/src/app/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/src/app/directives/loader/loader-directive.ts b/catalog-ui/src/app/directives/loader/loader-directive.ts new file mode 100644 index 0000000000..aa9c4b09c4 --- /dev/null +++ b/catalog-ui/src/app/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========================================================= + */ +'use strict'; +import {EVENTS} from "app/utils"; +import {EventListenerService} from "app/services"; + +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 EventListenerService: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 require('./loader-directive.html'); + } + + link = (scope:ILoaderScope, element:any) => { + + let interval; + + this.EventListenerService.registerObserverCallback(EVENTS.SHOW_LOADER_EVENT, (loaderType)=> { + if (scope.loaderType !== loaderType) { + return; + } + scope.display = true; + }); + this.EventListenerService.registerObserverCallback(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 = (EventListenerService:EventListenerService)=> { + return new LoaderDirective( EventListenerService); + }; + +} + +LoaderDirective.factory.$inject = ['EventListenerService']; diff --git a/catalog-ui/src/app/directives/modal/sdc-modal.html b/catalog-ui/src/app/directives/modal/sdc-modal.html new file mode 100644 index 0000000000..a8419f162d --- /dev/null +++ b/catalog-ui/src/app/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/src/app/directives/modal/sdc-modal.less b/catalog-ui/src/app/directives/modal/sdc-modal.less new file mode 100644 index 0000000000..d8dfdbb73b --- /dev/null +++ b/catalog-ui/src/app/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/src/app/directives/modal/sdc-modal.ts b/catalog-ui/src/app/directives/modal/sdc-modal.ts new file mode 100644 index 0000000000..aedc28262f --- /dev/null +++ b/catalog-ui/src/app/directives/modal/sdc-modal.ts @@ -0,0 +1,80 @@ +'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() { + } + + scope = { + modal: '=', + type: '@', + header: '@', + headerTranslate: '@', + headerTranslateValues: '@', + showCloseButton: '@', + hideBackground: '@', + buttons: '=', + getCloseModalResponse: '=' + }; + + public replace = true; + public restrict = 'E'; + public transclude = true; + + template = ():string => { + return require('./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 = ()=> { + return new SdcModalDirective(); + }; + +} + +SdcModalDirective.factory.$inject = []; diff --git a/catalog-ui/src/app/directives/perfect-scrollbar/angular-perfect-scrollbar.ts b/catalog-ui/src/app/directives/perfect-scrollbar/angular-perfect-scrollbar.ts new file mode 100644 index 0000000000..914a7049bb --- /dev/null +++ b/catalog-ui/src/app/directives/perfect-scrollbar/angular-perfect-scrollbar.ts @@ -0,0 +1,125 @@ +'use strict'; + +export interface IPerfectScrollerScope extends ng.IScope { + //update(event:string): void; +} + +export class PerfectScrollerDirective implements ng.IDirective { + + constructor(private $parse: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'); + }); + } + + $elem.bind('$destroy', function () { + $elem.perfectScrollbar('destroy'); + }); + + }; + + public static factory = ($parse:any)=> { + return new PerfectScrollerDirective($parse); + }; + +} + +PerfectScrollerDirective.factory.$inject = ['$parse']; diff --git a/catalog-ui/src/app/directives/print-graph-screen/print-graph-screen.ts b/catalog-ui/src/app/directives/print-graph-screen/print-graph-screen.ts new file mode 100644 index 0000000000..a6e07eefee --- /dev/null +++ b/catalog-ui/src/app/directives/print-graph-screen/print-graph-screen.ts @@ -0,0 +1,190 @@ +'use strict'; +import {IAppMenu, Component, IAppConfigurtaion} from "app/models"; +import {UrlToBase64Service} from "app/services"; + +export interface IPrintGraphScreenScope extends ng.IScope { + entity:Component; +} + + +export class PrintGraphScreenDirective implements ng.IDirective { + + constructor(private $filter:ng.IFilterService, + private sdcMenu:IAppMenu, + private sdcConfig:IAppConfigurtaion, + private urlToBase64Service:UrlToBase64Service) { + } + + scope = { + entity: '=' + }; + restrict = 'A'; + link = (scope:IPrintGraphScreenScope, element:any) => { + + + element.bind('click', function () { + printScreen(); + }); + + + 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 :go.Diagram = go.Diagram.fromDiv(diagramDiv), canvasImg = new Image(); + // diagram.startTransaction('print screen'); + // let canvasImgBase64:any = diagram.makeImageData({ + // //scale: 1, + // size: new go.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} + // ]; + return null; + }; + + + }; + + public static factory = ($filter:ng.IFilterService, sdcMenu:IAppMenu, sdcConfig:IAppConfigurtaion, urlToBase64Service:UrlToBase64Service)=> { + return new PrintGraphScreenDirective($filter, sdcMenu, sdcConfig, urlToBase64Service); + }; + +} + +PrintGraphScreenDirective.factory.$inject = ['$filter', 'sdcMenu', 'sdcConfig', 'Sdc.Services.UrlToBase64Service']; diff --git a/catalog-ui/src/app/directives/property-types/data-type-fields-structure/data-type-fields-structure.html b/catalog-ui/src/app/directives/property-types/data-type-fields-structure/data-type-fields-structure.html new file mode 100644 index 0000000000..b62824d544 --- /dev/null +++ b/catalog-ui/src/app/directives/property-types/data-type-fields-structure/data-type-fields-structure.html @@ -0,0 +1,83 @@ +<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)" 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]}}"> + + </fields-structure> + </div> + <div data-ng-if="!dataTypesService.isDataTypeForDataTypePropertyType(property)" 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="{{(property.simpleType||property.type) == 'integer'? 10 : 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="inputOnValueChange(property)" + 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/src/app/directives/property-types/data-type-fields-structure/data-type-fields-structure.less b/catalog-ui/src/app/directives/property-types/data-type-fields-structure/data-type-fields-structure.less new file mode 100644 index 0000000000..5c65fdc9dc --- /dev/null +++ b/catalog-ui/src/app/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/src/app/directives/property-types/data-type-fields-structure/data-type-fields-structure.ts b/catalog-ui/src/app/directives/property-types/data-type-fields-structure/data-type-fields-structure.ts new file mode 100644 index 0000000000..2506dbe400 --- /dev/null +++ b/catalog-ui/src/app/directives/property-types/data-type-fields-structure/data-type-fields-structure.ts @@ -0,0 +1,154 @@ +/** + * Created by obarda on 1/27/2016. + */ +'use strict'; +import {DataTypesService} from "app/services"; +import { ValidationUtils } from "app/utils"; +import { DataTypePropertyModel } from "app/models/data-type-properties"; +import { DataTypesMap} from "app/models"; + +export interface IDataTypeFieldsStructureScope extends ng.IScope { + parentFormObj:ng.IFormController; + dataTypeProperties:Array<DataTypePropertyModel>; + typeName:string; + valueObjRef:any; + propertyNameValidationPattern:RegExp; + fieldsPrefixName:string; + readOnly:boolean; + currentTypeDefaultValue:any; + types:DataTypesMap; + expandByDefault:boolean; + expand:boolean; + expanded:boolean; + dataTypesService:DataTypesService; + + expandAndCollapse():void; + getValidationPattern(type:string):RegExp; + validateIntRange(value:string):boolean; + onValueChange(propertyName:string, type:string):void + inputOnValueChange(property:any):void; +} + + +export class DataTypeFieldsStructureDirective implements ng.IDirective { + + constructor(private DataTypesService:DataTypesService, + private PropertyNameValidationPattern:RegExp, + private ValidationUtils:ValidationUtils) { + } + + scope = { + valueObjRef: '=', + typeName: '=', + parentFormObj: '=', + fieldsPrefixName: '=', + readOnly: '=', + defaultValue: '@', + // types: '=', + expandByDefault: '=' + }; + + restrict = 'E'; + replace = true; + template = ():string => { + return require('./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<DataTypePropertyModel>):any => { + let defaultValue = {}; + for (let i = 0; i < dataTypeProperties.length; i++) { + if (dataTypeProperties[i].type != 'string') { + if (!angular.isUndefined(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); + 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 (angular.isUndefined(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]); + } + }; + + scope.inputOnValueChange = (property:any) => { + + let value = !scope.parentFormObj[scope.fieldsPrefixName + property.name].$error.pattern + && ('integer' == property.type && scope.parentFormObj[scope.fieldsPrefixName + property.name].$setValidity('pattern', scope.validateIntRange(scope.valueObjRef[property.name])) + || scope.onValueChange(property.name, (property.simpleType || property.type))); + return value; + } + }; + + public static factory = (DataTypesService:DataTypesService, + PropertyNameValidationPattern:RegExp, + ValidationUtils:ValidationUtils)=> { + return new DataTypeFieldsStructureDirective(DataTypesService, PropertyNameValidationPattern, ValidationUtils); + }; +} + +DataTypeFieldsStructureDirective.factory.$inject = ['Sdc.Services.DataTypesService', 'PropertyNameValidationPattern', 'ValidationUtils']; diff --git a/catalog-ui/src/app/directives/property-types/type-list/type-list-directive.html b/catalog-ui/src/app/directives/property-types/type-list/type-list-directive.html new file mode 100644 index 0000000000..138e848e59 --- /dev/null +++ b/catalog-ui/src/app/directives/property-types/type-list/type-list-directive.html @@ -0,0 +1,56 @@ +<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 ng-if="!readOnly" 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"></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/src/app/directives/property-types/type-list/type-list-directive.less b/catalog-ui/src/app/directives/property-types/type-list/type-list-directive.less new file mode 100644 index 0000000000..71263f2642 --- /dev/null +++ b/catalog-ui/src/app/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: 0 8px; + .delete-list-item{ + margin: 0 0 0 2px; + .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/src/app/directives/property-types/type-list/type-list-directive.ts b/catalog-ui/src/app/directives/property-types/type-list/type-list-directive.ts new file mode 100644 index 0000000000..ac5da2be73 --- /dev/null +++ b/catalog-ui/src/app/directives/property-types/type-list/type-list-directive.ts @@ -0,0 +1,106 @@ +/** + * Created by rcohen on 9/15/2016. + */ +'use strict'; +import {SchemaProperty} from "app/models"; +import {ValidationUtils, PROPERTY_TYPES} from "app/utils"; +import {DataTypesService} from "app/services"; + +export interface ITypeListScope extends ng.IScope { + parentFormObj:ng.IFormController; + schemaProperty:SchemaProperty; + isSchemaTypeDataType:boolean; + valueObjRef:any; + propertyNameValidationPattern:RegExp; + fieldsPrefixName:string; + readOnly:boolean; + listDefaultValue:any; + 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 DataTypesService:DataTypesService, + private PropertyNameValidationPattern:RegExp, + private ValidationUtils: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 + maxLength: '=' + }; + + restrict = 'E'; + replace = true; + template = ():string => { + return require('./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); + //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) == 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 = (DataTypesService:DataTypesService, + PropertyNameValidationPattern:RegExp, + ValidationUtils:ValidationUtils)=> { + return new TypeListDirective(DataTypesService, PropertyNameValidationPattern, ValidationUtils); + }; +} + +TypeListDirective.factory.$inject = ['Sdc.Services.DataTypesService', 'PropertyNameValidationPattern', 'ValidationUtils']; + diff --git a/catalog-ui/src/app/directives/property-types/type-map/type-map-directive.html b/catalog-ui/src/app/directives/property-types/type-map/type-map-directive.html new file mode 100644 index 0000000000..37b076f26e --- /dev/null +++ b/catalog-ui/src/app/directives/property-types/type-map/type-map-directive.html @@ -0,0 +1,73 @@ +<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" + data-ng-maxlength="50" + maxlength="50" + name="mapKey{{fieldsPrefixName}}{{$index}}" + data-ng-pattern="MapKeyValidationPattern" + 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> + <span ng-show="parentFormObj['mapKey'+fieldsPrefixName+$index].$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '50' }"></span> + <span ng-show="parentFormObj['mapKey'+fieldsPrefixName+$index].$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></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" + ></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/src/app/directives/property-types/type-map/type-map-directive.less b/catalog-ui/src/app/directives/property-types/type-map/type-map-directive.less new file mode 100644 index 0000000000..2480b626f2 --- /dev/null +++ b/catalog-ui/src/app/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/src/app/directives/property-types/type-map/type-map-directive.ts b/catalog-ui/src/app/directives/property-types/type-map/type-map-directive.ts new file mode 100644 index 0000000000..a0c9a6c460 --- /dev/null +++ b/catalog-ui/src/app/directives/property-types/type-map/type-map-directive.ts @@ -0,0 +1,143 @@ +/** + * Created by rcohen on 9/15/2016. + */ +'use strict'; +import {ValidationUtils, PROPERTY_TYPES} from "app/utils"; +import {DataTypesService} from "app/services"; +import {SchemaProperty} from "app/models"; + +export interface ITypeMapScope extends ng.IScope { + parentFormObj:ng.IFormController; + schemaProperty:SchemaProperty; + isSchemaTypeDataType:boolean; + valueObjRef:any; + mapKeys:Array<string>;//array of map keys + MapKeyValidationPattern:RegExp; + fieldsPrefixName:string; + readOnly:boolean; + mapDefaultValue:any; + 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 DataTypesService:DataTypesService, + private MapKeyValidationPattern:RegExp, + private ValidationUtils: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 + maxLength: '=' + }; + + restrict = 'E'; + replace = true; + template = ():string => { + return require('./type-map-directive.html'); + }; + + link = (scope:ITypeMapScope, element:any, $attr:any) => { + scope.MapKeyValidationPattern = this.MapKeyValidationPattern; + + //reset valueObjRef and mapKeys when schema type is changed + scope.$watchCollection('schemaProperty.type', (newData:any):void => { + scope.isSchemaTypeDataType = this.DataTypesService.isDataTypeForSchemaType(scope.schemaProperty); + 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]; + let existsKeyIndex = Object.keys(scope.valueObjRef).indexOf(newKey); + if (existsKeyIndex > -1 && existsKeyIndex != index) { + scope.parentFormObj[fieldName].$setValidity('keyExist', false); + } else { + scope.parentFormObj[fieldName].$setValidity('keyExist', true); + if (!scope.parentFormObj[fieldName].$invalid) { + let newObj = {}; + angular.forEach(scope.valueObjRef,function(value:any,key:string){ + if(key == oldKey){ + newObj[newKey] = value; + }else{ + newObj[key] = value; + } + }); + scope.valueObjRef = newObj; + } + } + }; + + 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 != PROPERTY_TYPES.STRING) { + objectOfValues[locationInObj] = JSON.parse(objectOfValues[locationInObj]); + } + } + }; + + public static factory = (DataTypesService:DataTypesService, + MapKeyValidationPattern:RegExp, + ValidationUtils:ValidationUtils, + $timeout:ng.ITimeoutService)=> { + return new TypeMapDirective(DataTypesService, MapKeyValidationPattern, ValidationUtils, $timeout); + }; +} + +TypeMapDirective.factory.$inject = ['Sdc.Services.DataTypesService', 'MapKeyValidationPattern', 'ValidationUtils', '$timeout']; diff --git a/catalog-ui/src/app/directives/punch-out/punch-out.ts b/catalog-ui/src/app/directives/punch-out/punch-out.ts new file mode 100644 index 0000000000..aa15f1f3e9 --- /dev/null +++ b/catalog-ui/src/app/directives/punch-out/punch-out.ts @@ -0,0 +1,80 @@ +'use strict'; +import {IUserProperties, IAppConfigurtaion} from "app/models"; +let PunchOutRegistry = require('third-party/PunchOutRegistry.js'); + +export interface IPunchOutScope extends ng.IScope { + name:string; + data:any; + user:IUserProperties; + onEvent:Function; +} + +export class PunchOutDirective implements ng.IDirective { + + constructor(private sdcConfig: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:IAppConfigurtaion) => { + return new PunchOutDirective(sdcConfig); + }; + +} + +PunchOutDirective.factory.$inject = ['sdcConfig']; diff --git a/catalog-ui/src/app/directives/sdc-tabs/sdc-single-tab/sdc-single-tab-directive.ts b/catalog-ui/src/app/directives/sdc-tabs/sdc-single-tab/sdc-single-tab-directive.ts new file mode 100644 index 0000000000..a41d9c59e4 --- /dev/null +++ b/catalog-ui/src/app/directives/sdc-tabs/sdc-single-tab/sdc-single-tab-directive.ts @@ -0,0 +1,46 @@ +'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() { + } + + scope = { + singleTab: "=", + isViewOnly: "=" + }; + + replace = true; + restrict = 'A'; + controller = '@'; + template = '<div ng-include src="singleTab.templateUrl"></div>'; + + public static factory = ()=> { + return new InnerSdcSingleTabDirective(); + }; +} + +SdcSingleTabDirective.factory.$inject = ['$compile', '$parse']; +InnerSdcSingleTabDirective.factory.$inject = []; + diff --git a/catalog-ui/src/app/directives/sdc-tabs/sdc-single-tab/sdc-single-tab.less b/catalog-ui/src/app/directives/sdc-tabs/sdc-single-tab/sdc-single-tab.less new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/catalog-ui/src/app/directives/sdc-tabs/sdc-single-tab/sdc-single-tab.less @@ -0,0 +1 @@ + diff --git a/catalog-ui/src/app/directives/sdc-tabs/sdc-tabs-directive-view.html b/catalog-ui/src/app/directives/sdc-tabs/sdc-tabs-directive-view.html new file mode 100644 index 0000000000..4dc71b8780 --- /dev/null +++ b/catalog-ui/src/app/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-show="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/src/app/directives/sdc-tabs/sdc-tabs-directive.ts b/catalog-ui/src/app/directives/sdc-tabs/sdc-tabs-directive.ts new file mode 100644 index 0000000000..1567bfba53 --- /dev/null +++ b/catalog-ui/src/app/directives/sdc-tabs/sdc-tabs-directive.ts @@ -0,0 +1,48 @@ +/** + * Created by obarda on 7/28/2016. + */ +'use strict'; +import {Tab} from "app/models"; + +export interface ISdcTabsDirectiveScope extends ng.IScope { + tabs:Array<Tab>; + selectedTab:Tab; + isActive:boolean; + onTabSelected(selectedTab:Tab); +} + +export class SdcTabsDirective implements ng.IDirective { + + constructor() { + } + + scope = { + tabs: "=", + selectedTab: "=?", + isViewOnly: "=" + }; + + replace = true; + restrict = 'E'; + template = ():string => { + return require('./sdc-tabs-directive-view.html'); + }; + + link = (scope:ISdcTabsDirectiveScope) => { + scope.isActive = true; + + if (!scope.selectedTab) { + scope.selectedTab = scope.tabs[0]; + } + + scope.onTabSelected = (selectedTab:Tab) => { + scope.selectedTab = selectedTab; + } + }; + + public static factory = ()=> { + return new SdcTabsDirective(); + }; +} + +SdcTabsDirective.factory.$inject = []; diff --git a/catalog-ui/src/app/directives/sdc-tabs/sdc-tabs.less b/catalog-ui/src/app/directives/sdc-tabs/sdc-tabs.less new file mode 100644 index 0000000000..15b6fe9e1d --- /dev/null +++ b/catalog-ui/src/app/directives/sdc-tabs/sdc-tabs.less @@ -0,0 +1,67 @@ +.sdc-tabs-body { + height: 100%; + width: 330px; + .sdc-tabs { + display: inline-block; + width: 40px; + vertical-align: top; + position: relative; + z-index: 99; + + .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: 0px; + } + + .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); + + .sdc-single-tab-content-body { + height: 100%; + display: flex; + } + } +} diff --git a/catalog-ui/src/app/directives/select-property-types/select-data-type-fields-structure/select-data-type-fields-structure.html b/catalog-ui/src/app/directives/select-property-types/select-data-type-fields-structure/select-data-type-fields-structure.html new file mode 100644 index 0000000000..8560e66978 --- /dev/null +++ b/catalog-ui/src/app/directives/select-property-types/select-data-type-fields-structure/select-data-type-fields-structure.html @@ -0,0 +1,81 @@ +<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" data-ng-init="property.isAlreadyInput = isAlreadyInput(property)"> + + <sdc-radio-button sdc-model="dataTypesService.selectedPropertiesName" value="{{path + '#' + property.name}}" data-ng-if="path && !property.isAlreadyInput" + disabled="false" elem-name="selectedPropertiesName" elem-id="{{path + '#' + property.name}}" class="selectPropertyType" + on-value-change="setSelectedType(property)" data-tests-id="propertyRadioButton_{{property.name}}"></sdc-radio-button> + <div class="existInputContainer" data-ng-if="property.isAlreadyInput"><span class="existInput"></span></div> + <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 data-ng-if="dataTypesService.isDataTypeForDataTypePropertyType(property)" class="inner-structure"> + <select-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]}}" + path="{{path + '#' + property.name}}" + is-parent-already-input="isParentAlreadyInput" + ></select-fields-structure> + + </div> + <div data-ng-if="!dataTypesService.isDataTypeForDataTypePropertyType(property)" ng-switch="property.type"> + <div ng-switch-when="map"> + <select-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]}}" + ></select-type-map> + </div> + <div ng-switch-when="list"> + <select-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]}}" + ></select-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-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> + </div> + </div> + </div> +</div> diff --git a/catalog-ui/src/app/directives/select-property-types/select-data-type-fields-structure/select-data-type-fields-structure.less b/catalog-ui/src/app/directives/select-property-types/select-data-type-fields-structure/select-data-type-fields-structure.less new file mode 100644 index 0000000000..43d2d646a1 --- /dev/null +++ b/catalog-ui/src/app/directives/select-property-types/select-data-type-fields-structure/select-data-type-fields-structure.less @@ -0,0 +1,117 @@ + +.selectPropertyType { + .tlv-radio-label { + margin-top: -7px; + } +} + +.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; + } + } + + } + + .existInputContainer { + height: 30px; + width: 20px; + position: relative; + } + .existInput { + .sprite-new; + .sdc-success; + position: absolute; + top: 18px; + left: 3px; + } + + .data-type-name { + .m_16_m; + margin-left: 22px; + } + + .i-sdc-form-input:disabled{ + .disabled; + &[type="text"]{ + opacity: 1 !important; + background-color: @tlv_color_t; + color:black; + } + } + + .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/src/app/directives/select-property-types/select-data-type-fields-structure/select-data-type-fields-structure.ts b/catalog-ui/src/app/directives/select-property-types/select-data-type-fields-structure/select-data-type-fields-structure.ts new file mode 100644 index 0000000000..aee4b3b6af --- /dev/null +++ b/catalog-ui/src/app/directives/select-property-types/select-data-type-fields-structure/select-data-type-fields-structure.ts @@ -0,0 +1,184 @@ +/** + * Created by obarda on 1/27/2016. + */ +'use strict'; +import {ValidationUtils} from "app/utils"; +import { DataTypesService } from "app/services"; +import { DataTypePropertyModel } from "app/models/data-type-properties"; +import {DataTypesMap, PropertyModel} from "app/models"; + +export interface ISelectDataTypeFieldsStructureScope extends ng.IScope { + parentFormObj:ng.IFormController; + dataTypeProperties:Array<DataTypePropertyModel>; + typeName:string; + valueObjRef:any; + propertyNameValidationPattern:RegExp; + fieldsPrefixName:string; + readOnly:boolean; + currentTypeDefaultValue:any; + types:DataTypesMap; + expandByDefault:boolean; + expand:boolean; + expanded:boolean; + dataTypesService:DataTypesService; + path:string; + isParentAlreadyInput:boolean; + + expandAndCollapse():void; + getValidationPattern(type:string):RegExp; + validateIntRange(value:string):boolean; + isAlreadyInput(property:PropertyModel):boolean; + setSelectedType(property:PropertyModel):void; + onValueChange(propertyName:string, type:string):void; +} + + +export class SelectDataTypeFieldsStructureDirective implements ng.IDirective { + + constructor(private DataTypesService:DataTypesService, + private PropertyNameValidationPattern:RegExp, + private ValidationUtils:ValidationUtils) { + } + + scope = { + valueObjRef: '=', + typeName: '=', + parentFormObj: '=', + fieldsPrefixName: '=', + readOnly: '=', + defaultValue: '@', + expandByDefault: '=', + path: '@', + isParentAlreadyInput: '=' + }; + + restrict = 'E'; + replace = true; + template = ():string => { + return require('./select-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<DataTypePropertyModel>):any => { + let defaultValue = {}; + for (let i = 0; i < dataTypeProperties.length; i++) { + if (dataTypeProperties[i].type != 'string') { + if (!angular.isUndefined(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:ISelectDataTypeFieldsStructureScope, $attr:any):void => { + scope.dataTypesService = this.DataTypesService; + scope.dataTypeProperties = angular.copy(this.DataTypesService.getFirsLevelOfDataTypeProperties(scope.typeName)); + 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 (angular.isUndefined(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:ISelectDataTypeFieldsStructureScope, 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); + }; + + /* + check if property is alrady declered on the service by meatching the input name & the property name + + */ + scope.isAlreadyInput = (property:PropertyModel):boolean => { + if (scope.path) { + if (scope.isParentAlreadyInput) { + return true; + } + let parentInputName = this.DataTypesService.selectedInstance.normalizedName + '_' + scope.path.replace('#', '_');// set the input parent as he need to declared as input + let inputName = parentInputName + '_' + property.name;// set the input name as he need to declared as input + let selectedProperty = _.find(this.DataTypesService.selectedComponentInputs, (componentInput)=> { + if (componentInput.name == parentInputName) { //check if the parent(all the complex) is already declared + scope.isParentAlreadyInput = true; + return true; + } else if (componentInput.name.substring(0, inputName.length) == inputName) { //check if specific property inside the complex + return true; + } + //return componentInput.name == parentInputName || componentInput.name.substring(0,inputName.length) == inputName;//check if the parent(all the complex) is already declared or specific property inside the complex + }); + if (selectedProperty) { + return true; + } + } + return false; + }; + + scope.setSelectedType = (property:PropertyModel):void=> { + scope.dataTypesService.selectedInput = property; + scope.dataTypesService.selectedPropertiesName = scope.path + '#' + property.name; + }; + + 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 = (DataTypesService:DataTypesService, + PropertyNameValidationPattern:RegExp, + ValidationUtils:ValidationUtils)=> { + return new SelectDataTypeFieldsStructureDirective(DataTypesService, PropertyNameValidationPattern, ValidationUtils); + }; +} + +SelectDataTypeFieldsStructureDirective.factory.$inject = ['Sdc.Services.DataTypesService', 'PropertyNameValidationPattern', 'ValidationUtils']; diff --git a/catalog-ui/src/app/directives/select-property-types/select-type-list/select-type-list-directive.html b/catalog-ui/src/app/directives/select-property-types/select-type-list/select-type-list-directive.html new file mode 100644 index 0000000000..f439147301 --- /dev/null +++ b/catalog-ui/src/app/directives/select-property-types/select-type-list/select-type-list-directive.html @@ -0,0 +1,56 @@ +<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-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 ng-if="!readOnly" 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"> + <select-fields-structure value-obj-ref="valueObjRef[$index]" + type-name="schemaProperty.type" + parent-form-obj="parentFormObj" + fields-prefix-name="fieldsPrefixName+''+$index" + read-only="readOnly"> + <!--path="{{path}}"--> + </select-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/src/app/directives/select-property-types/select-type-list/select-type-list-directive.less b/catalog-ui/src/app/directives/select-property-types/select-type-list/select-type-list-directive.less new file mode 100644 index 0000000000..71263f2642 --- /dev/null +++ b/catalog-ui/src/app/directives/select-property-types/select-type-list/select-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: 0 8px; + .delete-list-item{ + margin: 0 0 0 2px; + .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/src/app/directives/select-property-types/select-type-list/select-type-list-directive.ts b/catalog-ui/src/app/directives/select-property-types/select-type-list/select-type-list-directive.ts new file mode 100644 index 0000000000..d277040798 --- /dev/null +++ b/catalog-ui/src/app/directives/select-property-types/select-type-list/select-type-list-directive.ts @@ -0,0 +1,108 @@ +/** + * Created by rcohen on 9/15/2016. + */ +'use strict'; +import {DataTypesService} from "app/services/data-types-service"; +import {SchemaProperty} from "app/models/aschema-property"; +import {ValidationUtils, PROPERTY_TYPES} from "app/utils"; + +export interface ISelectTypeListScope extends ng.IScope { + parentFormObj:ng.IFormController; + schemaProperty:SchemaProperty; + isSchemaTypeDataType:boolean; + valueObjRef:any; + propertyNameValidationPattern:RegExp; + fieldsPrefixName:string; + readOnly:boolean; + listDefaultValue:any; + listNewItem:any; + maxLength:number; + dataTypesService:DataTypesService; + + getValidationPattern(type:string):RegExp; + validateIntRange(value:string):boolean; + addListItem():void; + deleteListItem(listItemIndex:number):void +} + +export class SelectTypeListDirective implements ng.IDirective { + + constructor(private DataTypesService:DataTypesService, + private PropertyNameValidationPattern:RegExp, + private ValidationUtils: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 + maxLength: '=', + path: '@' + }; + + restrict = 'E'; + replace = true; + template = ():string => { + return require('./select-type-list-directive.html'); + }; + + link = (scope:ISelectTypeListScope, element:any, $attr:any) => { + scope.dataTypesService = this.DataTypesService; + 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); + //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) == 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 = (DataTypesService:DataTypesService, + PropertyNameValidationPattern:RegExp, + ValidationUtils:ValidationUtils)=> { + return new SelectTypeListDirective(DataTypesService, PropertyNameValidationPattern, ValidationUtils); + }; +} + +SelectTypeListDirective.factory.$inject = ['Sdc.Services.DataTypesService', 'PropertyNameValidationPattern', 'ValidationUtils']; + diff --git a/catalog-ui/src/app/directives/select-property-types/select-type-map/select-type-map-directive.html b/catalog-ui/src/app/directives/select-property-types/select-type-map/select-type-map-directive.html new file mode 100644 index 0000000000..a56428e5c2 --- /dev/null +++ b/catalog-ui/src/app/directives/select-property-types/select-type-map/select-type-map-directive.html @@ -0,0 +1,56 @@ +<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" + data-ng-maxlength="50" + maxlength="50" + name="mapKey{{fieldsPrefixName}}{{$index}}" + 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> + <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-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> + <div data-ng-if="isSchemaTypeDataType" class="i-sdc-form-item map-item-field"> + <label class="i-sdc-form-label">Value</label> + <select-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"> + <!--path="{{path + '#' + mapKeys[$index]}}"--> + </select-fields-structure> + </div> + </div> +</div> + diff --git a/catalog-ui/src/app/directives/select-property-types/select-type-map/select-type-map-directive.less b/catalog-ui/src/app/directives/select-property-types/select-type-map/select-type-map-directive.less new file mode 100644 index 0000000000..2480b626f2 --- /dev/null +++ b/catalog-ui/src/app/directives/select-property-types/select-type-map/select-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/src/app/directives/select-property-types/select-type-map/select-type-map-directive.ts b/catalog-ui/src/app/directives/select-property-types/select-type-map/select-type-map-directive.ts new file mode 100644 index 0000000000..19df8dfb71 --- /dev/null +++ b/catalog-ui/src/app/directives/select-property-types/select-type-map/select-type-map-directive.ts @@ -0,0 +1,139 @@ +/** + * Created by rcohen on 9/15/2016. + */ +'use strict'; +import {SchemaProperty} from "app/models"; +import {DataTypesService} from "app/services"; +import {ValidationUtils, PROPERTY_TYPES} from "app/utils"; + +export interface ISelectTypeMapScope extends ng.IScope { + parentFormObj:ng.IFormController; + schemaProperty:SchemaProperty; + isSchemaTypeDataType:boolean; + valueObjRef:any; + mapKeys:Array<string>;//array of map keys + MapKeyValidationPattern:RegExp; + fieldsPrefixName:string; + readOnly:boolean; + mapDefaultValue:any; + maxLength:number; + dataTypesService:DataTypesService; + + 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 SelectTypeMapDirective implements ng.IDirective { + + constructor(private DataTypesService:DataTypesService, + private MapKeyValidationPattern:RegExp, + private ValidationUtils: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 + maxLength: '=', + path: '@' + }; + + restrict = 'E'; + replace = true; + template = ():string => { + return require('./select-type-map-directive.html'); + }; + + link = (scope:ISelectTypeMapScope, element:any, $attr:any) => { + + scope.dataTypesService = this.DataTypesService; + scope.MapKeyValidationPattern = this.MapKeyValidationPattern; + + //reset valueObjRef and mapKeys when schema type is changed + scope.$watchCollection('schemaProperty.type', (newData:any):void => { + scope.isSchemaTypeDataType = this.DataTypesService.isDataTypeForSchemaType(scope.schemaProperty); + 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]; + let existsKeyIndex = Object.keys(scope.valueObjRef).indexOf(newKey); + if (existsKeyIndex > -1 && existsKeyIndex != index) { + 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 != PROPERTY_TYPES.STRING) { + objectOfValues[locationInObj] = JSON.parse(objectOfValues[locationInObj]); + } + } + }; + + public static factory = (DataTypesService:DataTypesService, + MapKeyValidationPattern:RegExp, + ValidationUtils:ValidationUtils, + $timeout:ng.ITimeoutService)=> { + return new SelectTypeMapDirective(DataTypesService, MapKeyValidationPattern, ValidationUtils, $timeout); + }; +} + +SelectTypeMapDirective.factory.$inject = ['Sdc.Services.DataTypesService', 'MapKeyValidationPattern', 'ValidationUtils', '$timeout']; diff --git a/catalog-ui/src/app/directives/structure-tree/structure-tree-directive.html b/catalog-ui/src/app/directives/structure-tree/structure-tree-directive.html new file mode 100644 index 0000000000..7d8a883b33 --- /dev/null +++ b/catalog-ui/src/app/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/src/app/directives/structure-tree/structure-tree-directive.less b/catalog-ui/src/app/directives/structure-tree/structure-tree-directive.less new file mode 100644 index 0000000000..094c3f70ba --- /dev/null +++ b/catalog-ui/src/app/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/src/app/directives/structure-tree/structure-tree-directive.ts b/catalog-ui/src/app/directives/structure-tree/structure-tree-directive.ts new file mode 100644 index 0000000000..4d6fbee4db --- /dev/null +++ b/catalog-ui/src/app/directives/structure-tree/structure-tree-directive.ts @@ -0,0 +1,195 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ +'use strict'; +import {RelationshipModel, Component, ComponentInstance} from "app/models"; +import {Dictionary} from "app/utils"; + + +export interface IStructureTreeScope extends ng.IScope { + + component: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() { + } + + scope = { + component: '=', + }; + restrict = 'E'; + template = ():string => { + return require('./structure-tree-directive.html'); + }; + + link = (scope:IStructureTreeScope, $elem:any) => { + + let RESOURCE_INSTANCE_LIST:string = "resourceInstancesChildesList"; + let resourceInstanceMap:Dictionary<string, ResourceInstanceNode>; + let relations:Array<RelationshipModel>; + //************* Start Building Tree Functions *******************// + + //remove unnecessary instances + let initResourceInstanceMap = ():void => { + + resourceInstanceMap = new Dictionary<string, ResourceInstanceNode>(); + + _.forEach(scope.component.componentInstances, (resourceInstance: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: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 = () => { + return new StructureTreeDirective(); + }; +} + +StructureTreeDirective.factory.$inject = []; diff --git a/catalog-ui/src/app/directives/tag/tag-directive.html b/catalog-ui/src/app/directives/tag/tag-directive.html new file mode 100644 index 0000000000..28c22a7978 --- /dev/null +++ b/catalog-ui/src/app/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/src/app/directives/tag/tag-directive.less b/catalog-ui/src/app/directives/tag/tag-directive.less new file mode 100644 index 0000000000..f72e366ac6 --- /dev/null +++ b/catalog-ui/src/app/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/src/app/directives/tag/tag-directive.ts b/catalog-ui/src/app/directives/tag/tag-directive.ts new file mode 100644 index 0000000000..77a26fc6f7 --- /dev/null +++ b/catalog-ui/src/app/directives/tag/tag-directive.ts @@ -0,0 +1,49 @@ +'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() { + } + + scope = { + tagData: '=', + onDelete: '&', + hideTooltip: '=', + hideDelete: '=', + sdcDisable: '=' + }; + + replace = true; + restrict = 'EA'; + template = ():string => { + return require('./tag-directive.html'); + }; + + link = (scope:ITagScope) => { + scope.delete = ()=> { + scope.onDelete({'uniqueId': scope.tagData.id}); + } + }; + + public static factory = ()=> { + return new TagDirective(); + }; + +} + +TagDirective.factory.$inject = []; diff --git a/catalog-ui/src/app/directives/user-header-details/user-header-details-directive.html b/catalog-ui/src/app/directives/user-header-details/user-header-details-directive.html new file mode 100644 index 0000000000..1c99a18ab5 --- /dev/null +++ b/catalog-ui/src/app/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/src/app/directives/user-header-details/user-header-details-directive.less b/catalog-ui/src/app/directives/user-header-details/user-header-details-directive.less new file mode 100644 index 0000000000..e432581f3b --- /dev/null +++ b/catalog-ui/src/app/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('/assets/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/src/app/directives/user-header-details/user-header-details-directive.ts b/catalog-ui/src/app/directives/user-header-details/user-header-details-directive.ts new file mode 100644 index 0000000000..a1a54b0517 --- /dev/null +++ b/catalog-ui/src/app/directives/user-header-details/user-header-details-directive.ts @@ -0,0 +1,52 @@ +'use strict'; +import {User, IUser, IAppConfigurtaion} from "app/models"; +import {IUserResourceClass, IUserResource} from "app/services"; +export interface IUserHeaderDetailsScope extends ng.IScope { + name:string; + role:string; + iconUrl:string; + UserResourceClass:IUserResourceClass; + user:IUser; + sdcConfig:IAppConfigurtaion; + initUser:Function; +} + +export class UserHeaderDetailsDirective implements ng.IDirective { + + constructor(private $http:ng.IHttpService, private sdcConfig:IAppConfigurtaion, private UserResourceClass:IUserResourceClass) { + } + + scope = { + iconUrl: '=?' + }; + + replace = true; + restrict = 'E'; + template = ():string => { + return require('./user-header-details-directive.html'); + }; + + link = (scope:IUserHeaderDetailsScope) => { + + scope.initUser = ():void => { + let defaultUserId:string; + let user: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 User(user); + }); + } else { + scope.user = new User(user); + } + }; + scope.initUser(); + }; + + public static factory = ($http:ng.IHttpService, sdcConfig:IAppConfigurtaion, UserResourceClass:IUserResourceClass)=> { + return new UserHeaderDetailsDirective($http, sdcConfig, UserResourceClass); + }; + +} + +UserHeaderDetailsDirective.factory.$inject = ['$http', 'sdcConfig', 'Sdc.Services.UserResourceService']; diff --git a/catalog-ui/src/app/directives/utils/expand-collapse-list-header/expand-collapse-list-header.html b/catalog-ui/src/app/directives/utils/expand-collapse-list-header/expand-collapse-list-header.html new file mode 100644 index 0000000000..b351273c08 --- /dev/null +++ b/catalog-ui/src/app/directives/utils/expand-collapse-list-header/expand-collapse-list-header.html @@ -0,0 +1,19 @@ +<div class="main-wrap" data-tests-id="list-of-{{title}}"> + <div class="header"> + <div class="title">{{title}}</div> + <div class="buttons"> + <span class="order-by hand sprite-new" + data-ng-show="expandCollapseListData.expandCollapse" + data-ng-class="{'asc':!desc, 'desc':desc }" + data-ng-click="swapOrderBy()"></span> + <span class="search hand sprite-new search-icon" + data-ng-class="{'selected':showSearchBox}" + data-ng-show="expandCollapseListData.expandCollapse" + data-ng-click="showHideSearchBox()"></span> + <span class="hand sprite-new expand-list" + data-ng-class="{'open':expandCollapseListData.expandCollapse}" + data-ng-click="expandCollapseListData.expandCollapse=!expandCollapseListData.expandCollapse"></span> + </div> + </div> + <input type="text" id="list-search-box" class="search-box" data-ng-if="expandCollapseListData.expandCollapse && showSearchBox" data-ng-model="expandCollapseListData.filter" autofocus/> +</div> diff --git a/catalog-ui/src/app/directives/utils/expand-collapse-list-header/expand-collapse-list-header.less b/catalog-ui/src/app/directives/utils/expand-collapse-list-header/expand-collapse-list-header.less new file mode 100644 index 0000000000..2a80f28007 --- /dev/null +++ b/catalog-ui/src/app/directives/utils/expand-collapse-list-header/expand-collapse-list-header.less @@ -0,0 +1,43 @@ +.main-wrap{ + text-align: center; + .header{ + background-color: @tlv_color_w; + height: 42px; + border-bottom: 1px solid rgba(0, 159, 219, 0.6); + border-top: 1px solid rgba(0, 159, 219, 0.6); + .title{ + .f-type._14_m; + color: @main_color_l; + font-weight: 400; + line-height: 42px; + padding-left: 18px; + float: left; + } + .buttons{ + float: right; + padding-right: 11px; + line-height: 42px; + span{ + vertical-align: middle; + } + .search,.order-by{ + margin-right: 5px; + } + //temporary/// + .search{ + display: none; + } + ////////////// + } + } + + .search-box{ + border-radius: 2px; + height: 30px; + width:275px; + border: 1px @tlv_color_x solid; + margin: 8px 0; + padding: 0 5px; + } +} + diff --git a/catalog-ui/src/app/directives/utils/expand-collapse-list-header/expand-collapse-list-header.ts b/catalog-ui/src/app/directives/utils/expand-collapse-list-header/expand-collapse-list-header.ts new file mode 100644 index 0000000000..73cb2def0b --- /dev/null +++ b/catalog-ui/src/app/directives/utils/expand-collapse-list-header/expand-collapse-list-header.ts @@ -0,0 +1,66 @@ +/** + * Created by rcohen on 12/5/2016. + */ +'use strict'; + +export class ExpandCollapseListData { + filter:string;//variable for filter text + orderByField:string;//order by field name + expandCollapse:boolean;//boolean param for expand collapse the list +} + +export interface IExpandCollapseListHeaderScope extends ng.IScope { + title:string;//the title on the header + expandCollapseListData:ExpandCollapseListData; + showSearchBox:boolean; + desc:boolean;//order by desc or asc + + swapOrderBy():void; + showHideSearchBox():void; +} + +export class ExpandCollapseListHeaderDirective implements ng.IDirective { + + constructor(private $timeout:ng.ITimeoutService) { + } + + scope = { + title: '@', + expandCollapseListData: '=' + }; + + public replace = false; + public restrict = 'AE'; + public transclude = true; + + template = ():string => { + return require('./expand-collapse-list-header.html'); + }; + + link = (scope:IExpandCollapseListHeaderScope, $elem:any) => { + scope.swapOrderBy = ():void => { + if (scope.expandCollapseListData.orderByField.charAt(0) === '-') { + scope.expandCollapseListData.orderByField = scope.expandCollapseListData.orderByField.substr(1); + } else { + scope.expandCollapseListData.orderByField = '-' + scope.expandCollapseListData.orderByField; + } + scope.desc = !scope.desc; + }; + + scope.showHideSearchBox = ():void => { + scope.showSearchBox = !scope.showSearchBox; + if (scope.showSearchBox) { + this.$timeout(function () { + angular.element("#list-search-box").focus(); + }, 0); + } + }; + }; + + public static factory = ($timeout:ng.ITimeoutService)=> { + return new ExpandCollapseListHeaderDirective($timeout); + }; + +} + +ExpandCollapseListHeaderDirective.factory.$inject = ['$timeout']; diff --git a/catalog-ui/src/app/directives/utils/expand-collapse-menu-box/expand-collaps-menu-box.ts b/catalog-ui/src/app/directives/utils/expand-collapse-menu-box/expand-collaps-menu-box.ts new file mode 100644 index 0000000000..2a2432fcea --- /dev/null +++ b/catalog-ui/src/app/directives/utils/expand-collapse-menu-box/expand-collaps-menu-box.ts @@ -0,0 +1,47 @@ +'use strict'; +import {MenuItem, MenuItemGroup} from "app/utils"; + +export interface IExpandCollapseMenuBoxDirectiveScope extends ng.IScope { + menuItemsGroup:MenuItemGroup; + menuTitle:string; + parentScope:ng.IScope; + onMenuItemClick(menuItem:MenuItem):void; +} + +export class ExpandCollapseMenuBoxDirective implements ng.IDirective { + + constructor() { + } + + scope = { + menuTitle: '@', + menuItemsGroup: '=', + parentScope: '=' + }; + + public replace = false; + public restrict = 'AE'; + public transclude = true; + + template = ():string => { + return require('./expand-collapse-menu-box.html'); + }; + + link = (scope:IExpandCollapseMenuBoxDirectiveScope) => { + scope.onMenuItemClick = (menuItem: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 = ()=> { + return new ExpandCollapseMenuBoxDirective(); + }; + +} + +ExpandCollapseMenuBoxDirective.factory.$inject = []; diff --git a/catalog-ui/src/app/directives/utils/expand-collapse-menu-box/expand-collapse-menu-box.html b/catalog-ui/src/app/directives/utils/expand-collapse-menu-box/expand-collapse-menu-box.html new file mode 100644 index 0000000000..f90f00f230 --- /dev/null +++ b/catalog-ui/src/app/directives/utils/expand-collapse-menu-box/expand-collapse-menu-box.html @@ -0,0 +1,17 @@ +<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-header"> + <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> + </div> + </expand-collapse> + + <div class="w-sdc-designer-sidebar-section-content" > + <div class="i-sdc-designer-sidebar-section-content-item expand-collapse-menu-box-item second-level" + 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/src/app/directives/utils/expand-collapse-menu-box/expand-collapse-menu-box.less b/catalog-ui/src/app/directives/utils/expand-collapse-menu-box/expand-collapse-menu-box.less new file mode 100644 index 0000000000..13ec1c99be --- /dev/null +++ b/catalog-ui/src/app/directives/utils/expand-collapse-menu-box/expand-collapse-menu-box.less @@ -0,0 +1,67 @@ +.expand-collapse-menu-box { + line-height: 20px; + //padding: 13px 0px 5px 10px; + background-color: @tlv_color_t; + //margin: 3px 3px 5px 0px; + + .expand-collapse-header{ + border-bottom: 1px solid @main_color_o; + height: @action_nav_height; + background-color:@main_color_p; + display:flex; + align-items:center; + } + + .expand-collapse-menu-box-title { + .f-type._18_m; + color: @main_color_a; + //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; + padding: 8px 0; + } + + &.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; + vertical-align: text-top; + margin-left: 14px; + vertical-align: 14px; + } + .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; + background-color: #009fdb; + color: #ffffff; + } + + } + } +} diff --git a/catalog-ui/src/app/directives/utils/expand-collapse/expand-collapse.html b/catalog-ui/src/app/directives/utils/expand-collapse/expand-collapse.html new file mode 100644 index 0000000000..a2358ea2b7 --- /dev/null +++ b/catalog-ui/src/app/directives/utils/expand-collapse/expand-collapse.html @@ -0,0 +1 @@ +<ng-transclude></ng-transclude> diff --git a/catalog-ui/src/app/directives/utils/expand-collapse/expand-collapse.less b/catalog-ui/src/app/directives/utils/expand-collapse/expand-collapse.less new file mode 100644 index 0000000000..e6b7b7d516 --- /dev/null +++ b/catalog-ui/src/app/directives/utils/expand-collapse/expand-collapse.less @@ -0,0 +1,11 @@ +.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/src/app/directives/utils/expand-collapse/expand-collapse.ts b/catalog-ui/src/app/directives/utils/expand-collapse/expand-collapse.ts new file mode 100644 index 0000000000..3993f06036 --- /dev/null +++ b/catalog-ui/src/app/directives/utils/expand-collapse/expand-collapse.ts @@ -0,0 +1,115 @@ +'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() { + } + + scope = { + expandedSelector: '@', + loadDataFunction: '&?', + isCloseOnInit: '=?' + }; + + public replace = false; + public restrict = 'AE'; + public transclude = true; + + template = ():string => { + return require('./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(); + // }); + $elem.bind('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 = ()=> { + return new ExpandCollapseDirective(); + }; +} + +ExpandCollapseDirective.factory.$inject = []; diff --git a/catalog-ui/src/app/directives/utils/sdc-keyboard-events/sdc-keyboard-events.ts b/catalog-ui/src/app/directives/utils/sdc-keyboard-events/sdc-keyboard-events.ts new file mode 100644 index 0000000000..71a963a492 --- /dev/null +++ b/catalog-ui/src/app/directives/utils/sdc-keyboard-events/sdc-keyboard-events.ts @@ -0,0 +1,84 @@ +'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/src/app/directives/utils/sdc-tags/sdc-tags.html b/catalog-ui/src/app/directives/utils/sdc-tags/sdc-tags.html new file mode 100644 index 0000000000..fb1ada69c3 --- /dev/null +++ b/catalog-ui/src/app/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/src/app/directives/utils/sdc-tags/sdc-tags.less b/catalog-ui/src/app/directives/utils/sdc-tags/sdc-tags.less new file mode 100644 index 0000000000..942196e663 --- /dev/null +++ b/catalog-ui/src/app/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/src/app/directives/utils/sdc-tags/sdc-tags.ts b/catalog-ui/src/app/directives/utils/sdc-tags/sdc-tags.ts new file mode 100644 index 0000000000..082a77dd9f --- /dev/null +++ b/catalog-ui/src/app/directives/utils/sdc-tags/sdc-tags.ts @@ -0,0 +1,75 @@ +'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() { + } + + scope = { + tags: '=', + specialTag: '=', + pattern: '=', + sdcDisabled: '=', + formElement: '=', + elementName: '@', + maxTags: '@' + }; + + public replace = false; + public restrict = 'E'; + public transclude = false; + + template = ():string => { + return require('./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 = ()=> { + return new SdcTagsDirective(); + }; + +} + +SdcTagsDirective.factory.$inject = []; diff --git a/catalog-ui/src/app/directives/utils/smart-tooltip/smart-tooltip.ts b/catalog-ui/src/app/directives/utils/smart-tooltip/smart-tooltip.ts new file mode 100644 index 0000000000..d0177b4094 --- /dev/null +++ b/catalog-ui/src/app/directives/utils/smart-tooltip/smart-tooltip.ts @@ -0,0 +1,61 @@ +'use strict'; + +export interface ISmartTooltipScope extends ng.IScope { + sdcSmartToolip; +} + +export class SmartTooltipDirective implements ng.IDirective { + + constructor(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 = ($compile:ng.ICompileService)=> { + return new SmartTooltipDirective($compile); + }; +} + +SmartTooltipDirective.factory.$inject = ['$compile']; diff --git a/catalog-ui/src/app/directives/utils/validation-on-load/validation-on-load.ts b/catalog-ui/src/app/directives/utils/validation-on-load/validation-on-load.ts new file mode 100644 index 0000000000..d489efa928 --- /dev/null +++ b/catalog-ui/src/app/directives/utils/validation-on-load/validation-on-load.ts @@ -0,0 +1,48 @@ +'use strict'; + +export interface IValidationOnLoadScope extends ng.IScope { + formToValidate:ng.IFormController; +} + +export class ValidationOnLoadDirective implements ng.IDirective { + + constructor(private $timeout:ng.ITimeoutService) { + } + + scope = { + formToValidate: '=' + }; + + public replace = false; + public restrict = 'A'; + + + public link = (scope:IValidationOnLoadScope, $elem:ng.IAugmentedJQuery, $attrs:angular.IAttributes) => { + + let init = ()=> { + //validate errors + if (scope.formToValidate.$error) { + angular.forEach(scope.formToValidate.$error, (value, key)=> { + //skip on the required error if its a new form + if (key != 'required') { + angular.forEach(value, function (field) { + field.$setDirty();//trigger to show the error label + }); + } + }) + } + }; + + this.$timeout(()=> { + init(); + }, 0); + + }; + + public static factory = ($timeout:ng.ITimeoutService)=> { + return new ValidationOnLoadDirective($timeout); + }; + +} + +ValidationOnLoadDirective.factory.$inject = ['$timeout']; diff --git a/catalog-ui/src/app/filters.ts b/catalog-ui/src/app/filters.ts new file mode 100644 index 0000000000..3a71c73289 --- /dev/null +++ b/catalog-ui/src/app/filters.ts @@ -0,0 +1,15 @@ +/** + * Created by ob0695 on 2/26/2017. + */ +export * from './filters/catalog-status-filter'; +export * from './filters/category-type-filter'; +export * from './filters/clear-whitespaces-filter'; +export * from './filters/entity-filter'; +export * from './filters/graph-resource-name-filter'; +export * from './filters/resource-name-filter'; +export * from './filters/resource-type-filter'; +export * from './filters/string-to-date-filter'; +export * from './filters/tests-id-filter'; +export * from './filters/trim-filter'; +export * from './filters/truncate-filter'; + diff --git a/catalog-ui/src/app/filters/catalog-status-filter.ts b/catalog-ui/src/app/filters/catalog-status-filter.ts new file mode 100644 index 0000000000..c28ec1d1e5 --- /dev/null +++ b/catalog-ui/src/app/filters/catalog-status-filter.ts @@ -0,0 +1,18 @@ +/** + * Created by obarda on 19/08/2015. + */ +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/src/app/filters/category-type-filter.ts b/catalog-ui/src/app/filters/category-type-filter.ts new file mode 100644 index 0000000000..66663263c3 --- /dev/null +++ b/catalog-ui/src/app/filters/category-type-filter.ts @@ -0,0 +1,28 @@ +import {ComponentType} from "../utils/constants"; +import {CacheService} from "../services/cache-service"; +export class CategoryTypeFilter { + + static $inject = ['Sdc.Services.CacheService']; + + constructor(cacheService:CacheService) { + let filter = <CategoryTypeFilter>(categories:any, selectedType:Array<string>, selectedSubResourceTypes:Array<string>) => { + + if (selectedType.indexOf(ComponentType.RESOURCE) === -1 && selectedSubResourceTypes.length > 0) { + selectedType = selectedType.concat([ComponentType.RESOURCE]); + } + + 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/src/app/filters/clear-whitespaces-filter.ts b/catalog-ui/src/app/filters/clear-whitespaces-filter.ts new file mode 100644 index 0000000000..ea6129e22b --- /dev/null +++ b/catalog-ui/src/app/filters/clear-whitespaces-filter.ts @@ -0,0 +1,16 @@ +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/src/app/filters/entity-filter.ts b/catalog-ui/src/app/filters/entity-filter.ts new file mode 100644 index 0000000000..97d9b85f9b --- /dev/null +++ b/catalog-ui/src/app/filters/entity-filter.ts @@ -0,0 +1,94 @@ +import {Component, Resource} from "../models"; +export class EntityFilter { + + constructor() { + + let filter = <EntityFilter>( (components:Array<Component>, filter:any) => { + + let filteredComponents:Array<Component> = components; + + // filter by type + // -------------------------------------------------------------------------- + if ((filter.selectedComponentTypes && filter.selectedComponentTypes.length > 0) || (filter.selectedResourceSubTypes && filter.selectedResourceSubTypes.length > 0)) { + let filteredTypes = []; + angular.forEach(components, (component: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:Resource = <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: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: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/src/app/filters/graph-resource-name-filter.ts b/catalog-ui/src/app/filters/graph-resource-name-filter.ts new file mode 100644 index 0000000000..a4698612f6 --- /dev/null +++ b/catalog-ui/src/app/filters/graph-resource-name-filter.ts @@ -0,0 +1,22 @@ +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/src/app/filters/resource-name-filter.ts b/catalog-ui/src/app/filters/resource-name-filter.ts new file mode 100644 index 0000000000..cd0189dc9a --- /dev/null +++ b/catalog-ui/src/app/filters/resource-name-filter.ts @@ -0,0 +1,21 @@ +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/src/app/filters/resource-type-filter.ts b/catalog-ui/src/app/filters/resource-type-filter.ts new file mode 100644 index 0000000000..f2d06f0edd --- /dev/null +++ b/catalog-ui/src/app/filters/resource-type-filter.ts @@ -0,0 +1,17 @@ +import {CacheService} from "../services/cache-service"; + +export class ResourceTypeFilter { + static '$inject' = ['Sdc.Services.CacheService']; + + constructor(cacheService: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/src/app/filters/string-to-date-filter.ts b/catalog-ui/src/app/filters/string-to-date-filter.ts new file mode 100644 index 0000000000..846180a2cc --- /dev/null +++ b/catalog-ui/src/app/filters/string-to-date-filter.ts @@ -0,0 +1,12 @@ +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/src/app/filters/tests-id-filter.ts b/catalog-ui/src/app/filters/tests-id-filter.ts new file mode 100644 index 0000000000..0e5af31169 --- /dev/null +++ b/catalog-ui/src/app/filters/tests-id-filter.ts @@ -0,0 +1,11 @@ +export class TestsIdFilter { + + constructor() { + let filter = <TestsIdFilter>( (testId:string) => { + return testId.replace(/\s/g, '_').toLowerCase(); + }); + + return filter; + } +} + diff --git a/catalog-ui/src/app/filters/trim-filter.ts b/catalog-ui/src/app/filters/trim-filter.ts new file mode 100644 index 0000000000..f174034748 --- /dev/null +++ b/catalog-ui/src/app/filters/trim-filter.ts @@ -0,0 +1,15 @@ +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/src/app/filters/truncate-filter.ts b/catalog-ui/src/app/filters/truncate-filter.ts new file mode 100644 index 0000000000..e660871e3b --- /dev/null +++ b/catalog-ui/src/app/filters/truncate-filter.ts @@ -0,0 +1,26 @@ +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/src/app/models.ts b/catalog-ui/src/app/models.ts new file mode 100644 index 0000000000..5b286d0667 --- /dev/null +++ b/catalog-ui/src/app/models.ts @@ -0,0 +1,84 @@ +/** + * Created by ob0695 on 2/23/2017. + */ +import from = require("core-js/fn/array/from"); +export * from './models/activity'; +export * from './models/additional-information'; +export * from './models/app-config'; +export * from './models/validation-config'; +export * from './models/artifacts'; +export * from './models/aschema-property'; +export * from './models/schema-attribute'; +export * from './models/attributes'; +export * from './models/capability'; +export * from './models/category'; +export * from './models/comments'; +export * from './models/components/component'; +export * from './models/components/displayComponent'; +export * from './models/components/product'; +export * from './models/components/resource'; +export * from './models/components/service'; +export * from './models/componentsInstances/componentInstance'; +export * from './models/componentsInstances/productInstance'; +export * from './models/componentsInstances/resourceInstance'; +export * from './models/componentsInstances/serviceInstance'; +export * from './models/csar-component'; +//export * from './models/data-type-properties'; +export * from './models/properties-inputs/property-be-model'; +export * from './models/properties-inputs/property-fe-model'; +export * from './models/properties-inputs/property-fe-map'; +export * from './models/properties-inputs/derived-fe-property'; +export * from './models/properties-inputs/input-fe-model'; +export * from './models/data-types-map'; +export * from './models/data-types'; +export * from './models/distribution'; +export * from './models/export-excel'; +export * from './models/file-download'; +export * from './models/graph/graph-links/common-base-link'; +export * from './models/graph/graph-links/common-ci-link-base'; +export * from './models/graph/graph-links/composition-graph-links/composition-ci-link-base'; +export * from './models/graph/graph-links/composition-graph-links/composition-ci-simple-link'; +export * from './models/graph/graph-links/composition-graph-links/composition-ci-ucpe-host-link'; +export * from './models/graph/graph-links/composition-graph-links/composition-ci-ucpe-link'; +export * from './models/graph/graph-links/composition-graph-links/composition-ci-vl-link'; +export * from './models/graph/graph-links/composition-graph-links/composition-ci-vl-ucpe-link'; +export * from './models/graph/graph-links/links-factory'; +export * from './models/graph/graph-links/module-graph-links/module-ci-link-base'; +export * from './models/graph/graph-links/module-graph-links/module-ci-vl-link'; +export * from './models/graph/graphTooltip'; +export * from './models/graph/assetPopoverObj'; +export * from './models/graph/link-menu'; +export * from './models/graph/match-relation'; +export * from './models/graph/nodes/base-common-node'; +export * from './models/graph/nodes/common-ci-node-base'; +export * from './models/graph/nodes/composition-graph-nodes/composition-ci-node-base'; +export * from './models/graph/nodes/composition-graph-nodes/composition-ci-node-cp'; +export * from './models/graph/nodes/composition-graph-nodes/composition-ci-node-service'; +export * from './models/graph/nodes/composition-graph-nodes/composition-ci-node-ucpe-cp'; +export * from './models/graph/nodes/composition-graph-nodes/composition-ci-node-ucpe'; +export * from './models/graph/nodes/composition-graph-nodes/composition-ci-node-vf'; +export * from './models/graph/nodes/composition-graph-nodes/composition-ci-node-vfc'; +export * from './models/graph/nodes/composition-graph-nodes/composition-ci-node-vl'; +export * from './models/graph/nodes/modules-graph-nodes/module-node-base'; +export * from './models/graph/nodes/nodes-factory'; +export * from './models/graph/point'; +export * from './models/graph/relationMenuObjects'; +export * from './models/graph/relationship'; +export * from './models/heat-parameters'; +export * from './models/input-property-base'; +export * from './models/inputs-and-properties'; +export * from './models/inputs'; +export * from './models/instance-inputs-properties-map'; +export * from './models/left-panel'; +export * from './models/member'; +export * from './models/modules/base-module'; +export * from './models/properties'; +export * from './models/requirement'; +export * from './models/tab'; +export * from './models/tooltip-data'; +export * from './models/user'; +export * from './models/validate'; +export * from './models/component-metadata'; + +export * from './models/button'; +export * from './models/filter-properties-assignment-data' diff --git a/catalog-ui/src/app/models/activity.ts b/catalog-ui/src/app/models/activity.ts new file mode 100644 index 0000000000..d60e69258c --- /dev/null +++ b/catalog-ui/src/app/models/activity.ts @@ -0,0 +1,27 @@ +/** + * Created by obarda on 19/11/2015. + */ +'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/src/app/models/additional-information.ts b/catalog-ui/src/app/models/additional-information.ts new file mode 100644 index 0000000000..0fc850a227 --- /dev/null +++ b/catalog-ui/src/app/models/additional-information.ts @@ -0,0 +1,20 @@ +'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/src/app/models/app-config.ts b/catalog-ui/src/app/models/app-config.ts new file mode 100644 index 0000000000..a80dd40da8 --- /dev/null +++ b/catalog-ui/src/app/models/app-config.ts @@ -0,0 +1,226 @@ +'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; + //*********// + + 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; + hostedApplications:Array<IHostedApplication>; + 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; + showOutlook:boolean; + validationConfigPath:string; +} + +export interface IResourceTypesFilter { + resource:Array<string>; +} + +export interface IHostedApplication { + moduleName:string; + navTitle:string; + defaultState:string; + exists?:boolean; + state:IHostedApplicationState; +} + +export interface IHostedApplicationState { + name:string; + url:string; + relativeHtmlPath:string; + controllerName: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/src/app/models/artifacts.ts b/catalog-ui/src/app/models/artifacts.ts new file mode 100644 index 0000000000..63e4b594f1 --- /dev/null +++ b/catalog-ui/src/app/models/artifacts.ts @@ -0,0 +1,101 @@ +'use strict'; + +import {ArtifactType} from './../utils'; +import {HeatParameterModel} from "./heat-parameters"; + +//this object contains keys, each key contain ArtifactModel +export class ArtifactGroupModel { + + constructor(artifacts?:ArtifactGroupModel) { + _.forEach(artifacts, (artifact:ArtifactModel, key) => { + this[key] = new ArtifactModel(artifact); + }); + } + + public filteredByType(type:string):ArtifactGroupModel { + let tmpArtifactGroupModel = new ArtifactGroupModel(); + _.each(Object.keys(this), (key)=>{ + if (this[key].artifactType === type) { + tmpArtifactGroupModel[key] = this[key]; + } + }); + return tmpArtifactGroupModel; + }; +} + +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<HeatParameterModel>; + generatedFromId:string; + + //custom properties + selected:boolean; + originalDescription:string; + envArtifact:ArtifactModel; + + 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 ArtifactType.HEAT === this.artifactType || ArtifactType.HEAT_VOL === this.artifactType || ArtifactType.HEAT_NET === this.artifactType; + }; + + public isThirdParty = ():boolean => { + return _.has(ArtifactType.THIRD_PARTY_RESERVED_TYPES, this.artifactType); + }; + + public toJSON = ():any => { + this.selected = undefined; + this.originalDescription = undefined; + this.envArtifact = undefined; + return this; + }; +} + + diff --git a/catalog-ui/src/app/models/aschema-property.ts b/catalog-ui/src/app/models/aschema-property.ts new file mode 100644 index 0000000000..a430a8cc0e --- /dev/null +++ b/catalog-ui/src/app/models/aschema-property.ts @@ -0,0 +1,56 @@ +/** + * Created by osonsino on 16/05/2016. + */ +'use strict'; +import { PROPERTY_DATA } from "app/utils"; + +export class SchemaPropertyGroupModel { + property:SchemaProperty; + + constructor(schemaProperty?:SchemaProperty) { + this.property = schemaProperty; + } +} + +export class SchemaProperty { + + type:string; + required:boolean; + definition:boolean; + description:string; + password:boolean; + //custom properties + simpleType:string; + isSimpleType: boolean; + isDataType: boolean; + private _derivedFromSimpleTypeName:string; + get derivedFromSimpleTypeName():string { + return this._derivedFromSimpleTypeName; + } + set derivedFromSimpleTypeName(derivedFromSimpleTypeName:string) { + this._derivedFromSimpleTypeName = derivedFromSimpleTypeName; + } + + 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; + this.isSimpleType = (-1 < PROPERTY_DATA.SIMPLE_TYPES.indexOf(this.type)); + this.isDataType = PROPERTY_DATA.TYPES.indexOf(this.type) == -1; + } + } + + public toJSON = ():any => { + this.simpleType = undefined; + this.isSimpleType = undefined; + this.isDataType = undefined; + this._derivedFromSimpleTypeName = undefined; + return this; + }; +} + + diff --git a/catalog-ui/src/app/models/attributes.ts b/catalog-ui/src/app/models/attributes.ts new file mode 100644 index 0000000000..f2562e98bf --- /dev/null +++ b/catalog-ui/src/app/models/attributes.ts @@ -0,0 +1,119 @@ +'use strict'; +import {SchemaAttributeGroupModel, SchemaAttribute} from "./schema-attribute"; +import {SchemaPropertyGroupModel, SchemaProperty} from "./aschema-property"; + +export class AttributesGroup { + constructor(attributesObj?:AttributesGroup) { + _.forEach(attributesObj, (attributes:Array<AttributeModel>, instance) => { + this[instance] = []; + _.forEach(attributes, (attribute:AttributeModel):void => { + attribute.resourceInstanceUniqueId = instance; + attribute.readonly = true; + this[instance].push(new AttributeModel(attribute)); + }); + }); + } +} + +export interface IAttributeModel { + + //server data + uniqueId:string; + name:string; + defaultValue:string; + description:string; + type:string; + schema: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:SchemaAttributeGroupModel; + status:string; + value:string; + hidden:boolean; + parentUniqueId:string; + //custom data + resourceInstanceUniqueId:string; + readonly:boolean; + valueUniqueUid:string; + + constructor(attribute?: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 SchemaPropertyGroupModel(new SchemaProperty()); + } else { + //forcing creating new object, so editing different one than the object in the table + this.schema = new SchemaAttributeGroupModel(new 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/src/app/models/button.ts b/catalog-ui/src/app/models/button.ts new file mode 100644 index 0000000000..8fbaf9091c --- /dev/null +++ b/catalog-ui/src/app/models/button.ts @@ -0,0 +1,20 @@ +/** + * Created by rc2122 on 5/10/2017. + */ +export class ButtonModel { + text: string; + cssClass: string; + callback: Function; + getDisabled:Function; + constructor(text?:string, cssClass?:string, callback?:Function, getDisabled?:Function){ + this.text = text; + this.cssClass = cssClass; + this.callback = callback; + this.getDisabled = getDisabled; + + } +} + +export class ButtonsModelMap { + [buttonName: string]: ButtonModel; +} diff --git a/catalog-ui/src/app/models/capability.ts b/catalog-ui/src/app/models/capability.ts new file mode 100644 index 0000000000..94995bca55 --- /dev/null +++ b/catalog-ui/src/app/models/capability.ts @@ -0,0 +1,95 @@ +/** + * Created by obarda on 4/20/2016. + */ +'use strict'; +import {PropertyModel} from "./properties"; + +//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?:CapabilitiesGroup) { + _.forEach(capabilityGroupObj, (capabilitiesArrayObj:Array<Capability>, instance) => { + this[instance] = []; + _.forEach(capabilitiesArrayObj, (capability:Capability):void => { + this[instance].push(new Capability(capability)); + }); + }); + } + + public findValueByKey(keySubstring:string):Array<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<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.replace("tosca.capabilities.", "") + " " ) : "") + + (this.description || "") + " " + + (this.ownerName || "") + " " + + (this.validSourceTypes ? (this.validSourceTypes.join(',') + " ") : "") + + this.minOccurrences + "," + this.maxOccurrences; + if (this.properties && this.properties.length) { + _.forEach(this.properties, (prop:PropertyModel)=> { + this.filterTerm += " " + prop.name + + " " + (prop.description || "") + + " " + prop.type + + (prop.schema && prop.schema.property ? (" " + prop.schema.property.type) : ""); + }); + } + } +} + + diff --git a/catalog-ui/src/app/models/categories.ts b/catalog-ui/src/app/models/categories.ts new file mode 100644 index 0000000000..2aad61194f --- /dev/null +++ b/catalog-ui/src/app/models/categories.ts @@ -0,0 +1,10 @@ +import {IMainCategory} from "./category"; +/** + * Created by ob0695 on 5/8/2017. + */ +export class Categories { + + resourceCategories:Array<IMainCategory>; + productCategories:Array<IMainCategory>; + serviceCategories:Array<IMainCategory>; +} diff --git a/catalog-ui/src/app/models/category.ts b/catalog-ui/src/app/models/category.ts new file mode 100644 index 0000000000..fcfe61bfd6 --- /dev/null +++ b/catalog-ui/src/app/models/category.ts @@ -0,0 +1,47 @@ +'use strict'; + + + +export class ICategoryBase { + + //server properties + name:string; + normalizedName:string; + uniqueId:string; + icons:Array<string>; + + //custom properties + filterTerms:string; + isDisabled:boolean; + filteredGroup:Array<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/src/app/models/comments.ts b/catalog-ui/src/app/models/comments.ts new file mode 100644 index 0000000000..b61713cc2c --- /dev/null +++ b/catalog-ui/src/app/models/comments.ts @@ -0,0 +1,11 @@ +'use strict'; + +export class AsdcComment { + public userRemarks:string; + + constructor() { + } +} + + + diff --git a/catalog-ui/src/app/models/component-metadata.ts b/catalog-ui/src/app/models/component-metadata.ts new file mode 100644 index 0000000000..9476d75bd0 --- /dev/null +++ b/catalog-ui/src/app/models/component-metadata.ts @@ -0,0 +1,89 @@ +import {IMainCategory} from "./category"; +/** + * Created by obarda on 4/18/2017. + */ +export class ComponentMetadata { + + 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 categories:Array<IMainCategory>; + public highestVersion:boolean; + public normalizedName:string; + public systemName:string; + + //Resource only + public resourceType: string; + public csarUUID:string; + public csarVersion:string; + public derivedList: string[]; + public vendorName:string; + public vendorRelease:string; + public derivedFrom: Array<string>; + + //Service only + public projectCode:string; + public distributionStatus:string; + public ecompGeneratedNaming: boolean; + public namingPolicy: string; + + + //backend lifecycleState + public state:string; + + deserialize (response): ComponentMetadata { + this.abstract = response.abstract; + this.uniqueId = response.uniqueId; + this.uuid = response.uuid; + this.invariantUUID = response.invariantUUID; + this.contactId = response.contactId; + this.categories = response.categories; + this.creatorUserId = response.creatorUserId; + this.creationDate = response.creationDate; + this.creatorFullName = response.creatorFullName; + this.description = response.description; + this.icon = response.icon; + this.lastUpdateDate = response.lastUpdateDate; + this.lastUpdaterUserId = response.lastUpdaterUserId; + this.lastUpdaterFullName = response.lastUpdaterFullName; + this.lifecycleState = response.lifecycleState; + this.name = response.name; + this.version = response.version; + this.tags = angular.copy(response.tags, this.tags); + this.allVersions = response.allVersions; + this.componentType = response.componentType; + this.distributionStatus = response.distributionStatus; + this.highestVersion = response.highestVersion; + this.vendorName = response.vendorName; + this.vendorRelease = response.vendorRelease; + this.derivedList = response.derivedList; + this.normalizedName = response.normalizedName; + this.systemName = response.systemName; + this.projectCode = response.projectCode; + this.resourceType = response.resourceType; + this.csarUUID = response.csarUUID; + this.csarVersion = response.version; + this.state = response.state; + this.ecompGeneratedNaming = response.ecompGeneratedNaming; + this.namingPolicy = response.namingPolicy; + this.derivedFrom = response.derivedFrom; + return this; + } + +} diff --git a/catalog-ui/src/app/models/components/component.ts b/catalog-ui/src/app/models/components/component.ts new file mode 100644 index 0000000000..898285f032 --- /dev/null +++ b/catalog-ui/src/app/models/components/component.ts @@ -0,0 +1,911 @@ +'use strict'; + +import {AsdcComment, ArtifactModel, ArtifactGroupModel, IFileDownload, PropertyModel, PropertiesGroup, AttributeModel, AttributesGroup, ComponentInstance, + InputModel, DisplayModule, Module, IValidate, RelationshipModel, IMainCategory, RequirementsGroup, CapabilitiesGroup, AdditionalInformationModel, + Resource, IAppMenu, Product, Service} from "../../models"; + +import {IComponentService} from "../../services/components/component-service"; +import {CommonUtils} from "../../utils/common-utils"; +import {QueueUtils} from "../../utils/functions"; +import {ArtifactGroupType} from "../../utils/constants"; +import {ComponentMetadata} from "../component-metadata"; + +// import {} +export interface IComponent { + + //---------------------------------------------- API CALLS ----------------------------------------------------// + + //Component API + getComponent():ng.IPromise<Component>; + updateComponent():ng.IPromise<Component>; + createComponentOnServer():ng.IPromise<Component>; + changeLifecycleState(state:string, commentObj:AsdcComment):ng.IPromise<Component>; + validateName(newName:string):ng.IPromise<IValidate>; + updateRequirementsCapabilities():ng.IPromise<any>; + + //Artifacts API + addOrUpdateArtifact(artifact:ArtifactModel):ng.IPromise<ArtifactModel>; + updateMultipleArtifacts(artifacts:Array<ArtifactModel>):ng.IPromise<any>; + deleteArtifact(artifactId:string, artifactLabel:string):ng.IPromise<ArtifactModel>; + downloadInstanceArtifact(artifactId:string):ng.IPromise<IFileDownload>; + downloadArtifact(artifactId:string):ng.IPromise<IFileDownload>; + getArtifactByGroupType(artifactGroupType:string):ng.IPromise<ArtifactGroupModel>; + + + //Property API + addOrUpdateProperty(property:PropertyModel):ng.IPromise<PropertyModel>; + deleteProperty(propertyId:string):ng.IPromise<PropertyModel>; + updateInstanceProperty(property:PropertyModel):ng.IPromise<PropertyModel>; + + //Attribute API + deleteAttribute(attributeId:string):ng.IPromise<AttributeModel>; + addOrUpdateAttribute(attribute:AttributeModel):ng.IPromise<AttributeModel>; + updateInstanceAttribute(attribute:AttributeModel):ng.IPromise<AttributeModel>; + + + + + //Component Instance API + createComponentInstance(componentInstance:ComponentInstance):ng.IPromise<ComponentInstance>; + deleteComponentInstance(componentInstanceId:string):ng.IPromise<ComponentInstance>; + addOrUpdateInstanceArtifact(artifact:ArtifactModel):ng.IPromise<ArtifactModel>; + deleteInstanceArtifact(artifactId:string, artifactLabel:string):ng.IPromise<ArtifactModel>; + uploadInstanceEnvFile(artifact:ArtifactModel):ng.IPromise<ArtifactModel>; + changeComponentInstanceVersion(componentUid:string):ng.IPromise<Component>; + updateComponentInstance(componentInstance:ComponentInstance):ng.IPromise<ComponentInstance>; + updateMultipleComponentInstances(instances:Array<ComponentInstance>):ng.IPromise<Array<ComponentInstance>>; + + //Inputs API + getComponentInstanceInputProperties(componentInstanceId:string, inputId:string):ng.IPromise<Array<PropertyModel>> + getComponentInstanceProperties(componentInstanceId:string):ng.IPromise<Array<PropertyModel>> + getComponentInputs(componentId:string):ng.IPromise<Array<InputModel>>; + + createRelation(link:RelationshipModel):ng.IPromise<RelationshipModel>; + deleteRelation(link:RelationshipModel):ng.IPromise<RelationshipModel>; + + + //Modules + getModuleForDisplay(moduleId:string):ng.IPromise<DisplayModule>; + getModuleInstanceForDisplay(componentInstanceId:string, moduleId:string):ng.IPromise<DisplayModule>; + updateGroupMetadata(group:Module):ng.IPromise<Module>; + //---------------------------------------------- HELP FUNCTIONS ----------------------------------------------------// + + getComponentSubType():string; + isAlreadyCertified():boolean; + isProduct():boolean; + isService():boolean; + isResource():boolean; + isComplex():boolean; + getAdditionalInformation():Array<AdditionalInformationModel>; + getAllVersionsAsSortedArray():Array<any>; + getStatus(sdcMenu:IAppMenu):string; +} + + +export abstract 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:ArtifactGroupModel; + public artifacts:ArtifactGroupModel; + public toscaArtifacts:ArtifactGroupModel; + public distributionStatus:string; + public categories:Array<IMainCategory>; + public componentInstancesProperties:PropertiesGroup; + public componentInstancesAttributes:AttributesGroup; + public componentInstancesRelations:Array<RelationshipModel>; + public componentInstances:Array<ComponentInstance>; + public inputs:Array<InputModel>; + public capabilities:CapabilitiesGroup; + public requirements:RequirementsGroup; + public additionalInformation:any; + public properties:Array<PropertyModel>; + public attributes:Array<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<Module>; + //custom properties + public componentService:IComponentService; + public filterTerm:string; + public iconSprite:string; + public selectedInstance:ComponentInstance; + public mainCategory:string; + public subCategory:string; + public selectedCategory:string; + public showMenu:boolean; + + + constructor(componentService: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 ArtifactGroupModel(component.artifacts); + this.toscaArtifacts = new 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.componentInstancesRelations = CommonUtils.initComponentInstanceRelations(component.componentInstancesRelations); + this.componentInstancesProperties = new PropertiesGroup(component.componentInstancesProperties); + this.componentInstancesAttributes = new AttributesGroup(component.componentInstancesAttributes); + this.name = component.name; + this.version = component.version; + this.tags = []; + angular.copy(component.tags, this.tags); + this.capabilities = new CapabilitiesGroup(component.capabilities); + this.requirements = new RequirementsGroup(component.requirements); + this.allVersions = component.allVersions; + this.deploymentArtifacts = new 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 = CommonUtils.initComponentInstances(component.componentInstances); + this.properties = CommonUtils.initProperties(component.properties, this.uniqueId); + this.attributes = CommonUtils.initAttributes(component.attributes, this.uniqueId); + this.selectedInstance = component.selectedInstance; + this.iconSprite = component.iconSprite; + this.showMenu = true; + this.groups = CommonUtils.initModules(component.groups); + } + + //custom properties + this.componentService = componentService; + } + + public setUniqueId = (uniqueId:string):void => { + this.uniqueId = uniqueId; + }; + + public setSelectedInstance = (componentInstance:ComponentInstance):void => { + this.selectedInstance = componentInstance; + }; + + + //------------------------------------------ API Calls ----------------------------------------------------------------// + public changeLifecycleState = (state:string, commentObj:AsdcComment):ng.IPromise<Component> => { + return this.componentService.changeLifecycleState(this, state, JSON.stringify(commentObj)); + }; + + public getComponent = ():ng.IPromise<Component> => { + return this.componentService.getComponent(this.uniqueId); + }; + + public createComponentOnServer = ():ng.IPromise<Component> => { + this.handleTags(); + return this.componentService.createComponent(this); + }; + + public updateComponent = ():ng.IPromise<Component> => { + this.handleTags(); + return this.componentService.updateComponent(this); + }; + + public validateName = (newName:string, subtype?:string):ng.IPromise<IValidate> => { + return this.componentService.validateName(newName, subtype); + }; + + public downloadArtifact = (artifactId:string):ng.IPromise<IFileDownload> => { + return this.componentService.downloadArtifact(this.uniqueId, artifactId); + }; + + public addOrUpdateArtifact = (artifact:ArtifactModel):ng.IPromise<ArtifactModel> => { + let deferred = this.$q.defer(); + let onSuccess = (artifactObj:ArtifactModel):void => { + let newArtifact = new 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<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 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<ArtifactModel> => { + let deferred = this.$q.defer(); + let onSuccess = (artifactObj:ArtifactModel):void => { + let newArtifact = new 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 getArtifactByGroupType = (artifactGroupType:string):ng.IPromise<ArtifactGroupModel> => { + + let deferred = this.$q.defer(); + let onSuccess = (response:ArtifactGroupModel):void => { + deferred.resolve(response); + }; + let onFailed = (error:any):void => { + deferred.reject(error); + }; + this.componentService.getArtifactByGroupType(this.uniqueId, artifactGroupType).then(onSuccess, onFailed); + return deferred.promise; + }; + + public getComponentInstanceArtifactsByGroupType = (componentInstanceId:string, artifactGroupType:string):ng.IPromise<ArtifactGroupModel> => { + + let deferred = this.$q.defer(); + let onSuccess = (response:ArtifactGroupModel):void => { + deferred.resolve(response); + }; + let onFailed = (error:any):void => { + deferred.reject(error); + }; + this.componentService.getComponentInstanceArtifactsByGroupType(this.uniqueId, componentInstanceId, artifactGroupType).then(onSuccess, onFailed); + return deferred.promise; + }; + + public addOrUpdateProperty = (property:PropertyModel):ng.IPromise<PropertyModel> => { + let deferred = this.$q.defer(); + + let onError = (error:any):void => { + deferred.reject(error); + }; + + if (!property.uniqueId) { + let onSuccess = (property:PropertyModel):void => { + let newProperty = new PropertyModel(property); + this.properties.push(newProperty); + deferred.resolve(newProperty); + }; + this.componentService.addProperty(this.uniqueId, property).then(onSuccess, onError); + } + else { + let onSuccess = (newProperty:PropertyModel):void => { + // find exist instance property in parent component for update the new value ( find bu uniqueId ) + let existProperty:PropertyModel = <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:AttributeModel):ng.IPromise<AttributeModel> => { + let deferred = this.$q.defer(); + + let onError = (error:any):void => { + deferred.reject(error); + }; + + if (!attribute.uniqueId) { + let onSuccess = (attribute:AttributeModel):void => { + let newAttribute = new AttributeModel(attribute); + this.attributes.push(newAttribute); + deferred.resolve(newAttribute); + }; + this.componentService.addAttribute(this.uniqueId, attribute).then(onSuccess, onError); + } + else { + let onSuccess = (newAttribute:AttributeModel):void => { + let existAttribute:AttributeModel = <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<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<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:PropertyModel):ng.IPromise<PropertyModel> => { + let deferred = this.$q.defer(); + let onSuccess = (newProperty:PropertyModel):void => { + // find exist instance property in parent component for update the new value ( find bu uniqueId & path) + let existProperty:PropertyModel = <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:AttributeModel):ng.IPromise<AttributeModel> => { + let deferred = this.$q.defer(); + let onSuccess = (newAttribute:AttributeModel):void => { + let existAttribute:AttributeModel = <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<IFileDownload> => { + return this.componentService.downloadInstanceArtifact(this.uniqueId, this.selectedInstance.uniqueId, artifactId); + }; + + public deleteInstanceArtifact = (artifactId:string, artifactLabel:string):ng.IPromise<ArtifactModel> => { + let deferred = this.$q.defer(); + let onSuccess = (artifactObj:ArtifactModel):void => { + let newArtifact = new 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<ArtifactModel> => { + let deferred = this.$q.defer(); + let onSuccess = (artifactObj:ArtifactModel):void => { + switch (artifactObj.artifactGroupType) { + case ArtifactGroupType.DEPLOYMENT: + this.selectedInstance.deploymentArtifacts[artifactObj.artifactLabel] = artifactObj; + break; + case ArtifactGroupType.INFORMATION: + this.selectedInstance.artifacts[artifactObj.artifactLabel] = artifactObj; + break; + } + 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:ArtifactModel):ng.IPromise<ArtifactModel> => { + let deferred = this.$q.defer(); + let onSuccess = (artifactObj: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<Component> => { + let deferred = this.$q.defer(); + let onFailed = (error:any):void => { + deferred.reject(error); + }; + let onSuccess = (componentInstance:ComponentInstance):void => { + let onSuccess = (component: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:ComponentInstance):ng.IPromise<ComponentInstance> => { + let deferred = this.$q.defer(); + let onSuccess = (instance:ComponentInstance):void => { + this.componentInstances.push(instance); + deferred.resolve(instance); + }; + let onFailed = (error:any):void => { + deferred.reject(error); + }; + this.componentService.createComponentInstance(this.uniqueId, componentInstance).then(onSuccess, onFailed); + return deferred.promise; + }; + + public updateComponentInstance = (componentInstance:ComponentInstance):ng.IPromise<ComponentInstance> => { + let deferred = this.$q.defer(); + let onSuccess = (updatedInstance:ComponentInstance):void => { + let componentInstance:ComponentInstance = _.find(this.componentInstances, (instance: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<ComponentInstance>):ng.IPromise<Array<ComponentInstance>> => { + let deferred = this.$q.defer(); + let onSuccess = (updatedInstances:Array<ComponentInstance>):void => { + _.forEach(updatedInstances, (updatedComponentInstance) => { + let componentInstance:ComponentInstance = _.find(this.componentInstances, (instance:ComponentInstance) => { + return instance.uniqueId === updatedComponentInstance.uniqueId; + }); + + let index = this.componentInstances.indexOf(componentInstance); + this.componentInstances[index] = componentInstance; + + }); + 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<ComponentInstance> => { + let deferred = this.$q.defer(); + let onSuccess = ():void => { + let onSuccess = (component:Component):void => { + this.componentInstances = CommonUtils.initComponentInstances(component.componentInstances); + this.componentInstancesProperties = new PropertiesGroup(component.componentInstancesProperties); + this.componentInstancesAttributes = new AttributesGroup(component.componentInstancesAttributes); + this.groups = component.groups; + this.componentInstancesRelations = CommonUtils.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:RelationshipModel):ng.IPromise<RelationshipModel> => { + let deferred = this.$q.defer(); + let onSuccess = (relation:RelationshipModel):void => { + console.info('Link created successfully', relation); + if (!this.componentInstancesRelations) { + this.componentInstancesRelations = []; + } + this.componentInstancesRelations.push(new 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:RelationshipModel):ng.IPromise<RelationshipModel> => { + let deferred = this.$q.defer(); + let onSuccess = (responseRelation: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<DisplayModule> => { + + let deferred = this.$q.defer(); + let onSuccess = (response: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; + }; + + public getModuleInstanceForDisplay = (componentInstanceId:string, moduleId:string):ng.IPromise<DisplayModule> => { + + let deferred = this.$q.defer(); + let onSuccess = (response:DisplayModule):void => { + deferred.resolve(response); + }; + let onFailed = (error:any):void => { + deferred.reject(error); + }; + this.componentService.getComponentInstanceModule(this.uniqueId, componentInstanceId, 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<ComponentInstance>> => { + + let deferred = this.$q.defer(); + let onSuccess = (response:Array<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<InputModel>> => { + + let deferred = this.$q.defer(); + let onSuccess = (inputsRes:Array<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<InputModel>> => { + + let deferred = this.$q.defer(); + let onSuccess = (response:Array<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<PropertyModel>> => { + + let deferred = this.$q.defer(); + let onSuccess = (response:Array<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; + }; + + // get inputs inatnce - Pagination function + public getComponentInstanceProperties = (componentInstanceId:string):ng.IPromise<Array<PropertyModel>> => { + + let deferred = this.$q.defer(); + let onSuccess = (response:Array<PropertyModel>):void => { + deferred.resolve(response); + }; + let onFailed = (error:any):void => { + deferred.reject(error); + }; + this.componentService.getComponentInstanceProperties(this.uniqueId, componentInstanceId).then(onSuccess, onFailed); + return deferred.promise; + }; + + + public updateGroupMetadata = (module:Module):ng.IPromise<Module> => { + + let deferred = this.$q.defer(); + + let onSuccess = (updatedModule:Module):void => { + let groupIndex:number = _.indexOf(this.groups, _.find(this.groups, (module: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<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):ArtifactGroupModel => { + switch (artifactGroupType) { + case ArtifactGroupType.DEPLOYMENT: + return this.deploymentArtifacts; + case ArtifactGroupType.INFORMATION: + return this.artifacts; + } + }; + + public getStatus = (sdcMenu: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 abstract setComponentDisplayData():void; + public abstract getTypeUrl():string; + + public setComponentMetadata(componentMetadata: ComponentMetadata) { + this.abstract = componentMetadata.abstract; + this.uniqueId = componentMetadata.uniqueId; + this.uuid = componentMetadata.uuid; + this.invariantUUID = componentMetadata.invariantUUID; + this.contactId = componentMetadata.contactId; + this.categories = componentMetadata.categories; + this.creatorUserId = componentMetadata.creatorUserId; + this.creationDate = componentMetadata.creationDate; + this.creatorFullName = componentMetadata.creatorFullName; + this.description = componentMetadata.description; + this.icon = componentMetadata.icon; + this.lastUpdateDate = componentMetadata.lastUpdateDate; + this.lastUpdaterUserId = componentMetadata.lastUpdaterUserId; + this.lastUpdaterFullName = componentMetadata.lastUpdaterFullName; + this.lifecycleState = componentMetadata.lifecycleState; + this.name = componentMetadata.name; + this.version = componentMetadata.version; + this.tags = angular.copy(componentMetadata.tags, this.tags); + this.allVersions = componentMetadata.allVersions; + this.componentType = componentMetadata.componentType; + this.distributionStatus = componentMetadata.distributionStatus; + this.highestVersion = componentMetadata.highestVersion; + this.vendorName = componentMetadata.vendorName; + this.vendorRelease = componentMetadata.vendorRelease; + this.derivedList = componentMetadata.derivedList; + this.normalizedName = componentMetadata.normalizedName; + this.systemName = componentMetadata.systemName; + this.projectCode = componentMetadata.projectCode; + this.categories = componentMetadata.categories; + + } + + 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/src/app/models/components/displayComponent.ts b/catalog-ui/src/app/models/components/displayComponent.ts new file mode 100644 index 0000000000..8b2b522fd9 --- /dev/null +++ b/catalog-ui/src/app/models/components/displayComponent.ts @@ -0,0 +1,111 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR 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. + */ + +'use strict'; +import {ComponentType} from "../../utils/constants"; +import {ComponentMetadata} from "../component-metadata"; +import {RequirementsGroup} from "../requirement"; +import {CapabilitiesGroup} from "../capability"; + +export class LeftPaletteComponent { + + uniqueId:string; + displayName:string; + version:string; + mainCategory:string; + subCategory:string; + iconClass:string; + componentSubType:string; + searchFilterTerms:string; + certifiedIconClass:string; + icon:string; + isRequirmentAndCapabilitiesLoaded:boolean; + + uuid:string; + name:string; + lifecycleState:string; + allVersions:any; + componentType:string; + systemName:string; + + capabilities:CapabilitiesGroup; + requirements:RequirementsGroup; + + constructor(public component:ComponentMetadata) { + this.icon = component.icon; + this.version = component.version; + this.uniqueId = component.uniqueId; + this.isRequirmentAndCapabilitiesLoaded = false; + this.uuid = component.uuid; + this.name = component.name; + this.allVersions = component.allVersions; + this.componentType = component.componentType; + this.systemName = component.systemName; + + 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'; + } + + this.componentSubType = component.resourceType ? component.resourceType: 'SERVICE'; + 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 ComponentType.SERVICE: + this.iconClass = "sprite-services-icons " + icon; + break; + case 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/src/app/models/components/product.ts b/catalog-ui/src/app/models/components/product.ts new file mode 100644 index 0000000000..0f2c00e822 --- /dev/null +++ b/catalog-ui/src/app/models/components/product.ts @@ -0,0 +1,105 @@ +/** + * Created by obarda on 2/4/2016. + */ +'use strict'; +import {Component} from "./component"; +import {IProductService} from "../../services/components/product-service"; +import {IGroup, ISubCategory, IMainCategory, ICategoryBase} from "../category"; +import {ComponentMetadata} from "../component-metadata"; + +export class Product extends Component { + + public contacts:Array<string>; + public componentService:IProductService; + public fullName:string; + + constructor(componentService: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:IMainCategory) => { + _.forEach(category.subcategories, (subcategory:ISubCategory) => { + subcategory.groupings = _.reject(subcategory.groupings, (group:IGroup) => { + return group.uniqueId === uniqueId; + }); + if (subcategory.groupings.length == 0) { // if there is no groups, delete the subcategory + category.subcategories = _.reject(category.subcategories, (subcategoryObj: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:IMainCategory) => { + return categoryObj.uniqueId === category.uniqueId; + }); + } + } + }); + }); + }; + + private getCategoryObjectById = (categoriesArray:Array<ICategoryBase>, categoryUniqueId:string):ICategoryBase => { + let categorySelected = _.find(categoriesArray, (category) => { + return category.uniqueId === categoryUniqueId; + }); + return categorySelected; + }; + + public addGroup = (category:IMainCategory, subcategory:ISubCategory, group:IGroup):void => { + if (!this.categories) { + this.categories = new Array<IMainCategory>(); + } + let existingCategory:IMainCategory = <IMainCategory>this.getCategoryObjectById(this.categories, category.uniqueId); + let newGroup = angular.copy(group); + newGroup.filterTerms = undefined; + newGroup.isDisabled = undefined; + if (!existingCategory) { + let newCategory:IMainCategory = angular.copy(category); + newCategory.filteredGroup = undefined; + newCategory.subcategories = []; + let newSubcategory:ISubCategory = angular.copy(subcategory); + newSubcategory.groupings = []; + newSubcategory.groupings.push(newGroup); + newCategory.subcategories.push(newSubcategory); + this.categories.push(newCategory); + } + else { + let existingSubcategory:ISubCategory = <ISubCategory> this.getCategoryObjectById(existingCategory.subcategories, subcategory.uniqueId); + if (!existingSubcategory) { + let newSubcategory:ISubCategory = angular.copy(subcategory); + newSubcategory.groupings = []; + newSubcategory.groupings.push(newGroup); + existingCategory.subcategories.push(newSubcategory); + + } else { + let existingGroup:IGroup = <IGroup> this.getCategoryObjectById(existingSubcategory.groupings, group.uniqueId); + if (!existingGroup) { + existingSubcategory.groupings.push(newGroup); + } + } + } + }; + + getTypeUrl():string { + return 'products/'; + } + + public setComponentMetadata(componentMetadata:ComponentMetadata) { + super.setComponentMetadata(componentMetadata); + this.setComponentDisplayData(); + }; + + setComponentDisplayData():void { + this.filterTerm = this.name + ' ' + this.description + ' ' + (this.tags ? this.tags.toString() : '') + ' ' + this.version; + this.iconSprite = "sprite-product-icons"; + } +} + + diff --git a/catalog-ui/src/app/models/components/resource.ts b/catalog-ui/src/app/models/components/resource.ts new file mode 100644 index 0000000000..e594451e1a --- /dev/null +++ b/catalog-ui/src/app/models/components/resource.ts @@ -0,0 +1,157 @@ +/** + * Created by obarda on 2/3/2016. + */ +'use strict'; +import {InstancesInputsOrPropertiesMapData} from "../instance-inputs-properties-map"; +import {PropertyModel} from "../properties"; +import {DisplayModule} from "../modules/base-module"; +import {InputModel} from "../inputs"; +import {ResourceType} from "../../utils/constants"; +import {Component} from "./component"; +import {FileUploadModel} from "../../directives/file-upload/file-upload"; +import {IResourceService} from "../../services/components/resource-service"; +import {ComponentMetadata} from "../component-metadata"; + +export class Resource extends Component { + + public interfaces:any; + public derivedFrom:Array<string>; + public componentService:IResourceService; + public resourceType:string; + public payloadData:string; + public payloadName:string; + public importedFile:FileUploadModel; + + // Onboarding parameters + public csarUUID:string; + public csarVersion:string; + public csarPackageType:string; + public packageId:string; + + constructor(componentService: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 = ResourceType.VF; + } + + this.componentService = componentService; + this.iconSprite = "sprite-resource-icons"; + } + + public setComponentMetadata(componentMetadata: ComponentMetadata) { + super.setComponentMetadata(componentMetadata); + this.resourceType = componentMetadata.resourceType; + this.csarUUID = componentMetadata.csarUUID; + this.csarVersion = componentMetadata.csarVersion; + this.derivedFrom = componentMetadata.derivedFrom; + this.setComponentDisplayData(); + }; + + public getComponentSubType = ():string => { + return this.resourceType; + }; + + public isComplex = ():boolean => { + return this.resourceType === ResourceType.VF; + }; + + public isVl = ():boolean => { + return ResourceType.VL == this.resourceType; + }; + + public isCsarComponent = ():boolean => { + return !!this.csarUUID; + }; + + public createComponentOnServer = ():ng.IPromise<Component> => { + let deferred = this.$q.defer(); + let onSuccess = (component: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; + }; + + + public updateResourceGroupProperties = (module:DisplayModule, properties:Array<PropertyModel>):ng.IPromise<Array<PropertyModel>> => { + let deferred = this.$q.defer(); + let onSuccess = (updatedProperties:Array<PropertyModel>):void => { + _.forEach(updatedProperties, (property:PropertyModel) => { // Replace all updated properties on the module we needed to update + _.extend(_.find(module.properties, {uniqueId: property.uniqueId}), property); + + }); + //_.extend(_.findWhere(this.groups, {uniqueId: module.uniqueId }), module); // replace the module on the component so all data will be updates if the module sent to the function is a copy + deferred.resolve(updatedProperties); + }; + let onError = (error:any):void => { + deferred.reject(error); + }; + + this.componentService.updateResourceGroupProperties(this.uniqueId, module.uniqueId, properties).then(onSuccess, onError); + return deferred.promise; + }; + + // For now we only implement the logic in service level + public createInputsFormInstances = (instanceInputsPropertiesMap:InstancesInputsOrPropertiesMapData):ng.IPromise<Array<InputModel>> => { + let deferred = this.$q.defer(); + return deferred.promise; + }; + + getTypeUrl():string { + return 'resources/'; + } + + + setComponentDisplayData():void { + this.filterTerm = this.name + ' ' + this.description + ' ' + (this.tags ? this.tags.toString() : '') + ' ' + this.version + ' ' + this.resourceType; + if (this.categories && this.categories[0] && this.categories[0].subcategories && this.categories[0].subcategories[0]) { + this.mainCategory = this.categories[0].name; + this.subCategory = this.categories[0].subcategories[0].name; + this.selectedCategory = this.mainCategory + "_#_" + this.subCategory; + this.iconSprite = "sprite-resource-icons"; + } + }; + + 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/src/app/models/components/service.ts b/catalog-ui/src/app/models/components/service.ts new file mode 100644 index 0000000000..a4dd3155d7 --- /dev/null +++ b/catalog-ui/src/app/models/components/service.ts @@ -0,0 +1,150 @@ +/** + * Created by obarda on 2/4/2016. + */ +'use strict'; +import {IServiceService} from "../../services/components/service-service"; +import {Component, PropertyModel, DisplayModule, InputsAndProperties, InputModel, InstancesInputsOrPropertiesMapData, InstancesInputsPropertiesMap, + Distribution, DistributionComponent, ArtifactGroupModel} from "../../models"; +import {ArtifactGroupType} from "../../utils/constants"; +import {ComponentMetadata} from "../component-metadata"; + +export class Service extends Component { + + public serviceApiArtifacts:ArtifactGroupModel; + public componentService:IServiceService; + public ecompGeneratedNaming:boolean; + public namingPolicy:string; + + constructor(componentService:IServiceService, $q:ng.IQService, component?:Service) { + super(componentService, $q, component); + this.ecompGeneratedNaming = true; + if (component) { + this.serviceApiArtifacts = new ArtifactGroupModel(component.serviceApiArtifacts); + this.filterTerm = this.name + ' ' + this.description + ' ' + (this.tags ? this.tags.toString() : '') + ' ' + this.version; + this.ecompGeneratedNaming = component.ecompGeneratedNaming; + this.namingPolicy = component.namingPolicy; + 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<Distribution>> => { + return this.componentService.getDistributionsList(this.uuid); + }; + + public getDistributionsComponent = (distributionId:string):ng.IPromise<Array<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:InstancesInputsOrPropertiesMapData, instancePropertiesMap:InstancesInputsOrPropertiesMapData):ng.IPromise<Array<InputModel>> => { + + let deferred = this.$q.defer(); + let onSuccess = (inputsCreated:Array<InputModel>):void => { + this.inputs = inputsCreated.concat(this.inputs); + deferred.resolve(inputsCreated); + }; + let onFailed = (error:any):void => { + deferred.reject(error); + }; + + let propertiesAndInputsMap:InstancesInputsPropertiesMap = new InstancesInputsPropertiesMap(instancesInputsMap, instancePropertiesMap); + propertiesAndInputsMap = propertiesAndInputsMap.cleanUnnecessaryDataBeforeSending(); // We need to create a copy of the map, without the already selected inputs / properties, and to send the clean map + this.componentService.createInputsFromInstancesInputs(this.uniqueId, propertiesAndInputsMap).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 getServiceInputInputsAndProperties = (inputId:string):ng.IPromise<Array<InputModel>> => { + let deferred = this.$q.defer(); + let onSuccess = (inputsAndProperties:InputsAndProperties):void => { + let input:InputModel = _.find(this.inputs, (input:InputModel) => { + return input.uniqueId === inputId; + }); + input.inputs = inputsAndProperties.inputs; + input.properties = inputsAndProperties.properties; + deferred.resolve(inputsAndProperties); + }; + let onFailed = (error:any):void => { + deferred.reject(error); + }; + this.componentService.getComponentInputInputsAndProperties(this.uniqueId, inputId).then(onSuccess, onFailed); + return deferred.promise; + }; + + public deleteServiceInput = (inputId:string):ng.IPromise<InputModel> => { + let deferred = this.$q.defer(); + + let onSuccess = (deletedInput:InputModel):void => { + delete _.remove(this.inputs, {uniqueId: deletedInput.uniqueId})[0]; + deferred.resolve(deletedInput); + }; + + let onFailed = (error:any):void => { + deferred.reject(error); + }; + + this.componentService.deleteComponentInput(this.uniqueId, inputId).then(onSuccess, onFailed); + return deferred.promise; + }; + + public getArtifactsByType = (artifactGroupType:string):ArtifactGroupModel => { + switch (artifactGroupType) { + case ArtifactGroupType.DEPLOYMENT: + return this.deploymentArtifacts; + case ArtifactGroupType.INFORMATION: + return this.artifacts; + case ArtifactGroupType.SERVICE_API: + return this.serviceApiArtifacts; + } + }; + + public updateGroupInstanceProperties = (resourceInstanceId:string, group:DisplayModule, properties:Array<PropertyModel>):ng.IPromise<Array<PropertyModel>> => { + + let deferred = this.$q.defer(); + let onSuccess = (updatedProperties:Array<PropertyModel>):void => { + _.forEach(updatedProperties, (property:PropertyModel) => { // Replace all updated properties on the we needed to update + _.extend(_.find(group.properties, {uniqueId: property.uniqueId}), property); + }); + deferred.resolve(updatedProperties); + }; + let onError = (error:any):void => { + deferred.reject(error); + }; + + this.componentService.updateGroupInstanceProperties(this.uniqueId, resourceInstanceId, group.groupInstanceUniqueId, properties).then(onSuccess, onError); + return deferred.promise; + }; + + getTypeUrl():string { + return 'services/'; + } + + + public setComponentMetadata(componentMetadata: ComponentMetadata) { + super.setComponentMetadata(componentMetadata); + this.ecompGeneratedNaming = componentMetadata.ecompGeneratedNaming; + this.namingPolicy = componentMetadata.namingPolicy; + this.setComponentDisplayData(); + } + + setComponentDisplayData():void { + this.filterTerm = this.name + ' ' + this.description + ' ' + (this.tags ? this.tags.toString() : '') + ' ' + this.version; + if (this.categories && this.categories[0]) { + this.mainCategory = this.categories[0].name; + this.selectedCategory = this.mainCategory; + } + this.iconSprite = "sprite-services-icons"; + } +} + diff --git a/catalog-ui/src/app/models/componentsInstances/componentInstance.ts b/catalog-ui/src/app/models/componentsInstances/componentInstance.ts new file mode 100644 index 0000000000..22c6232548 --- /dev/null +++ b/catalog-ui/src/app/models/componentsInstances/componentInstance.ts @@ -0,0 +1,113 @@ +/** + * Created by obarda on 2/4/2016. + */ +'use strict'; +import {ArtifactGroupModel, CapabilitiesGroup,RequirementsGroup, PropertyModel, InputModel, Module} from "../../models"; +import {ResourceType} from "../../utils/constants"; + +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:ArtifactGroupModel; + public artifacts:ArtifactGroupModel; + public propertyValueCounter:number; + public uniqueId:string; + public creationTime:number; + public modificationTime:number; + public capabilities:CapabilitiesGroup; + public requirements:RequirementsGroup; + public customizationUUID:string; + //custom properties + public certified:boolean; + public iconSprite:string; + public inputs:Array<InputModel>; + public properties:Array<PropertyModel>; + public groupInstances:Array<Module>; + + 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 ArtifactGroupModel(componentInstance.deploymentArtifacts); + this.artifacts = new ArtifactGroupModel(componentInstance.artifacts); + this.uniqueId = componentInstance.uniqueId; + this.creationTime = componentInstance.creationTime; + this.modificationTime = componentInstance.modificationTime; + this.propertyValueCounter = componentInstance.propertyValueCounter; + this.capabilities = new CapabilitiesGroup(componentInstance.capabilities); + this.requirements = new RequirementsGroup(componentInstance.requirements); + this.certified = componentInstance.certified; + this.customizationUUID = componentInstance.customizationUUID; + this.updatePosition(componentInstance.posX, componentInstance.posY); + this.groupInstances = componentInstance.groupInstances; + } + } + + 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 isComplex = () : boolean => { + return this.originType === ResourceType.VF; + } + + 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 => { + let temp = angular.copy(this); + temp.certified = undefined; + temp.iconSprite = undefined; + temp.inputs = undefined; + temp.groupInstances = undefined; + temp.properties = undefined; + temp.requirements = undefined; + temp.capabilities = undefined; + return temp; + }; +} diff --git a/catalog-ui/src/app/models/componentsInstances/productInstance.ts b/catalog-ui/src/app/models/componentsInstances/productInstance.ts new file mode 100644 index 0000000000..7b73f83988 --- /dev/null +++ b/catalog-ui/src/app/models/componentsInstances/productInstance.ts @@ -0,0 +1,13 @@ +/** + * Created by obarda on 2/4/2016. + */ +'use strict'; +import {ComponentInstance} from "./componentInstance"; + +export class ProductInstance extends ComponentInstance { + + constructor(componentInstance?:ProductInstance) { + super(componentInstance); + this.iconSprite = "sprite-product-icons"; + } +} diff --git a/catalog-ui/src/app/models/componentsInstances/resourceInstance.ts b/catalog-ui/src/app/models/componentsInstances/resourceInstance.ts new file mode 100644 index 0000000000..be4bde9af9 --- /dev/null +++ b/catalog-ui/src/app/models/componentsInstances/resourceInstance.ts @@ -0,0 +1,15 @@ +/** + * Created by obarda on 2/4/2016. + */ +'use strict'; +import {ComponentInstance} from "./componentInstance"; + +export class ResourceInstance extends ComponentInstance { + + constructor(componentInstance?:ResourceInstance) { + super(componentInstance); + + this.iconSprite = "sprite-resource-icons"; + } +} + diff --git a/catalog-ui/src/app/models/componentsInstances/serviceInstance.ts b/catalog-ui/src/app/models/componentsInstances/serviceInstance.ts new file mode 100644 index 0000000000..060519b83d --- /dev/null +++ b/catalog-ui/src/app/models/componentsInstances/serviceInstance.ts @@ -0,0 +1,14 @@ +/** + * Created by obarda on 2/4/2016. + */ +'use strict'; +import {ComponentInstance} from "./componentInstance"; + +export class ServiceInstance extends ComponentInstance { + + constructor(componentInstance?:ServiceInstance) { + super(componentInstance); + this.iconSprite = "sprite-services-icons"; + } +} + diff --git a/catalog-ui/src/app/models/csar-component.ts b/catalog-ui/src/app/models/csar-component.ts new file mode 100644 index 0000000000..301da959f7 --- /dev/null +++ b/catalog-ui/src/app/models/csar-component.ts @@ -0,0 +1,15 @@ +'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/src/app/models/data-type-properties.ts b/catalog-ui/src/app/models/data-type-properties.ts new file mode 100644 index 0000000000..a36e87b0fc --- /dev/null +++ b/catalog-ui/src/app/models/data-type-properties.ts @@ -0,0 +1,39 @@ +/** + * Created by rcohen on 9/25/2016. + */ +'use strict'; +import {SchemaPropertyGroupModel} from "./aschema-property"; +import {PropertyModel} from "./properties"; + +export class DataTypePropertyModel extends PropertyModel{ + //custom + simpleType:string; + valueObjectRef:any; + childrenProperties:Array<DataTypePropertyModel>; + isAllChildrenLevelsCalculated:boolean; + treeNodeId:string; + parent:DataTypePropertyModel; + expandedChildPropertyId:string; + + constructor(property?:PropertyModel); + constructor(name:string, type:string, treeNodeId:string, parent:DataTypePropertyModel, valueObjectRef:any, schema?:SchemaPropertyGroupModel); + constructor(nameOrPropertyObj?:string | PropertyModel, type?:string, treeNodeId?:string, parent?:DataTypePropertyModel, valueObjectRef?:any, schema?:SchemaPropertyGroupModel){ + super(typeof nameOrPropertyObj === "string" ? null : nameOrPropertyObj); + if ( typeof nameOrPropertyObj === "string" ) { + this.name = nameOrPropertyObj; + this.type = type; + this.treeNodeId = treeNodeId; + this.parent = parent; + this.valueObjectRef = valueObjectRef; + this.schema = schema; + } + } + + public updateExpandedChildPropertyId = (childPropertyId:string):void =>{ + if(this.expandedChildPropertyId == childPropertyId){ + this.expandedChildPropertyId = ""; + }else{ + this.expandedChildPropertyId = childPropertyId; + } + } +} diff --git a/catalog-ui/src/app/models/data-types-map.ts b/catalog-ui/src/app/models/data-types-map.ts new file mode 100644 index 0000000000..8aa38e26e7 --- /dev/null +++ b/catalog-ui/src/app/models/data-types-map.ts @@ -0,0 +1,17 @@ +/** + * Created by rcohen on 9/25/2016. + */ +'use strict'; +import {DataTypeModel} from "./data-types"; + +export class DataTypesMapData { + [dataTypeId:string]:Array<DataTypeModel>; +} + +export class DataTypesMap { + dataTypesMap:DataTypesMapData; + + constructor(dataTypesMap:DataTypesMapData) { + this.dataTypesMap = dataTypesMap; + } +} diff --git a/catalog-ui/src/app/models/data-types.ts b/catalog-ui/src/app/models/data-types.ts new file mode 100644 index 0000000000..060ebf7e7d --- /dev/null +++ b/catalog-ui/src/app/models/data-types.ts @@ -0,0 +1,34 @@ +/** + * Created by rcohen on 9/25/2016. + */ +'use strict'; +import {PropertyBEModel} from "./properties-inputs/property-be-model"; + +export class DataTypeModel { + + //server data + name:string; + uniqueId:string; + derivedFromName:string; + derivedFrom:DataTypeModel; + creationTime:string; + modificationTime:string; + properties: Array<PropertyBEModel>; + + 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/src/app/models/distribution.ts b/catalog-ui/src/app/models/distribution.ts new file mode 100644 index 0000000000..b6854c68a2 --- /dev/null +++ b/catalog-ui/src/app/models/distribution.ts @@ -0,0 +1,43 @@ +'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<DistributionComponent>; + public statusCount:any; + //custom data + public dateFormat:string; + + constructor() { + } + + public toJSON = ():any => { + this.dateFormat = undefined; + return this; + }; +} + + diff --git a/catalog-ui/src/app/models/export-excel.ts b/catalog-ui/src/app/models/export-excel.ts new file mode 100644 index 0000000000..7d0bc56a7b --- /dev/null +++ b/catalog-ui/src/app/models/export-excel.ts @@ -0,0 +1,12 @@ +/** + * Created by rcohen on 11/7/2016. + */ +'use strict'; + +export class ExportExcel { + fileName:string; + metaData:Array<string>;//array of text rows that display on the top of table + dataObj:any;//array of JSONs - the table data + tableHeaders:Array<string>; + groupByField:string;//[optional] get field name in order to split data to some tables group by this field +} diff --git a/catalog-ui/src/app/models/file-download.ts b/catalog-ui/src/app/models/file-download.ts new file mode 100644 index 0000000000..d63963e1ba --- /dev/null +++ b/catalog-ui/src/app/models/file-download.ts @@ -0,0 +1,6 @@ +'use strict'; + +export interface IFileDownload { + artifactName:string; + base64Contents:string; +} diff --git a/catalog-ui/src/app/models/filter-properties-assignment-data.ts b/catalog-ui/src/app/models/filter-properties-assignment-data.ts new file mode 100644 index 0000000000..067ac630de --- /dev/null +++ b/catalog-ui/src/app/models/filter-properties-assignment-data.ts @@ -0,0 +1,10 @@ +/** + * Created by rc2122 on 5/16/2017. + */ +export class FilterPropertiesAssignmentData { + propertyName:string; + selectedTypes:Array<string>; + constructor(){ + this.selectedTypes = []; + } +} diff --git a/catalog-ui/src/app/models/graph/assetPopoverObj.ts b/catalog-ui/src/app/models/graph/assetPopoverObj.ts new file mode 100644 index 0000000000..c4fea9b994 --- /dev/null +++ b/catalog-ui/src/app/models/graph/assetPopoverObj.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========================================================= + */ + +'use strict'; + +export class AssetPopoverObj { + + nodeId:string; + displayName:string; + menuPosition:Cy.Position; + menuSide:string; + isViewOnly:boolean; + VLArray:Array<any>; + CPArray:Array<any>; + + constructor(nodeId:string, displayName:string, menuPosition:Cy.Position, menuSide:string, isViewOnly?:boolean, VLArray?:Array<any>, CPArray?:Array<any>) { + this.nodeId = nodeId; + this.displayName = displayName; + this.menuPosition = {x: menuPosition.x, y: menuPosition.y}; + this.menuSide = menuSide; + this.isViewOnly = isViewOnly || false; + this.VLArray = VLArray || []; + this.CPArray = CPArray || []; + } +} + + diff --git a/catalog-ui/src/app/models/graph/graph-links/common-base-link.ts b/catalog-ui/src/app/models/graph/graph-links/common-base-link.ts new file mode 100644 index 0000000000..4d6d989daf --- /dev/null +++ b/catalog-ui/src/app/models/graph/graph-links/common-base-link.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. + */ + +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/src/app/models/graph/graph-links/common-ci-link-base.ts b/catalog-ui/src/app/models/graph/graph-links/common-ci-link-base.ts new file mode 100644 index 0000000000..12e66c86f9 --- /dev/null +++ b/catalog-ui/src/app/models/graph/graph-links/common-ci-link-base.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========================================================= + */ +import {Relationship, RelationshipModel} from "../relationship"; +import {CommonLinkBase} from "./common-base-link"; +/** + * Created by obarda on 6/29/2016. + */ + +export interface ICommonCiLinkBase { + +} + +export class CommonCiLinkBase extends CommonLinkBase implements ICommonCiLinkBase { + + relation:RelationshipModel; + + + constructor(relation?:RelationshipModel, singleRelationship?:Relationship) { + super(); + if (relation) { + if (singleRelationship) { + this.relation = new RelationshipModel(relation, singleRelationship); + } else { + this.relation = new RelationshipModel(relation); + } + this.source = relation.fromNode; + this.target = relation.toNode; + } else { + this.relation = new RelationshipModel(); + } + } +} diff --git a/catalog-ui/src/app/models/graph/graph-links/composition-graph-links/composition-ci-link-base.ts b/catalog-ui/src/app/models/graph/graph-links/composition-graph-links/composition-ci-link-base.ts new file mode 100644 index 0000000000..04b28c7ceb --- /dev/null +++ b/catalog-ui/src/app/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========================================================= + */ + +import {ICommonCiLinkBase, CommonCiLinkBase} from "../common-ci-link-base"; +import {RelationshipModel, Relationship} from "../../relationship"; + +export interface ICompositionCiLinkBase extends ICommonCiLinkBase { + updateLinkDirection():void; +} + +export class CompositionCiLinkBase extends CommonCiLinkBase implements ICompositionCiLinkBase { + + type:string; + visible:boolean; + + constructor(relation?:RelationshipModel, singleRelationship?:Relationship) { + super(relation, singleRelationship); + this.visible = true; + } + + public setRelation = (relation:RelationshipModel) => { + this.relation = relation; + }; + + updateLinkDirection():void { + this.source = this.relation.fromNode; + this.target = this.relation.toNode; + } +} diff --git a/catalog-ui/src/app/models/graph/graph-links/composition-graph-links/composition-ci-simple-link.ts b/catalog-ui/src/app/models/graph/graph-links/composition-graph-links/composition-ci-simple-link.ts new file mode 100644 index 0000000000..e213fa9b33 --- /dev/null +++ b/catalog-ui/src/app/models/graph/graph-links/composition-graph-links/composition-ci-simple-link.ts @@ -0,0 +1,32 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +import {CompositionCiLinkBase} from "./composition-ci-link-base"; +import {Relationship, RelationshipModel} from "../../relationship"; +import {GraphColors} from "../../../../utils/constants"; + +export class CompositionCiSimpleLink extends CompositionCiLinkBase { + + constructor(relation?:RelationshipModel, singleRelationship?:Relationship) { + super(relation, singleRelationship); + this.color = GraphColors.BASE_LINK; + this.classes = 'simple-link'; + } +} diff --git a/catalog-ui/src/app/models/graph/graph-links/composition-graph-links/composition-ci-ucpe-host-link.ts b/catalog-ui/src/app/models/graph/graph-links/composition-graph-links/composition-ci-ucpe-host-link.ts new file mode 100644 index 0000000000..b756dab129 --- /dev/null +++ b/catalog-ui/src/app/models/graph/graph-links/composition-graph-links/composition-ci-ucpe-host-link.ts @@ -0,0 +1,32 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ +import {RelationshipModel, Relationship} from "../../relationship"; +import {CompositionCiLinkBase} from "./composition-ci-link-base"; +/** + * Created by obarda on 4/20/2016. + */ +export class LinkUcpeHost extends CompositionCiLinkBase { + + constructor(relation?:RelationshipModel, singleRelationship?:Relationship) { + super(relation, singleRelationship); + this.visible = false; + this.classes = "ucpe-host-link"; + } +} diff --git a/catalog-ui/src/app/models/graph/graph-links/composition-graph-links/composition-ci-ucpe-link.ts b/catalog-ui/src/app/models/graph/graph-links/composition-graph-links/composition-ci-ucpe-link.ts new file mode 100644 index 0000000000..fc8631781a --- /dev/null +++ b/catalog-ui/src/app/models/graph/graph-links/composition-graph-links/composition-ci-ucpe-link.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========================================================= + */ + +import {RelationshipModel, Relationship} from "../../relationship"; +import {CompositionCiLinkBase} from "./composition-ci-link-base"; +import {GraphColors} from "../../../../utils/constants"; +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 = GraphColors.BASE_LINK; + } + + updateLinkDirection():void { + } +} diff --git a/catalog-ui/src/app/models/graph/graph-links/composition-graph-links/composition-ci-vl-link.ts b/catalog-ui/src/app/models/graph/graph-links/composition-graph-links/composition-ci-vl-link.ts new file mode 100644 index 0000000000..e0824ccbe8 --- /dev/null +++ b/catalog-ui/src/app/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========================================================= + */ + +import {CompositionCiLinkBase} from "./composition-ci-link-base"; +import {RelationshipModel, Relationship} from "../../relationship"; +import {GraphColors} from "../../../../utils/constants"; + +export class CompositionCiVLink extends CompositionCiLinkBase { + + constructor(relation?:RelationshipModel, singleRelationship?:Relationship) { + super(relation, singleRelationship); + this.color = GraphColors.VL_LINK; + this.classes = 'vl-link'; + } + + +} diff --git a/catalog-ui/src/app/models/graph/graph-links/composition-graph-links/composition-ci-vl-ucpe-link.ts b/catalog-ui/src/app/models/graph/graph-links/composition-graph-links/composition-ci-vl-ucpe-link.ts new file mode 100644 index 0000000000..854eeecad5 --- /dev/null +++ b/catalog-ui/src/app/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========================================================= + */ +import {CompositionCiUcpeLink} from "./composition-ci-ucpe-link"; +import {Relationship, RelationshipModel} from "../../relationship"; +import {GraphColors} from "../../../../utils/constants"; +/** + * Created by obarda on 4/20/2016. + */ + +export class CompositionCiVlUcpeLink extends CompositionCiUcpeLink { + + constructor(relation?:RelationshipModel, from?:boolean, singleRelation?:Relationship) { + super(relation, from, singleRelation); + this.color = GraphColors.VL_LINK; + } +} diff --git a/catalog-ui/src/app/models/graph/graph-links/links-factory.ts b/catalog-ui/src/app/models/graph/graph-links/links-factory.ts new file mode 100644 index 0000000000..7fdc0838ed --- /dev/null +++ b/catalog-ui/src/app/models/graph/graph-links/links-factory.ts @@ -0,0 +1,79 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR 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. + */ +'use strict'; +import {RelationshipModel, Relationship, CompositionCiLinkBase, CompositionCiNodeBase, LinkUcpeHost, CompositionCiUcpeLink, + CompositionCiVlUcpeLink, CompositionCiSimpleLink, ModuleCiLinkBase, ModuleCiVlLink, CompositionCiVLink} from "../../../models"; + +export class LinksFactory { + + constructor() { + } + + public createGraphLink = (cy:Cy.Instance, relation:RelationshipModel, singleRelation:Relationship):CompositionCiLinkBase => { + + let newRelation:CompositionCiLinkBase; + + let fromNode:CompositionCiNodeBase = cy.getElementById(relation.fromNode).data(); + let toNode: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 LinkUcpeHost(relation, singleRelation); + } else if (singleRelation.relationship.type && _.includes(singleRelation.relationship.type.toLowerCase(), 'link')) { + newRelation = new CompositionCiVlUcpeLink(relation, fromNode.isUcpePart, singleRelation); + } else { + newRelation = new CompositionCiUcpeLink(relation, fromNode.isUcpePart, singleRelation); + } + } else if (singleRelation.relationship.type && _.includes(singleRelation.relationship.type.toLowerCase(), 'link')) { + newRelation = new CompositionCiVLink(relation, singleRelation); + } else { + newRelation = new CompositionCiSimpleLink(relation, singleRelation); + } + + return newRelation; + }; + + public createUcpeHostLink = (relation:RelationshipModel):LinkUcpeHost => { + return new LinkUcpeHost(relation); + }; + + public createVLLink = (relation:RelationshipModel):CompositionCiVLink => { + return new CompositionCiVLink(relation); + } + + + public createModuleGraphLinks = (relation:RelationshipModel, singleRelation:Relationship):ModuleCiLinkBase => { + + let newRelation:ModuleCiLinkBase; + + if (_.includes(singleRelation.relationship.type.toLowerCase(), 'link')) { + newRelation = new ModuleCiVlLink(relation, singleRelation); + } else { + newRelation = new ModuleCiLinkBase(relation, singleRelation); + } + + return newRelation; + }; + +} diff --git a/catalog-ui/src/app/models/graph/graph-links/module-graph-links/module-ci-link-base.ts b/catalog-ui/src/app/models/graph/graph-links/module-graph-links/module-ci-link-base.ts new file mode 100644 index 0000000000..73cd2923eb --- /dev/null +++ b/catalog-ui/src/app/models/graph/graph-links/module-graph-links/module-ci-link-base.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. + */ +import {Relationship, RelationshipModel, ICommonCiLinkBase, CommonCiLinkBase} from "../../../../models"; +import {GraphColors} from "../../../../utils/constants"; + +export interface IModuleCiLinkBase extends ICommonCiLinkBase { + +} + +export class ModuleCiLinkBase extends CommonCiLinkBase implements IModuleCiLinkBase { + + constructor(relation?:RelationshipModel, singleRelationship?:Relationship) { + super(relation, singleRelationship); + this.color = GraphColors.BASE_LINK; + } + +} diff --git a/catalog-ui/src/app/models/graph/graph-links/module-graph-links/module-ci-vl-link.ts b/catalog-ui/src/app/models/graph/graph-links/module-graph-links/module-ci-vl-link.ts new file mode 100644 index 0000000000..0bc675d465 --- /dev/null +++ b/catalog-ui/src/app/models/graph/graph-links/module-graph-links/module-ci-vl-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========================================================= + */ +import {Relationship, RelationshipModel, ICommonCiLinkBase, CommonCiLinkBase} from "../../../../models"; +import {GraphColors} from "../../../../utils/constants"; + +export interface IModuleCiVlLink extends ICommonCiLinkBase { + +} + +export class ModuleCiVlLink extends CommonCiLinkBase implements IModuleCiVlLink { + + constructor(relation?:RelationshipModel, singleRelationship?:Relationship) { + super(relation, singleRelationship); + this.color = GraphColors.VL_LINK; + } +} diff --git a/catalog-ui/src/app/models/graph/graphTooltip.ts b/catalog-ui/src/app/models/graph/graphTooltip.ts new file mode 100644 index 0000000000..790608e7f7 --- /dev/null +++ b/catalog-ui/src/app/models/graph/graphTooltip.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========================================================= + */ +'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/src/app/models/graph/link-menu.ts b/catalog-ui/src/app/models/graph/link-menu.ts new file mode 100644 index 0000000000..b3352dd815 --- /dev/null +++ b/catalog-ui/src/app/models/graph/link-menu.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========================================================= + */ +'use strict'; +import {Point} from "./point"; + +export class LinkMenu { + position:Point; + isShow:boolean; + link:Cy.CollectionFirstEdge; + + constructor(); + constructor(point:Point, isShow:boolean, link:Cy.CollectionFirstEdge); + constructor(point?:Point, isShow?:boolean, link?:Cy.CollectionFirstEdge) { + this.position = point ? point : new Point(); + this.isShow = isShow ? isShow : false; + this.link = link ? link : null; + } +} + diff --git a/catalog-ui/src/app/models/graph/match-relation.ts b/catalog-ui/src/app/models/graph/match-relation.ts new file mode 100644 index 0000000000..2de2e930b6 --- /dev/null +++ b/catalog-ui/src/app/models/graph/match-relation.ts @@ -0,0 +1,111 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ +'use strict'; +import {Requirement} from "../requirement"; +import {Capability} from "../capability"; +import {Relationship, RelationshipModel, RelationType} from "./relationship"; + +export class MatchBase { + requirement:Requirement; + isFromTo:boolean; + fromNode:string; + toNode:string; + + constructor(requirement: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:Requirement; + + constructor(requirement:Requirement, secondRequirement: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:Capability; + + constructor(requirement:Requirement, capability:Capability, isFromTo:boolean, fromNode:string, toNode:string) { + super(requirement, isFromTo, fromNode, toNode); + this.capability = capability; + } + + public matchToRelation = ():Relationship => { + let relationship:Relationship = new Relationship(); + relationship.capability = this.capability.name; + relationship.capabilityOwnerId = this.capability.ownerId; + relationship.capabilityUid = this.capability.uniqueId; + relationship.relationship = new 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 = ():RelationshipModel => { + let relationshipModel:RelationshipModel = new RelationshipModel(); + let relationship:Relationship = this.matchToRelation(); + relationshipModel.setRelationshipModelParams(this.fromNode, this.toNode, [relationship]); + return relationshipModel; + }; +} + + diff --git a/catalog-ui/src/app/models/graph/nodes/base-common-node.ts b/catalog-ui/src/app/models/graph/nodes/base-common-node.ts new file mode 100644 index 0000000000..aee6b02086 --- /dev/null +++ b/catalog-ui/src/app/models/graph/nodes/base-common-node.ts @@ -0,0 +1,70 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR 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. + */ +'use strict'; +import {AngularJSBridge} from "../../../services/angular-js-bridge-service"; + +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 = 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/src/app/models/graph/nodes/common-ci-node-base.ts b/catalog-ui/src/app/models/graph/nodes/common-ci-node-base.ts new file mode 100644 index 0000000000..98fb583625 --- /dev/null +++ b/catalog-ui/src/app/models/graph/nodes/common-ci-node-base.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========================================================= + */ +import {CommonNodeBase} from "./base-common-node"; +import {ComponentInstance} from "../../componentsInstances/componentInstance"; +export abstract class CommonCINodeBase extends CommonNodeBase { + + public certified:boolean; + public template:string; + public componentInstance:ComponentInstance; + public group:string; + + constructor(instance: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/src/app/models/graph/nodes/composition-graph-nodes/composition-ci-node-base.ts b/catalog-ui/src/app/models/graph/nodes/composition-graph-nodes/composition-ci-node-base.ts new file mode 100644 index 0000000000..d17d97ac19 --- /dev/null +++ b/catalog-ui/src/app/models/graph/nodes/composition-graph-nodes/composition-ci-node-base.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========================================================= + */ + +import {ComponentInstance} from "../../../componentsInstances/componentInstance"; +import {CommonCINodeBase} from "../common-ci-node-base"; +import {ImageCreatorService} from "app/directives/graphs-v2/image-creator/image-creator.service"; +import {ImagesUrl} from "app/utils"; +import {AngularJSBridge} from "app/services"; + +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:ComponentInstance, + public imageCreator: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 + ImagesUrl.RESOURCE_ICONS + this.componentInstance.icon + '.png', + this.imagesPath + ImagesUrl.RESOURCE_ICONS + 'uncertified.png') + .then(imageBase64 => { + this.img = imageBase64; + node.style({'background-image': this.img}); + }); + + return this.img; + } + + protected getDisplayName():string { + + let graphResourceName = AngularJSBridge.getFilter('graphResourceName'); + let resourceName = AngularJSBridge.getFilter('resourceName'); + return graphResourceName(resourceName(this.componentInstance.name)); + } + +} diff --git a/catalog-ui/src/app/models/graph/nodes/composition-graph-nodes/composition-ci-node-cp.ts b/catalog-ui/src/app/models/graph/nodes/composition-graph-nodes/composition-ci-node-cp.ts new file mode 100644 index 0000000000..e6d0cc178f --- /dev/null +++ b/catalog-ui/src/app/models/graph/nodes/composition-graph-nodes/composition-ci-node-cp.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========================================================= + */ + +import {CompositionCiNodeBase} from "./composition-ci-node-base"; +import {ComponentInstance} from "../../../componentsInstances/componentInstance"; +import {ImageCreatorService} from "../../../../directives/graphs-v2/image-creator/image-creator.service"; +import {AngularJSBridge} from "../../../../services/angular-js-bridge-service"; +import {ImagesUrl} from "../../../../utils/constants"; + +export class CompositionCiNodeCp extends CompositionCiNodeBase { + + constructor(instance:ComponentInstance, + imageCreator:ImageCreatorService) { + super(instance, imageCreator); + this.initCp(); + } + + private initCp():void { + let sdcConfig = AngularJSBridge.getAngularConfig(); + this.img = sdcConfig.imagesPath + 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/src/app/models/graph/nodes/composition-graph-nodes/composition-ci-node-service.ts b/catalog-ui/src/app/models/graph/nodes/composition-graph-nodes/composition-ci-node-service.ts new file mode 100644 index 0000000000..dd2fb6c6dd --- /dev/null +++ b/catalog-ui/src/app/models/graph/nodes/composition-graph-nodes/composition-ci-node-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========================================================= + */ + +import {ImagesUrl} from "../../../../utils/constants"; +import {ComponentInstance, CompositionCiNodeBase} from "../../../../models"; +import {ImageCreatorService} from "../../../../directives/graphs-v2/image-creator/image-creator.service"; +export class CompositionCiNodeService extends CompositionCiNodeBase { + + constructor(instance:ComponentInstance, + imageCreator:ImageCreatorService) { + super(instance, imageCreator); + this.initService(); + } + + private initService():void { + + this.img = this.imagesPath + 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/src/app/models/graph/nodes/composition-graph-nodes/composition-ci-node-ucpe-cp.ts b/catalog-ui/src/app/models/graph/nodes/composition-graph-nodes/composition-ci-node-ucpe-cp.ts new file mode 100644 index 0000000000..37882963d3 --- /dev/null +++ b/catalog-ui/src/app/models/graph/nodes/composition-graph-nodes/composition-ci-node-ucpe-cp.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========================================================= + */ + +import {CompositionCiNodeCp, ComponentInstance} from "./../../../../models"; +import {ImageCreatorService} from "../../../../directives/graphs-v2/image-creator/image-creator.service"; + +export class CompositionCiNodeUcpeCp extends CompositionCiNodeCp { + + constructor(instance:ComponentInstance, + imageCreator: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/src/app/models/graph/nodes/composition-graph-nodes/composition-ci-node-ucpe.ts b/catalog-ui/src/app/models/graph/nodes/composition-graph-nodes/composition-ci-node-ucpe.ts new file mode 100644 index 0000000000..d209a10019 --- /dev/null +++ b/catalog-ui/src/app/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========================================================= + */ + +import {ComponentInstance} from "../../../componentsInstances/componentInstance"; +import {ImageCreatorService} from "../../../../directives/graphs-v2/image-creator/image-creator.service"; +import {CompositionCiNodeBase} from "./composition-ci-node-base"; + +export class NodeUcpe extends CompositionCiNodeBase { + constructor(instance:ComponentInstance, + imageCreator: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/src/app/models/graph/nodes/composition-graph-nodes/composition-ci-node-vf.ts b/catalog-ui/src/app/models/graph/nodes/composition-graph-nodes/composition-ci-node-vf.ts new file mode 100644 index 0000000000..cb0cd59b13 --- /dev/null +++ b/catalog-ui/src/app/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========================================================= + */ +import {CompositionCiNodeBase} from "./composition-ci-node-base"; +import {ComponentInstance} from "../../../componentsInstances/componentInstance"; +import {ImageCreatorService} from "../../../../directives/graphs-v2/image-creator/image-creator.service"; +import {ImagesUrl} from "../../../../utils/constants"; + +export class CompositionCiNodeVf extends CompositionCiNodeBase { + + constructor(instance:ComponentInstance, + imageCreator:ImageCreatorService) { + super(instance, imageCreator); + this.initVf(); + } + + private initVf():void { + this.img = this.imagesPath + 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/src/app/models/graph/nodes/composition-graph-nodes/composition-ci-node-vfc.ts b/catalog-ui/src/app/models/graph/nodes/composition-graph-nodes/composition-ci-node-vfc.ts new file mode 100644 index 0000000000..e08e6a4915 --- /dev/null +++ b/catalog-ui/src/app/models/graph/nodes/composition-graph-nodes/composition-ci-node-vfc.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========================================================= + */ +import {CompositionCiNodeBase} from "./composition-ci-node-base"; +import {ImageCreatorService} from "../../../../directives/graphs-v2/image-creator/image-creator.service"; +import {ComponentInstance} from "../../../componentsInstances/componentInstance"; +import {ImagesUrl} from "../../../../utils/constants"; + +export class CompositionCiNodeVfc extends CompositionCiNodeBase { + constructor(instance:ComponentInstance, imageCreator:ImageCreatorService) { + super(instance, imageCreator); + this.initVfc(); + } + + private initVfc():void { + this.img = this.imagesPath + ImagesUrl.RESOURCE_ICONS + this.componentInstance.icon + '.png'; + } +} diff --git a/catalog-ui/src/app/models/graph/nodes/composition-graph-nodes/composition-ci-node-vl.ts b/catalog-ui/src/app/models/graph/nodes/composition-graph-nodes/composition-ci-node-vl.ts new file mode 100644 index 0000000000..596d12058f --- /dev/null +++ b/catalog-ui/src/app/models/graph/nodes/composition-graph-nodes/composition-ci-node-vl.ts @@ -0,0 +1,56 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ +import {ComponentInstance} from "../../../componentsInstances/componentInstance"; +import {ImageCreatorService} from "../../../../directives/graphs-v2/image-creator/image-creator.service"; +import {CompositionCiNodeBase} from "./composition-ci-node-base"; +import {ImagesUrl} from "../../../../utils/constants"; + +export class CompositionCiNodeVl extends CompositionCiNodeBase { + private toolTipText:string; + + constructor(instance:ComponentInstance, imageCreator:ImageCreatorService) { + super(instance, imageCreator); + this.initVl(); + + } + + private initVl():void { + this.type = "basic-small-node"; + this.toolTipText = 'Point to point'; + if(this.componentInstance.capabilities) { + 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 + ImagesUrl.RESOURCE_ICONS + 'vl.png'; + + this.classes = 'vl-node'; + if (!this.certified) { + this.classes = this.classes + ' not-certified'; + } + } +} diff --git a/catalog-ui/src/app/models/graph/nodes/modules-graph-nodes/module-node-base.ts b/catalog-ui/src/app/models/graph/nodes/modules-graph-nodes/module-node-base.ts new file mode 100644 index 0000000000..51e3591304 --- /dev/null +++ b/catalog-ui/src/app/models/graph/nodes/modules-graph-nodes/module-node-base.ts @@ -0,0 +1,51 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR 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. + */ +'use strict'; +import {ImagesUrl} from "../../../../utils/constants"; +import {Module} from "../../../modules/base-module"; +import {CommonNodeBase} from "../base-common-node"; + +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 = ImagesUrl.MODULE_ICON; + this.classes = "module-node"; + + } +} diff --git a/catalog-ui/src/app/models/graph/nodes/nodes-factory.ts b/catalog-ui/src/app/models/graph/nodes/nodes-factory.ts new file mode 100644 index 0000000000..6c4f71e722 --- /dev/null +++ b/catalog-ui/src/app/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========================================================= + */ +'use strict'; + +import {CompositionCiNodeUcpeCp, Module, ModuleNodeBase, CompositionCiNodeVf, CompositionCiNodeVl, CompositionCiNodeCp, + NodeUcpe, CompositionCiNodeService, CompositionCiNodeBase, ComponentInstance} from "./../../../models"; +import {ComponentType, ResourceType} from "../../../utils/constants"; +import {ImageCreatorService} from "../../../directives/graphs-v2/image-creator/image-creator.service"; + +export class NodesFactory { + + constructor(private imageCreator:ImageCreatorService) { + } + + public createNode = (instance:ComponentInstance):CompositionCiNodeBase => { + + if (instance.isUcpe()) { + return new NodeUcpe(instance, this.imageCreator); + } + if (instance.originType === ComponentType.SERVICE) { + return new CompositionCiNodeService(instance, this.imageCreator); + } + if (instance.originType === ResourceType.CP) { + return new CompositionCiNodeCp(instance, this.imageCreator); + } + if (instance.originType === ResourceType.VL) { + return new CompositionCiNodeVl(instance, this.imageCreator); + } + + return new CompositionCiNodeVf(instance, this.imageCreator); + }; + + public createModuleNode = (module:Module):ModuleNodeBase => { + + return new ModuleNodeBase(module); + }; + + public createUcpeCpNode = (instance:ComponentInstance):CompositionCiNodeCp => { + + return new CompositionCiNodeUcpeCp(instance, this.imageCreator); + } +} + +NodesFactory.$inject = [ + 'ImageCreatorService' +]; diff --git a/catalog-ui/src/app/models/graph/point.ts b/catalog-ui/src/app/models/graph/point.ts new file mode 100644 index 0000000000..8fece5af99 --- /dev/null +++ b/catalog-ui/src/app/models/graph/point.ts @@ -0,0 +1,20 @@ +/** + * Created by obarda on 11/7/2016. + */ +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; +} diff --git a/catalog-ui/src/app/models/graph/relationMenuObjects.ts b/catalog-ui/src/app/models/graph/relationMenuObjects.ts new file mode 100644 index 0000000000..aaef0b218c --- /dev/null +++ b/catalog-ui/src/app/models/graph/relationMenuObjects.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========================================================= + */ +'use strict'; +import {MatchReqToReq, MatchBase} from "./match-relation"; +import {CompositionCiNodeBase} from "./nodes/composition-graph-nodes/composition-ci-node-base"; +import {Component} from "../components/component"; +import {ComponentInstance} from "../componentsInstances/componentInstance"; + +export class RelationMenuDirectiveObj { + + fromNode:CompositionCiNodeBase; + toNode:CompositionCiNodeBase; + menuPosition:Cy.Position; + rightSideLink:GraphLinkMenuSide; + leftSideLink:GraphLinkMenuSide; + selectionText:string; + vlType:string; + + constructor(fromNode:CompositionCiNodeBase, toNode:CompositionCiNodeBase, menuPosition:Cy.Position, possibleRelations:Array<MatchBase>) { + this.fromNode = fromNode; + this.toNode = toNode; + // this.modelLinks = modelLinks; + 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 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: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:ComponentInstance) { + this.componentInstance = componentInstance; + this.capabilities = {}; + this.requirements = {}; + } + + public selectMatchArr(matchArr:Array<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/src/app/models/graph/relationship.ts b/catalog-ui/src/app/models/graph/relationship.ts new file mode 100644 index 0000000000..6345ab4c04 --- /dev/null +++ b/catalog-ui/src/app/models/graph/relationship.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========================================================= + */ +'use strict'; +import {Capability} from "../capability"; +import {Requirement} from "../requirement"; + +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:Relationship):void => { + this.relationships.push(new 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?: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:Capability, requirement:Requirement)=> { + this.capability = capability.name; + this.capabilityOwnerId = capability.ownerId; + this.capabilityUid = capability.uniqueId; + this.relationship = new RelationType(capability.type); + this.requirement = requirement.name; + this.requirementOwnerId = requirement.ownerId; + this.requirementUid = requirement.uniqueId; + }; + +} diff --git a/catalog-ui/src/app/models/heat-parameters.ts b/catalog-ui/src/app/models/heat-parameters.ts new file mode 100644 index 0000000000..be5ad92fb0 --- /dev/null +++ b/catalog-ui/src/app/models/heat-parameters.ts @@ -0,0 +1,18 @@ +/** + * Created by rcohen on 10/31/2016. + */ +'use strict'; + +export class HeatParameterModel { + uniqueId:string; + name:string; + type:string; + description:string; + currentValue:string; + defaultValue:string; + + constructor(parameter?:HeatParameterModel) { + } + +} + diff --git a/catalog-ui/src/app/models/input-property-base.ts b/catalog-ui/src/app/models/input-property-base.ts new file mode 100644 index 0000000000..dce76d597e --- /dev/null +++ b/catalog-ui/src/app/models/input-property-base.ts @@ -0,0 +1,26 @@ +/** + * Created by obarda on 1/22/2017. + */ +'use strict'; +import {SchemaPropertyGroupModel} from "./aschema-property"; + +export interface InputPropertyBase { + + uniqueId:string; + name:string; + defaultValue:string; + description:string; + password:boolean; + required:boolean; + type:string; + parentUniqueId:string; + schema:SchemaPropertyGroupModel; + componentInstanceId:string; + + //instance properties + value:string; + + //custom properties + isAlreadySelected:boolean; +} + diff --git a/catalog-ui/src/app/models/inputs-and-properties.ts b/catalog-ui/src/app/models/inputs-and-properties.ts new file mode 100644 index 0000000000..18ff928918 --- /dev/null +++ b/catalog-ui/src/app/models/inputs-and-properties.ts @@ -0,0 +1,22 @@ +/** + * Created by obarda on 1/11/2017. + */ +'use strict'; +import {PropertyModel} from "./properties"; +import {InputModel} from "./inputs"; + +export class InputsAndProperties { + + inputs:Array<InputModel>; + properties:Array<PropertyModel>; + + + constructor(inputs?:Array<InputModel>, properties?:Array<PropertyModel>) { + if (inputs && inputs.length > 0) { + this.inputs = inputs; + } + if (properties && properties.length > 0) { + this.properties = properties; + } + } +} diff --git a/catalog-ui/src/app/models/inputs.ts b/catalog-ui/src/app/models/inputs.ts new file mode 100644 index 0000000000..55e739b452 --- /dev/null +++ b/catalog-ui/src/app/models/inputs.ts @@ -0,0 +1,80 @@ +/** + * Created by obarda on 8/24/2016. + */ +'use strict'; +import {PropertyModel} from "./properties"; +import {InputPropertyBase} from "./input-property-base"; +import {SchemaPropertyGroupModel} from "./aschema-property"; + +export interface IInputModel extends InputPropertyBase { + //server data + definition:boolean; + value:string; + componentInstanceName:string; + //costom properties + isNew:boolean; + properties:Array<PropertyModel>; + inputs:Array<InputModel>; + filterTerm:string; + +} +export class InputModel implements IInputModel { + + //server data + uniqueId:string; + name:string; + type:string; + password:boolean; + required:boolean; + definition:boolean; + parentUniqueId:string; + description:string; + componentInstanceName:string; + componentInstanceId:string; + schema:SchemaPropertyGroupModel; + defaultValue:string; + value:string; + + //costom properties + isNew:boolean; + isDeleteDisabled:boolean; + properties:Array<PropertyModel>; + inputs:Array<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.schema = input.schema; + this.defaultValue = input.defaultValue; + this.value = input.value; + this.filterTerm = this.name + ' ' + this.description + ' ' + this.type + ' ' + this.componentInstanceName; + this.inputs = input.inputs; + this.properties = input.properties; + } + } + + public toJSON = ():any => { + let input = angular.copy(this); + input.isNew = undefined; + input.isDeleteDisabled = undefined; + input.properties = undefined; + input.inputs = undefined; + input.isAlreadySelected = undefined; + input.filterTerm = undefined; + return input; + }; +} + diff --git a/catalog-ui/src/app/models/instance-inputs-properties-map.ts b/catalog-ui/src/app/models/instance-inputs-properties-map.ts new file mode 100644 index 0000000000..47b99dfffd --- /dev/null +++ b/catalog-ui/src/app/models/instance-inputs-properties-map.ts @@ -0,0 +1,67 @@ +/** + * Created by obarda on 9/12/2016. + */ +'use strict'; +import {InputPropertyBase} from "./input-property-base"; +import {PropertyModel} from "./properties"; +import {InputModel} from "./inputs"; + +export class InstancesInputsOrPropertiesMapData { + [instanceId:string]:Array<InputPropertyBase>; +} + +export class InstancesInputsPropertiesMap { + componentInstanceProperties:InstancesInputsOrPropertiesMapData; + componentInstanceInputsMap:InstancesInputsOrPropertiesMapData; + + constructor(componentInstanceInputsMapData:InstancesInputsOrPropertiesMapData, componentInstanceInputsPropertiesMapData:InstancesInputsOrPropertiesMapData) { + this.componentInstanceInputsMap = componentInstanceInputsMapData; + this.componentInstanceProperties = componentInstanceInputsPropertiesMapData; + } + + private removeUnnecessaryData = (properties:Array<InputPropertyBase>, instanceId:string, mapData:any) => { + mapData[instanceId] = []; + if (properties && properties.length > 0) { + _.forEach(properties, (propertyOrInput:InputPropertyBase) => { + if (propertyOrInput instanceof PropertyModel) { // Handle Properties + if (propertyOrInput && !propertyOrInput.isAlreadySelected) { + mapData[instanceId].push(propertyOrInput); + } + } + if (propertyOrInput instanceof InputModel) { // Handle Inputs + if (propertyOrInput && !propertyOrInput.isAlreadySelected) { + mapData[instanceId].push(propertyOrInput); + } + } + }); + if (mapData[instanceId].length === 0) { + delete mapData[instanceId]; + } + } else { + delete mapData[instanceId]; + } + } + + /* + In the toJson we remove all inputs and property already selected (The check box selected but they are disable) + also we remove empty array in order to prevent Backend error + */ + + public cleanUnnecessaryDataBeforeSending = ():InstancesInputsPropertiesMap => { + + let map:InstancesInputsPropertiesMap = new InstancesInputsPropertiesMap(new InstancesInputsOrPropertiesMapData(), new InstancesInputsOrPropertiesMapData()); + angular.copy(this, map); + + //Removing unnecessary data from inputs map + _.forEach(map.componentInstanceInputsMap, (inputs:Array<InputModel>, instanceId:string) => { + this.removeUnnecessaryData(inputs, instanceId, map.componentInstanceInputsMap); + }); + + //Removing unnecessary data from properties map + _.forEach(map.componentInstanceProperties, (properties:Array<PropertyModel>, instanceId:string) => { + this.removeUnnecessaryData(properties, instanceId, map.componentInstanceProperties); + }); + + return map; + }; +} diff --git a/catalog-ui/src/app/models/left-panel.ts b/catalog-ui/src/app/models/left-panel.ts new file mode 100644 index 0000000000..759d5f317e --- /dev/null +++ b/catalog-ui/src/app/models/left-panel.ts @@ -0,0 +1,11 @@ +'use strict'; + +export class LeftPanelModel { + numberOfElements:number; + sortedCategories:any; + + constructor() { + this.numberOfElements = 0; + this.sortedCategories = {}; + } +} diff --git a/catalog-ui/src/app/models/member.ts b/catalog-ui/src/app/models/member.ts new file mode 100644 index 0000000000..a2d9d17aec --- /dev/null +++ b/catalog-ui/src/app/models/member.ts @@ -0,0 +1,16 @@ +/** + * Created by obarda on 8/2/2016. + */ +'use strict'; + +export class Members { + + [index:string]:string; + + constructor(members?:Members) { + _.forEach(members, (memberId:string, index) => { + this[index] = memberId; + }); + } +} + diff --git a/catalog-ui/src/app/models/modules/base-module.ts b/catalog-ui/src/app/models/modules/base-module.ts new file mode 100644 index 0000000000..4a5f282468 --- /dev/null +++ b/catalog-ui/src/app/models/modules/base-module.ts @@ -0,0 +1,95 @@ +/** + * Created by obarda on 6/30/2016. + */ +/** + * Created by obarda on 2/4/2016. + */ +'use strict'; +import {PropertyModel} from "../properties"; +import {ArtifactModel} from "../artifacts"; +import {CommonUtils} from "../../utils/common-utils"; +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<ArtifactModel>; + public artifactsUuid:Array<string>; + public properties:Array<PropertyModel>; + public members:Array<string>; + public customizationUUID:string; + public groupInstanceUniqueId:string; // This will only have a value if this is a group instance + + 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 = CommonUtils.initProperties(module.properties); + this.members = module.members; + this.customizationUUID = module.customizationUUID; + this.groupInstanceUniqueId = module.groupInstanceUniqueId; + this.name = this.name.replace(/:/g, '..'); + + } + } +} + +export class DisplayModule extends Module { + + isBase:string; + artifacts:Array<ArtifactModel>; + + //custom properties + public vfInstanceName:string; + public heatName:string; + public moduleName:string; + public customizationUUID:string; + + + constructor(displayModule?:DisplayModule) { + super(displayModule); + + this.isBase = displayModule.isBase; + this.customizationUUID = displayModule.customizationUUID; + 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<ArtifactModel>):void => { + this.artifacts = new Array<ArtifactModel>(); + _.forEach(artifacts, (artifact:ArtifactModel) => { + this.artifacts.push(new 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/src/app/models/properties-inputs/derived-fe-property.ts b/catalog-ui/src/app/models/properties-inputs/derived-fe-property.ts new file mode 100644 index 0000000000..1d79353453 --- /dev/null +++ b/catalog-ui/src/app/models/properties-inputs/derived-fe-property.ts @@ -0,0 +1,72 @@ +import { SchemaPropertyGroupModel, SchemaProperty } from '../aschema-property'; +import { PROPERTY_DATA, PROPERTY_TYPES} from 'app/utils'; +import { PropertyBEModel } from '../../models'; + +export enum DerivedPropertyType { + SIMPLE, + LIST, + MAP, + COMPLEX //other datatype, list of non-simple, or map of non-simple +} + +export class DerivedFEProperty extends PropertyBEModel { + parentName: string; + propertiesName: string; //"network_assignments#ipv4_subnet#use_ipv4 = parentPath + name + derivedDataType: DerivedPropertyType; + isDeclared: boolean; + isSelected: boolean; + isDisabled: boolean; + isChildOfListOrMap: boolean; + + constructor(property: PropertyBEModel, parentName?: string) + constructor(name: string, parentName: string, type: string, value: string, isChildOfListOrMap?:boolean, schema?: SchemaPropertyGroupModel); + constructor(nameOrPropertyObj?: string | PropertyBEModel, parentName?: string, type?: string, value?: string, isChildOfListOrMap?: boolean, schema?: SchemaPropertyGroupModel) { + + super(typeof nameOrPropertyObj === 'string' ? null : nameOrPropertyObj); + + if (typeof nameOrPropertyObj !== 'string') { //constructor #1 + this.parentName = parentName ? parentName : null; + this.propertiesName = (parentName) ? parentName + '#' + nameOrPropertyObj.name : nameOrPropertyObj.name; + } else { //constructor #2 + this.name = nameOrPropertyObj; + this.type = type; + this.parentName = parentName; + this.propertiesName = parentName + '#' + nameOrPropertyObj; + this.value = value; + if (schema) { + this.schema = new SchemaPropertyGroupModel(new SchemaProperty(schema.property)); + } + } + this.derivedDataType = this.getDerivedPropertyType(); + this.isChildOfListOrMap = (isChildOfListOrMap) ? isChildOfListOrMap : false; + } + + public getDerivedPropertyType = () => { + if (PROPERTY_DATA.SIMPLE_TYPES.indexOf(this.type) > -1) { + return DerivedPropertyType.SIMPLE; + } else if (this.type == PROPERTY_TYPES.LIST) { + return DerivedPropertyType.LIST; + } else if (this.type == PROPERTY_TYPES.MAP) { + return DerivedPropertyType.MAP; + } else { + return DerivedPropertyType.COMPLEX; + } + } + +} +export class DerivedFEPropertyMap { + [parentPath: string]: Array<DerivedFEProperty>; +} + + + +// isDataType: boolean; + + +// canAdd: boolean; +// canCollapse: boolean; +// canBeDeclared: boolean; + +// derivedValue: string; +// derivedValueType: string; +// propertiesName: string;
\ No newline at end of file diff --git a/catalog-ui/src/app/models/properties-inputs/input-fe-model.ts b/catalog-ui/src/app/models/properties-inputs/input-fe-model.ts new file mode 100644 index 0000000000..1261df3d6d --- /dev/null +++ b/catalog-ui/src/app/models/properties-inputs/input-fe-model.ts @@ -0,0 +1,32 @@ +import { SchemaPropertyGroupModel, SchemaProperty } from "../aschema-property"; +import { PropertyBEModel } from "../../models"; +import {PROPERTY_DATA} from "../../utils/constants"; + +export class InputFEModel extends PropertyBEModel { + isSimpleType: boolean; + isDataType: boolean; + instanceName: string; + propertyName: string; + + + constructor(input?: PropertyBEModel) { + super(input); + if (input) { + this.isSimpleType = PROPERTY_DATA.SIMPLE_TYPES.indexOf(this.type) > -1; + this.isDataType = PROPERTY_DATA.TYPES.indexOf(this.type) == -1; + let propNameIndex:number = this.name.indexOf('_'); + this.instanceName = this.name.substring(0, propNameIndex); + if (input.inputPath) { + this.propertyName = input.inputPath.substring(0, input.inputPath.indexOf('#')) + } else { + this.propertyName = this.name.substring(propNameIndex + 1); + } + } + } + + + + public toJSON = (): any => { + }; + +} diff --git a/catalog-ui/src/app/models/properties-inputs/property-be-model.ts b/catalog-ui/src/app/models/properties-inputs/property-be-model.ts new file mode 100644 index 0000000000..a5279d0668 --- /dev/null +++ b/catalog-ui/src/app/models/properties-inputs/property-be-model.ts @@ -0,0 +1,84 @@ +import { SchemaPropertyGroupModel, SchemaProperty } from "../aschema-property"; + +export class PropertyBEModel { + + defaultValue: string; + description: string; + fromDerived: boolean; + name: string; + parentUniqueId: string; + password: boolean; + required: boolean; + schema: SchemaPropertyGroupModel; + type: string; + uniqueId: string; + value: string; + definition: boolean; + inputPath: string; + propertiesName: string; + input: PropertyBEModel; + + constructor(property?: PropertyBEModel, childProperty?:PropertyBEModel) { + if (property) { + this.defaultValue = property.defaultValue; + this.description = property.description; + this.fromDerived = property.fromDerived; + this.name = property.name; + this.parentUniqueId = property.parentUniqueId; + this.password = property.password; + this.required = property.required; + this.schema = property.schema; + this.type = property.type; + this.uniqueId = property.uniqueId; + this.value = property.value ? property.value : property.defaultValue; + this.definition = property.definition; + if (property.inputPath) { + this.inputPath = property.inputPath; + } + } + if (childProperty) { + this.input = childProperty; + this.propertiesName = childProperty.propertiesName; + } + + if (!this.schema || !this.schema.property) { + this.schema = new SchemaPropertyGroupModel(new SchemaProperty()); + } else { //forcing creating new object, so editing different one than the object in the table + this.schema = new SchemaPropertyGroupModel(new SchemaProperty(this.schema.property)); + } + } + + + + public toJSON = (): any => { + let temp = angular.copy(this); + temp.value = temp.value === "{}" || temp.value === "[]" ? undefined : temp.value; + temp.defaultValue = temp.defaultValue === "{}" || temp.defaultValue === "[]" ? undefined : temp.defaultValue; + return temp; + }; + +} + + +// EXTRAS FROM CONSTRUCTOR: +// this.source = property.source; +// this.valueUniqueUid = property.valueUniqueUid; +// this.path = property.path; +// this.rules = property.rules; +// this.resourceInstanceUniqueId = property.resourceInstanceUniqueId; +// this.readonly = property.readonly; +// this.simpleType = property.simpleType; +// this.componentInstanceId = property.componentInstanceId; +// this.parentValue = property.parentValue; +//NEW PROPERTIES MAY NEED: +// export class PropertyFEModel extends PropertyBEModel { +// componentInstanceId: string; +// isAlreadySelected: boolean; +// filterTerm: string; +// } +//FOR INPUTS, BE ALSO INCLUDES: +//export class InputFEModel extends PropertyBEModel { +// hidden: boolean; +// label: string; +// immutable: boolean; +// } diff --git a/catalog-ui/src/app/models/properties-inputs/property-fe-map.ts b/catalog-ui/src/app/models/properties-inputs/property-fe-map.ts new file mode 100644 index 0000000000..3b267460b1 --- /dev/null +++ b/catalog-ui/src/app/models/properties-inputs/property-fe-map.ts @@ -0,0 +1,21 @@ +'use strict'; +import { PropertyBEModel, PropertyFEModel } from "../../models"; + +export class InstanceBePropertiesMap { + [instanceId: string]: Array<PropertyBEModel>; +} + +export class InstanceFePropertiesMap { + [instanceId: string]: Array<PropertyFEModel>; +} + +export class InstancePropertiesAPIMap { + componentInstanceProperties: InstanceBePropertiesMap; + componentInstanceInputsMap: InstanceBePropertiesMap; + + constructor(inputsMapData: InstanceBePropertiesMap, propertiesMapData: InstanceBePropertiesMap) { + this.componentInstanceInputsMap = inputsMapData ? inputsMapData: new InstanceBePropertiesMap(); + this.componentInstanceProperties = propertiesMapData ? propertiesMapData: new InstanceBePropertiesMap(); + } + +} diff --git a/catalog-ui/src/app/models/properties-inputs/property-fe-model.ts b/catalog-ui/src/app/models/properties-inputs/property-fe-model.ts new file mode 100644 index 0000000000..29f2c79225 --- /dev/null +++ b/catalog-ui/src/app/models/properties-inputs/property-fe-model.ts @@ -0,0 +1,132 @@ +import {SchemaPropertyGroupModel, SchemaProperty} from '../aschema-property'; +import { PROPERTY_DATA } from 'app/utils'; +import { PropertyBEModel, DerivedFEPropertyMap, DerivedFEProperty } from '../../models'; + + +export class PropertyFEModel extends PropertyBEModel { + + //START - TO REMOVE: + treeNodeId: string; + parent: PropertyFEModel; + + childrenProperties: Array<PropertyFEModel>; + isAllChildrenLevelsCalculated: boolean; + uniqueId: string; + valueObjectRef: any; + //END - TO REMOVE: + + expandedChildPropertyId: string; + flattenedChildren: Array<DerivedFEProperty>; //[parentPath] : Array<DerivedFEProp> + isDataType: boolean; //aka- isComplexType. (Type is NOT: simple, list, or map) + isDeclared: boolean; + isDisabled: boolean; + isSelected: boolean; + isSimpleType: boolean; + + private _derivedFromSimpleTypeName:string; + get derivedFromSimpleTypeName():string { + return this._derivedFromSimpleTypeName; + } + set derivedFromSimpleTypeName(derivedFromSimpleTypeName:string) { + this._derivedFromSimpleTypeName = derivedFromSimpleTypeName; + } + + constructor(property?: PropertyBEModel); + constructor(name: string, type: string, treeNodeId: string, parent: PropertyFEModel, valueObjectRef: any, schema?: SchemaPropertyGroupModel); + constructor(nameOrPropertyObj?: string | PropertyBEModel, type?: string, treeNodeId?: string, parent?: PropertyFEModel, valueObjectRef?: any, schema?: SchemaPropertyGroupModel) { + + super(typeof nameOrPropertyObj === 'string' ? null : nameOrPropertyObj); + + if (typeof nameOrPropertyObj === 'string') { + this.name = nameOrPropertyObj; + this.type = type; + this.treeNodeId = treeNodeId; + this.parent = parent; + this.valueObjectRef = valueObjectRef; + this.value = this.value || this.defaultValue; + if(schema){ + this.schema = new SchemaPropertyGroupModel(new SchemaProperty(schema.property)); + } + } + + this.isSimpleType = PROPERTY_DATA.SIMPLE_TYPES.indexOf(this.type) > -1; + this.isDataType = PROPERTY_DATA.TYPES.indexOf(this.type) == -1; + this.setNonDeclared(); + } + + public convertChildToInput = (childName: string): void => { + //childName: "mac_count_required" + let childJson = this.flattenedChildren[childName].map((child) => { + + }); + }; + + public getChildJsonRecursive = (child: string, value?: string): void => { + //TODO: use array.map for the below + /* value += "{" + this.flattenedChildren[child].name + ":"; + if (this.flattenedChildren[child].valueType == 'simple') { + value += this.flattenedChildren[child].value + '}'; + return value; + } else { + this.flattenedChildren[child].forEach(grandChild => { + if (this.flattenedChildren[grandChild].valueType == 'simple') { + return "{" + this.flattenedChildren[grandChild].name + ':' + this.flattenedChildren[child].value.toString() + "}"; + } else { + return this.getChildJsonRecursive(grandChild + '#' + this.flattenedChildren[child].name); + } + }); + } + + return "{" + this.flattenedChildren[child].name + this.flattenedChildren[child].value.toString() + "}"; +*/ + + }; + + public setNonDeclared = (childPath?: string): void => { + if (!childPath) { //declaring a child prop + this.isDeclared = false; + } else { + let childProp: DerivedFEProperty = this.flattenedChildren.find(child => child.propertiesName == childPath); + childProp.isDeclared = false; + } + } + + public setAsDeclared = (childNameToDeclare?:string): void => { + if (!childNameToDeclare) { //declaring a child prop + this.isSelected = false; + this.isDeclared = true; + } else { + let childProp: DerivedFEProperty = this.flattenedChildren.find(child => child.propertiesName == childNameToDeclare); + childProp.isSelected = false; + childProp.isDeclared = true; + } + } + + + + //For expand-collapse functionality + public updateExpandedChildPropertyId = (childPropertyId: string): void => { + if (childPropertyId.lastIndexOf('#') > -1) { + this.expandedChildPropertyId = (this.expandedChildPropertyId == childPropertyId) ? (childPropertyId.substring(0, childPropertyId.lastIndexOf('#'))) : childPropertyId; + } else { + this.expandedChildPropertyId = this.name; + } + //console.log("expandedChild is now " + this.expandedChildPropertyId); + } + + public convertToServerObject: Function = (): any => { //TODO: Idan, Rachel, Nechama: Decide what we need to do here + // let serverObject = {}; + // let mapData = { + // 'type': this.type, + // 'required': this.required || false, + // 'defaultValue': this.defaultValue != '' && this.defaultValue != '[]' && this.defaultValue != '{}' ? this.defaultValue : null, + // 'description': this.description, + // 'isPassword': this.password || false, + // 'schema': this.schema, + // 'name': this.name + // }; + // serverObject[this.name] = mapData; + + //return JSON.stringify(serverObject); + }; +} diff --git a/catalog-ui/src/app/models/properties.ts b/catalog-ui/src/app/models/properties.ts new file mode 100644 index 0000000000..357dac2e7d --- /dev/null +++ b/catalog-ui/src/app/models/properties.ts @@ -0,0 +1,141 @@ +'use strict'; +import {SchemaPropertyGroupModel, SchemaProperty} from "./aschema-property"; +import {InputPropertyBase} from "./input-property-base"; + +export class PropertiesGroup { + constructor(propertiesObj?:PropertiesGroup) { + _.forEach(propertiesObj, (properties:Array<PropertyModel>, instance) => { + this[instance] = []; + _.forEach(properties, (property:PropertyModel):void => { + property.resourceInstanceUniqueId = instance; + property.readonly = true; + this[instance].push(new PropertyModel(property)); + }); + }); + } +} + +export interface IPropertyModel extends InputPropertyBase { + + //server data + constraints:Array<Object>; + source:string; + + //instance properties + valueUniqueUid:string; + path:Array<string>; + rules:Array<Object>; + propertiesName:string; + input:any; + + //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:SchemaPropertyGroupModel; + componentInstanceId:string; + parentValue:string; + + //instance properties + value:string; + valueUniqueUid:string; + path:Array<string>; + rules:Array<Object>; + propertiesName:string; + input:any; + + //custom properties + resourceInstanceUniqueId:string; + readonly:boolean; + simpleType:string; + filterTerm:string; + isAlreadySelected:boolean; + addOn:string; + + + constructor(property?: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; + this.componentInstanceId = property.componentInstanceId; + this.parentValue = property.parentValue; + } + + if (!this.schema || !this.schema.property) { + this.schema = new SchemaPropertyGroupModel(new SchemaProperty()); + } else { + //forcing creating new object, so editing different one than the object in the table + this.schema = new SchemaPropertyGroupModel(new SchemaProperty(this.schema.property)); + } + if (property && property.uniqueId) { + this.filterTerm = this.name + " " + (this.description || "") + " " + this.type.replace("org.openecomp.datatypes.heat.", ""); + if (this.schema.property && this.schema.property.type) { + this.filterTerm += " " + this.schema.property.type.replace("org.openecomp.datatypes.heat.", ""); + } + } + } + + 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 toJSON = ():any => { + // if(!this.resourceInstanceUniqueId){ + // this.value = undefined; + // } + let temp = angular.copy(this); + temp.readonly = undefined; + temp.resourceInstanceUniqueId = undefined; + temp.simpleType = undefined; + temp.value = temp.value === "{}" || temp.value === "[]" ? undefined : temp.value; + temp.defaultValue = temp.defaultValue === "{}" || temp.defaultValue === "[]" ? undefined : temp.defaultValue; + temp.rules = null; //don't send rules to server until feature is fully supported + temp.isAlreadySelected = undefined; + temp.addOn = undefined; + return temp; + }; +} diff --git a/catalog-ui/src/app/models/property-fe-model.ts b/catalog-ui/src/app/models/property-fe-model.ts new file mode 100644 index 0000000000..f83d6d6b51 --- /dev/null +++ b/catalog-ui/src/app/models/property-fe-model.ts @@ -0,0 +1,78 @@ +import {SchemaPropertyGroupModel, SchemaProperty} from './aschema-property'; +import { PROPERTY_DATA } from 'app/utils'; +import { FilterPropertiesAssignmentData, PropertyBEModel } from 'app/models'; + +export class PropertyFEModel extends PropertyBEModel { + public static filterData:FilterPropertiesAssignmentData; + childrenProperties: Array<PropertyFEModel>; + expandedChildPropertyId: string; + isAllChildrenLevelsCalculated: boolean; + isDataType: boolean; + isDisabled: boolean; + isSelected: boolean; + isSimpleType: boolean; + parent: PropertyFEModel; + treeNodeId: string; + valueObjectRef: any; + private _derivedFromSimpleTypeName:string; + get derivedFromSimpleTypeName():string { + return this._derivedFromSimpleTypeName; + } + set derivedFromSimpleTypeName(derivedFromSimpleTypeName:string) { + this._derivedFromSimpleTypeName = derivedFromSimpleTypeName; + } + + constructor(property?: PropertyBEModel); + constructor(name: string, type: string, treeNodeId: string, parent: PropertyFEModel, valueObjectRef: any, schema?: SchemaPropertyGroupModel); + constructor(nameOrPropertyObj?: string | PropertyBEModel, type?: string, treeNodeId?: string, parent?: PropertyFEModel, valueObjectRef?: any, schema?: SchemaPropertyGroupModel) { + + super(typeof nameOrPropertyObj === 'string' ? null : nameOrPropertyObj); + + if (typeof nameOrPropertyObj === 'string') { + this.name = nameOrPropertyObj; + this.type = type; + this.treeNodeId = treeNodeId; + this.parent = parent; + this.valueObjectRef = valueObjectRef; + this.value = this.value || this.defaultValue; + if(schema){ + this.schema = new SchemaPropertyGroupModel(new SchemaProperty(schema.property)); + } + } + this.isSimpleType = PROPERTY_DATA.SIMPLE_TYPES.indexOf(this.type) > -1; + this.isDataType = PROPERTY_DATA.TYPES.indexOf(this.type) == -1; + this.setNonDeclared(); + } + + + public setNonDeclared = (): void => { + this.isSelected = false; + this.isDisabled = false; + } + + public setAsDeclared = (): void => { + this.isSelected = true; + this.isDisabled = true; + } + + //For expand-collapse functionality + public updateExpandedChildPropertyId = (childPropertyId: string): void => { + this.expandedChildPropertyId = (this.expandedChildPropertyId == childPropertyId) ? '' : childPropertyId; + } + + public convertToServerObject: Function = (): any => { //TODO: Idan, Rachel, Nechama: Decide what we need to do here + // let serverObject = {}; + // let mapData = { + // 'type': this.type, + // 'required': this.required || false, + // 'defaultValue': this.defaultValue != '' && this.defaultValue != '[]' && this.defaultValue != '{}' ? this.defaultValue : null, + // 'description': this.description, + // 'isPassword': this.password || false, + // 'schema': this.schema, + // 'name': this.name + // }; + // serverObject[this.name] = mapData; + + //return JSON.stringify(serverObject); + }; +} diff --git a/catalog-ui/src/app/models/requirement.ts b/catalog-ui/src/app/models/requirement.ts new file mode 100644 index 0000000000..e62c809ec9 --- /dev/null +++ b/catalog-ui/src/app/models/requirement.ts @@ -0,0 +1,71 @@ +/** + * Created by obarda on 4/20/2016. + */ +'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 { + [key: string]: Array<Requirement>; + constructor(requirementGroupObj?:RequirementsGroup) { + _.forEach(requirementGroupObj, (requirementsArrayObj:Array<Requirement>, instance) => { + this[instance] = []; + _.forEach(requirementsArrayObj, (requirement:Requirement):void => { + this[instance].push(new 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/src/app/models/schema-attribute.ts b/catalog-ui/src/app/models/schema-attribute.ts new file mode 100644 index 0000000000..3c48aa3fa2 --- /dev/null +++ b/catalog-ui/src/app/models/schema-attribute.ts @@ -0,0 +1,16 @@ +'use strict'; +import {SchemaProperty} from "./aschema-property"; + +export class SchemaAttributeGroupModel { + property:SchemaAttribute; + + constructor(schemaAttribute?:SchemaAttribute) { + this.property = schemaAttribute; + } +} + +export class SchemaAttribute extends SchemaProperty { + +} + + diff --git a/catalog-ui/src/app/models/tab.ts b/catalog-ui/src/app/models/tab.ts new file mode 100644 index 0000000000..a7ce509ce0 --- /dev/null +++ b/catalog-ui/src/app/models/tab.ts @@ -0,0 +1,27 @@ +/** + * Created by obarda on 7/31/2016. + */ +'use strict'; + +export class Tab { + + public templateUrl:string; + public controller:string; + public data:any; + public icon:string; + public name:string; + public isViewMode:boolean; + + constructor(templateUrl:string, controller:string, name:string, isViewMode:boolean, data?:any, icon?:string) { + + this.templateUrl = templateUrl; + this.controller = controller; + this.icon = icon; + this.data = data; + this.name = name; + this.isViewMode = isViewMode; + } +} + + + diff --git a/catalog-ui/src/app/models/tooltip-data.ts b/catalog-ui/src/app/models/tooltip-data.ts new file mode 100644 index 0000000000..902a20e926 --- /dev/null +++ b/catalog-ui/src/app/models/tooltip-data.ts @@ -0,0 +1,6 @@ +'use strict'; + +export class TooltipData { +} + + diff --git a/catalog-ui/src/app/models/user.ts b/catalog-ui/src/app/models/user.ts new file mode 100644 index 0000000000..8ef800acb7 --- /dev/null +++ b/catalog-ui/src/app/models/user.ts @@ -0,0 +1,96 @@ +'use strict'; +import {IUserResource} from "../services/user-resource-service"; + +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:IUserResource; + getRole():UserRole; + getRoleToView():string; + getName():string; + getFirstName():string; + getLastName():string; +} + +export class User implements IUser { + + constructor(public resource: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/src/app/models/validate.ts b/catalog-ui/src/app/models/validate.ts new file mode 100644 index 0000000000..59638ab4dd --- /dev/null +++ b/catalog-ui/src/app/models/validate.ts @@ -0,0 +1,7 @@ +'use strict'; + +export interface IValidate { + isValid:boolean; +} + + diff --git a/catalog-ui/src/app/models/validation-config.ts b/catalog-ui/src/app/models/validation-config.ts new file mode 100644 index 0000000000..3524addf86 --- /dev/null +++ b/catalog-ui/src/app/models/validation-config.ts @@ -0,0 +1,20 @@ +class PropertyValue { + min: number; + max: number; +} + +class validationPatterns { + string: string; + comment:string; + integer: string; +} + +export class Validations { + propertyValue: PropertyValue; + validationPatterns: validationPatterns; +} + +export class ValidationConfiguration { + static validation: Validations; + +} diff --git a/catalog-ui/src/app/modules/directive-module.ts b/catalog-ui/src/app/modules/directive-module.ts new file mode 100644 index 0000000000..6ad89b8db7 --- /dev/null +++ b/catalog-ui/src/app/modules/directive-module.ts @@ -0,0 +1,152 @@ +import {ClickedOutsideDirective} from "../directives/clicked-outside/clicked-outside-directive"; +import {LoaderDirective} from "../directives/loader/loader-directive"; +import {UserHeaderDetailsDirective} from "../directives/user-header-details/user-header-details-directive"; +import {FileTypeDirective} from "../directives/file-type/file-type"; +import {DownloadArtifactDirective} from "../directives/download-artifact/download-artifact"; +import {EllipsisDirective} from "../directives/ellipsis/ellipsis-directive"; +import {InvalidCharactersDirective} from "../directives/invalid-characters/invalid-characters"; +import {ExpandCollapseDirective} from "../directives/utils/expand-collapse/expand-collapse"; +import {PerfectScrollerDirective} from "../directives/perfect-scrollbar/angular-perfect-scrollbar"; +import {SdcModalDirective} from "../directives/modal/sdc-modal"; +import {FileOpenerDirective} from "../directives/file-opener/file-opener"; +import {FileUploadDirective} from "../directives/file-upload/file-upload"; +import {StructureTreeDirective} from "../directives/structure-tree/structure-tree-directive"; +import {SmartTooltipDirective} from "../directives/utils/smart-tooltip/smart-tooltip"; +import {PrintGraphScreenDirective} from "../directives/print-graph-screen/print-graph-screen"; +import {TagDirective} from "../directives/tag/tag-directive"; +import {SdcTagsDirective} from "../directives/utils/sdc-tags/sdc-tags"; +import {SdcKeyboardEventsDirective} from "../directives/utils/sdc-keyboard-events/sdc-keyboard-events"; +import {ExpandCollapseMenuBoxDirective} from "../directives/utils/expand-collapse-menu-box/expand-collaps-menu-box"; +import {PunchOutDirective} from "../directives/punch-out/punch-out"; +import {CustomValidationDirective} from "../directives/custom-validation/custom-validation"; +import {EcompHeaderDirective} from "../directives/ecomp-header/ecomp-header"; +import {EcompFooterDirective} from "../directives/ecomp-footer/ecomp-footer"; +import {EditNamePopoverDirective} from "../directives/edit-name-popover/edit-name-popover-directive"; +import {DataTypeFieldsStructureDirective} from "../directives/property-types/data-type-fields-structure/data-type-fields-structure"; +import {TypeMapDirective} from "../directives/property-types/type-map/type-map-directive"; +import {TypeListDirective} from "../directives/property-types/type-list/type-list-directive"; +import {SelectDataTypeFieldsStructureDirective} from "../directives/select-property-types/select-data-type-fields-structure/select-data-type-fields-structure"; +import {SelectTypeMapDirective} from "../directives/select-property-types/select-type-map/select-type-map-directive"; +import {SelectTypeListDirective} from "../directives/select-property-types/select-type-list/select-type-list-directive"; +import {ValidationOnLoadDirective} from "../directives/utils/validation-on-load/validation-on-load"; +import {InfoTooltipDirective} from "../directives/info-tooltip/info-tooltip"; +import {SdcTabsDirective} from "../directives/sdc-tabs/sdc-tabs-directive"; +import {SdcSingleTabDirective, InnerSdcSingleTabDirective} from "../directives/sdc-tabs/sdc-single-tab/sdc-single-tab-directive"; +import {ExpandCollapseListHeaderDirective} from "../directives/utils/expand-collapse-list-header/expand-collapse-list-header"; +import {JsonExportExcelDirective} from "../directives/export-json-to-excel/export-json-to-excel"; +import {TopNavDirective} from "../directives/layout/top-nav/top-nav"; +import {TopProgressDirective} from "../directives/layout/top-progress/top-progress"; +import {CheckboxElementDirective} from "../directives/elements/checkbox/checkbox"; +import {RadiobuttonElementDirective} from "../directives/elements/radiobutton/radiobutton"; +import {OnLastRepeatDirective} from "../directives/events/on-last-repeat/on-last-repeat"; +import {InputRowDirective} from "../directives/inputs-and-properties/inputs/input-row-directive"; +import {PropertyRowDirective} from "../directives/inputs-and-properties/properties/property-row-directive"; +import {NodesFactory} from "../models/graph/nodes/nodes-factory"; +import {LinksFactory} from "../models/graph/graph-links/links-factory"; +import {ImageCreatorService} from "../directives/graphs-v2/image-creator/image-creator.service"; +import {Palette} from "../directives/graphs-v2/palette/palette.directive"; +import {CompositionGraph} from "../directives/graphs-v2/composition-graph/composition-graph.directive"; +import {RelationMenuDirective} from "../directives/graphs-v2/relation-menu/relation-menu"; +import {DeploymentGraph} from "../directives/graphs-v2/deployment-graph/deployment-graph.directive"; +import {CommonGraphUtils} from "../directives/graphs-v2/common/common-graph-utils"; +import {CompositionGraphNodesUtils} from "../directives/graphs-v2/composition-graph/utils/composition-graph-nodes-utils"; +import {CompositionGraphGeneralUtils} from "../directives/graphs-v2/composition-graph/utils/composition-graph-general-utils"; +import {CompositionGraphLinkUtils} from "../directives/graphs-v2/composition-graph/utils/composition-graph-links-utils"; +import {DeploymentGraphGeneralUtils} from "../directives/graphs-v2/deployment-graph/deployment-utils/deployment-graph-general-utils"; +import {CompositionGraphPaletteUtils} from "../directives/graphs-v2/composition-graph/utils/composition-graph-palette-utils"; +import {MatchCapabilitiesRequirementsUtils} from "../directives/graphs-v2/composition-graph/utils/match-capability-requierment-utils"; +import {AssetPopoverDirective} from "../directives/graphs-v2/asset-popover/asset-popover"; +import {downgradeComponent} from "@angular/upgrade/static"; +import {CapabilitiesListDirective} from "../directives/capabilities-and-requirements/capability/capabilities-list-directive"; +import {RequirementsListDirective} from "../directives/capabilities-and-requirements/requirement/requirements-list-directive"; + +let moduleName:string = 'Sdc.Directives'; +let directiveModule:ng.IModule = angular.module(moduleName, []); + +directiveModule.directive('clickedOutside', ClickedOutsideDirective.factory); +directiveModule.directive('loader', LoaderDirective.factory); +directiveModule.directive('userHeaderDetails', UserHeaderDetailsDirective.factory); +directiveModule.directive('ellipsis', EllipsisDirective.factory); +directiveModule.directive('downloadArtifact', DownloadArtifactDirective.factory); +directiveModule.directive('fileType', FileTypeDirective.factory); +directiveModule.directive('invalidCharacters', InvalidCharactersDirective.factory); +directiveModule.directive('perfectScrollbar', PerfectScrollerDirective.factory); +directiveModule.directive('expandCollapse', ExpandCollapseDirective.factory); +directiveModule.directive('sdcModal', SdcModalDirective.factory); +directiveModule.directive('fileOpener', FileOpenerDirective.factory); +directiveModule.directive('fileUpload', FileUploadDirective.factory); +directiveModule.directive('structureTree', StructureTreeDirective.factory); +directiveModule.directive('sdcSmartTooltip', SmartTooltipDirective.factory); +directiveModule.directive('printGraphScreen', PrintGraphScreenDirective.factory); +directiveModule.directive('sdcTag', TagDirective.factory); +directiveModule.directive('sdcTags', SdcTagsDirective.factory); +directiveModule.directive('sdcKeyboardEvents', SdcKeyboardEventsDirective.factory); +directiveModule.directive('expandCollapseMenuBox', ExpandCollapseMenuBoxDirective.factory); +directiveModule.directive('punchOut', PunchOutDirective.factory); +directiveModule.directive('customValidation', CustomValidationDirective.factory); +directiveModule.directive('ecompHeader', EcompHeaderDirective.factory); +directiveModule.directive('ecompFooter', EcompFooterDirective.factory); +directiveModule.directive('editNamePopover', EditNamePopoverDirective.factory); +directiveModule.directive('fieldsStructure', DataTypeFieldsStructureDirective.factory); +directiveModule.directive('typeMap', TypeMapDirective.factory); +directiveModule.directive('typeList', TypeListDirective.factory); +directiveModule.directive('selectFieldsStructure', SelectDataTypeFieldsStructureDirective.factory); +directiveModule.directive('selectTypeMap', SelectTypeMapDirective.factory); +directiveModule.directive('selectTypeList', SelectTypeListDirective.factory); +directiveModule.directive('infoTooltip', InfoTooltipDirective.factory); +directiveModule.directive('validationOnLoad', ValidationOnLoadDirective.factory); +directiveModule.directive('sdcTabs', SdcTabsDirective.factory); +directiveModule.directive('sdcSingleTab', SdcSingleTabDirective.factory); +directiveModule.directive('innerSdcSingleTab', InnerSdcSingleTabDirective.factory); +directiveModule.directive('jsonExportExcel', JsonExportExcelDirective.factory); +directiveModule.directive('expandCollapseListHeader', ExpandCollapseListHeaderDirective.factory); +// +// // Layouts +directiveModule.directive('topNav', TopNavDirective.factory); +directiveModule.directive('topProgress', TopProgressDirective.factory); +// +// // Elements +directiveModule.directive('sdcCheckbox', CheckboxElementDirective.factory); +directiveModule.directive('sdcRadioButton', RadiobuttonElementDirective.factory); +// +// // Events +directiveModule.directive('onLastRepeat', OnLastRepeatDirective.factory); +// +// //Inputs & Properties +directiveModule.directive('inputRow', InputRowDirective.factory); +directiveModule.directive('propertyRow', PropertyRowDirective.factory); +// +// +// // ------------------------------------------- Composition & Deployment Graphs------------------------------------------// +// +// //Util service for Graph +directiveModule.service('NodesFactory', NodesFactory); +directiveModule.service('LinksFactory', LinksFactory); +directiveModule.service('ImageCreatorService', ImageCreatorService); +// +// //composition +directiveModule.directive('palette', Palette.factory); +directiveModule.directive('compositionGraph', CompositionGraph.factory); +directiveModule.directive('relationMenu', RelationMenuDirective.factory); + //directiveModule.directive('assetPopover', AssetPopoverDirective.factory); +// +// //deployment +directiveModule.directive('deploymentGraph', DeploymentGraph.factory); +// +// //Graph Utils - Common +directiveModule.service('CommonGraphUtils', CommonGraphUtils); +// +// //Composition Graph Utils +directiveModule.service('CompositionGraphNodesUtils', CompositionGraphNodesUtils); +directiveModule.service('CompositionGraphGeneralUtils', CompositionGraphGeneralUtils); +directiveModule.service('CompositionGraphLinkUtils', CompositionGraphLinkUtils); +directiveModule.service('CompositionGraphPaletteUtils', CompositionGraphPaletteUtils); +directiveModule.service('MatchCapabilitiesRequirementsUtils', MatchCapabilitiesRequirementsUtils); +// +// //Deployment Graph Utils +directiveModule.service('DeploymentGraphGeneralUtils', DeploymentGraphGeneralUtils); + + +//Compoisiton right tab directives +directiveModule.directive('capabilitiesList', CapabilitiesListDirective.factory); +directiveModule.directive('requirementsList', RequirementsListDirective.factory); diff --git a/catalog-ui/src/app/modules/filters.ts b/catalog-ui/src/app/modules/filters.ts new file mode 100644 index 0000000000..a62283f927 --- /dev/null +++ b/catalog-ui/src/app/modules/filters.ts @@ -0,0 +1,26 @@ +import {TestsIdFilter} from "../filters/tests-id-filter"; +import {TrimFilter} from "../filters/trim-filter"; +import {ResourceTypeFilter} from "../filters/resource-type-filter"; +import {StringToDateFilter} from "../filters/string-to-date-filter"; +import {CategoryTypeFilter} from "../filters/category-type-filter"; +import {CatalogStatusFilter} from "../filters/catalog-status-filter"; +import {TruncateFilter} from "../filters/truncate-filter"; +import {EntityFilter} from "../filters/entity-filter"; +import {GraphResourceNameFilter} from "../filters/graph-resource-name-filter"; +import {ResourceNameFilter} from "../filters/resource-name-filter"; +import {ClearWhiteSpacesFilter} from "../filters/clear-whitespaces-filter"; + +let moduleName:string = 'Sdc.Filters'; +let filterModule:ng.IModule = angular.module(moduleName, []); + +filterModule.filter("resourceName", ResourceNameFilter); +filterModule.filter("graphResourceName", GraphResourceNameFilter); +filterModule.filter("entityFilter", EntityFilter); +filterModule.filter("truncate", TruncateFilter); +filterModule.filter("catalogStatusFilter", CatalogStatusFilter); +filterModule.filter("categoryTypeFilter", CategoryTypeFilter); +filterModule.filter("stringToDateFilter", StringToDateFilter); +filterModule.filter("resourceTypeName", ResourceTypeFilter); +filterModule.filter("trim", TrimFilter); +filterModule.filter("clearWhiteSpaces", ClearWhiteSpacesFilter); +filterModule.filter('testsId', TestsIdFilter); diff --git a/catalog-ui/src/app/modules/service-module.ts b/catalog-ui/src/app/modules/service-module.ts new file mode 100644 index 0000000000..55ba87b9fb --- /dev/null +++ b/catalog-ui/src/app/modules/service-module.ts @@ -0,0 +1,66 @@ +import {ConfigurationUiService} from "../services/configuration-ui-service"; +import {CookieService} from "../services/cookie-service"; +import {EntityService} from "../services/entity-service"; +import {AvailableIconsService} from "../services/available-icons-service"; +import {UrlToBase64Service} from "../services/url-tobase64-service"; +import {CacheService} from "../services/cache-service"; +import {HeaderInterceptor} from "../services/header-interceptor"; +import {HttpErrorInterceptor} from "../services/http-error-interceptor"; +import {SharingService} from "../services/sharing-service"; +import {SdcVersionService} from "../services/sdc-version-service"; +import {ActivityLogService} from "../services/activity-log-service"; +import {OnboardingService} from "../services/onboarding-service"; +import {EcompHeaderService} from "../services/ecomp-service"; +import {DataTypesService} from "../services/data-types-service"; +import {ComponentService} from "../services/components/component-service"; +import {ServiceService} from "../services/components/service-service"; +import {ResourceService} from "../services/components/resource-service"; +import {ProductService} from "../services/components/product-service"; +import {LeftPaletteLoaderService} from "../services/components/utils/composition-left-palette-service"; +import {EventListenerService} from "../services/event-listener-service"; +import {ProgressService} from "../services/progress-service"; +import {ArtifactsUtils} from "../utils/artifacts-utils"; +import {FileUtils} from "../utils/file-utils"; +import {ValidationUtils} from "../utils/validation-utils"; +import {AngularJSBridge} from "../services/angular-js-bridge-service"; +import {LoaderService} from "../services/loader-service"; +import {UserResourceService} from "../services/user-resource-service"; +import {CategoryResourceService} from "../services/category-resource-service"; + +let moduleName:string = 'Sdc.Services'; +let serviceModule:ng.IModule = angular.module(moduleName, []); + +serviceModule.service('Sdc.Services.ConfigurationUiService', ConfigurationUiService); +serviceModule.service('Sdc.Services.CookieService', CookieService); +serviceModule.service('Sdc.Services.EntityService', EntityService); +serviceModule.service('Sdc.Services.AvailableIconsService', AvailableIconsService); +serviceModule.service('Sdc.Services.UrlToBase64Service', UrlToBase64Service); +serviceModule.service('Sdc.Services.CacheService', CacheService); +serviceModule.service('Sdc.Services.HeaderInterceptor', HeaderInterceptor); +serviceModule.service('Sdc.Services.HttpErrorInterceptor', HttpErrorInterceptor); +serviceModule.service('Sdc.Services.SharingService', SharingService); +serviceModule.service('Sdc.Services.SdcVersionService', SdcVersionService); +serviceModule.service('Sdc.Services.ActivityLogService', ActivityLogService); +serviceModule.service('Sdc.Services.OnboardingService', OnboardingService); +serviceModule.service('Sdc.Services.EcompHeaderService', EcompHeaderService); +serviceModule.service('Sdc.Services.DataTypesService', DataTypesService); + +//Components Services +serviceModule.service('Sdc.Services.Components.ComponentService', ComponentService); +serviceModule.service('Sdc.Services.Components.ServiceService',ServiceService); +serviceModule.service('Sdc.Services.Components.ResourceService', ResourceService); +serviceModule.service('Sdc.Services.Components.ProductService', ProductService); +serviceModule.service('LeftPaletteLoaderService', LeftPaletteLoaderService); +serviceModule.service('EventListenerService', EventListenerService); +serviceModule.service('Sdc.Services.ProgressService', ProgressService); + +//Utils +serviceModule.service('ArtifactsUtils', ArtifactsUtils); +serviceModule.service('FileUtils', FileUtils); +serviceModule.service('ValidationUtils', ValidationUtils); + +serviceModule.service('AngularJSBridge',AngularJSBridge); +serviceModule.service('LoaderService', LoaderService); + +serviceModule.factory('Sdc.Services.UserResourceService', UserResourceService.getResource); +serviceModule.factory('Sdc.Services.CategoryResourceService', CategoryResourceService.getResource); diff --git a/catalog-ui/src/app/modules/utils.ts b/catalog-ui/src/app/modules/utils.ts new file mode 100644 index 0000000000..529857ac39 --- /dev/null +++ b/catalog-ui/src/app/modules/utils.ts @@ -0,0 +1,16 @@ +import {ComponentFactory} from "../utils/component-factory"; +import {ComponentInstanceFactory} from "../utils/component-instance-factory"; +import {ChangeLifecycleStateHandler} from "../utils/change-lifecycle-state-handler"; +import {ModalsHandler} from "../utils/modals-handler"; +import {MenuHandler} from "../utils/menu-handler"; + +let moduleName:string = 'Sdc.Utils'; +let serviceModule:ng.IModule = angular.module(moduleName, []); + +//Utils +serviceModule.service('ComponentFactory', ComponentFactory); +serviceModule.service('ComponentInstanceFactory', ComponentInstanceFactory); +serviceModule.service('ChangeLifecycleStateHandler', ChangeLifecycleStateHandler); +serviceModule.service('ModalsHandler', ModalsHandler); +serviceModule.service('MenuHandler', MenuHandler); + diff --git a/catalog-ui/src/app/modules/view-model-module.ts b/catalog-ui/src/app/modules/view-model-module.ts new file mode 100644 index 0000000000..4060fe794e --- /dev/null +++ b/catalog-ui/src/app/modules/view-model-module.ts @@ -0,0 +1,125 @@ +import {AddCategoryModalViewModel} from "../view-models/admin-dashboard/add-category-modal/add-category-modal-view-model"; +import {DashboardViewModel} from "../view-models/dashboard/dashboard-view-model"; +import {WorkspaceViewModel} from "../view-models/workspace/workspace-view-model"; +import {CompositionViewModel} from "../view-models/workspace/tabs/composition/composition-view-model"; +import {DetailsViewModel} from "../view-models/workspace/tabs/composition/tabs/details/details-view-model"; +import {ResourceArtifactsViewModel} from "../view-models/workspace/tabs/composition/tabs/artifacts/artifacts-view-model"; +import {PropertyFormBaseView} from "../view-models/forms/property-forms/base-property-form/property-form-base-model"; +import {PropertyFormViewModel} from "../view-models/forms/property-forms/component-property-form/property-form-view-model"; +import {ModulePropertyView} from "../view-models/forms/property-forms/module-property-modal/module-property-model"; +import {ArtifactResourceFormViewModel} from "../view-models/forms/artifact-form/artifact-form-view-model"; +import {SelectDataTypeViewModel} from "../view-models/forms/property-forms/select-datatype-modal/select-datatype-modal-view-model"; +import {AttributeFormViewModel} from "../view-models/forms/attribute-form/attribute-from-view-model"; +import {ResourcePropertiesViewModel} from "../view-models/workspace/tabs/composition/tabs/properties-and-attributes/properties-view-model"; +import {CatalogViewModel} from "../view-models/catalog/catalog-view-model"; +import {OnboardVendorViewModel} from "../view-models/onboard-vendor/onboard-vendor-view-model"; +import {DistributionViewModel} from "../view-models/workspace/tabs/distribution/distribution-view-model"; +import {SupportViewModel} from "../view-models/support/support-view-model"; +import {ConfirmationModalViewModel} from "../view-models/modals/confirmation-modal/confirmation-modal-view-model"; +import {EmailModalViewModel} from "../view-models/modals/email-modal/email-modal-view-model"; +import {MessageModalViewModel} from "../view-models/modals/message-modal/message-base-modal-model"; +import {ServerMessageModalViewModel} from "../view-models/modals/message-modal/message-server-modal/server-message-modal-view-model"; +import {ClientMessageModalViewModel} from "../view-models/modals/message-modal/message-client-modal/client-message-modal-view-model"; +import {ErrorViewModel} from "../view-models/modals/error-modal/error-view-model"; +import {RelationsViewModel} from "../view-models/workspace/tabs/composition/tabs/relations/relations-view-model"; +import {ResourceInstanceNameViewModel} from "../view-models/forms/resource-instance-name-form/resource-instance-name-model"; +import {WelcomeViewModel} from "../view-models/welcome/welcome-view"; +import {PreLoadingViewModel} from "../view-models/preloading/preloading-view"; +import {TutorialEndViewModel} from "../view-models/tutorial-end/tutorial-end"; +import {AdminDashboardViewModel} from "../view-models/admin-dashboard/admin-dashboard-view-model"; +import {EnvParametersFormViewModel} from "../view-models/forms/env-parameters-form/env-parameters-form"; +import {StructureViewModel} from "../view-models/workspace/tabs/composition/tabs/structure/structure-view"; +import {UserManagementViewModel} from "../view-models/admin-dashboard/user-management/user-management-view-model"; +import {CategoryManagementViewModel} from "../view-models/admin-dashboard/category-management/category-management-view-model"; + +import {OnboardingModalViewModel} from "../view-models/modals/onboarding-modal/onboarding-modal-view-model"; +import {DistributionStatusModalViewModel} from "../view-models/workspace/tabs/distribution/disribution-status-modal/disribution-status-modal-view-model"; +import {DcaeAppViewModel} from "../view-models/dcae-app/dcae-app-view-model"; +import {GeneralViewModel} from "../view-models/workspace/tabs/general/general-view-model"; +import {IconsViewModel} from "../view-models/workspace/tabs/icons/icons-view-model"; +import {DeploymentArtifactsViewModel} from "../view-models/workspace/tabs/deployment-artifacts/deployment-artifacts-view-model"; +import {InformationArtifactsViewModel} from "../view-models/workspace/tabs/information-artifacts/information-artifacts-view-model"; +import {ToscaArtifactsViewModel} from "../view-models/workspace/tabs/tosca-artifacts/tosca-artifacts-view-model"; +import {PropertiesViewModel} from "../view-models/workspace/tabs/properties/properties-view-model"; +import {AttributesViewModel} from "../view-models/workspace/tabs/attributes/attributes-view-model"; +import {ProductHierarchyViewModel} from "../view-models/workspace/tabs/product-hierarchy/product-hierarchy-view-model"; +import {ActivityLogViewModel} from "../view-models/workspace/tabs/activity-log/activity-log"; +import {ManagementWorkflowViewModel} from "../view-models/workspace/tabs/management-workflow/management-workflow-view-model"; +import {NetworkCallFlowViewModel} from "../view-models/workspace/tabs/network-call-flow/network-call-flow-view-model"; +import {DeploymentViewModel} from "../view-models/workspace/tabs/deployment/deployment-view-model"; +import {ResourceInputsViewModel} from "../view-models/workspace/tabs/inputs/resource-input/resource-inputs-view-model"; +import {ServiceInputsViewModel} from "../view-models/workspace/tabs/inputs/service-input/service-inputs-view-model"; +import {ReqAndCapabilitiesViewModel} from "../view-models/workspace/tabs/req-and-capabilities/req-and-capabilities-view-model"; +import {InputFormViewModel} from "../view-models/forms/input-form/input-form-view-modal"; +import {HierarchyViewModel} from "../view-models/tabs/hierarchy/hierarchy-view-model"; +import {downgradeComponent} from "@angular/upgrade/static"; +import {ConformanceLevelModalViewModel} from "../view-models/workspace/conformance-level-modal/conformance-level-modal-view-model"; +// import {NG2ExampleComponent} from "../ng2/view-ng2/ng2.example.component/ng2.example.component"; +// import {upgradeAdapter} from "../ng2/app.module"; +// import { UpgradeAdapter } from '@angular/upgrade'; +let moduleName:string = 'Sdc.ViewModels'; +let viewModelModule:ng.IModule = angular.module(moduleName, []); + +viewModelModule + .controller(moduleName + '.DashboardViewModel', DashboardViewModel) + + .controller(moduleName + '.DetailsViewModel', DetailsViewModel) + .controller(moduleName + '.ResourceArtifactsViewModel', ResourceArtifactsViewModel) + .controller(moduleName + '.PropertyFormBaseView', PropertyFormBaseView) + .controller(moduleName + '.PropertyFormViewModel', PropertyFormViewModel) + .controller(moduleName + '.ModulePropertyView', ModulePropertyView) + .controller(moduleName + '.SelectDataTypeViewModel', SelectDataTypeViewModel) + .controller(moduleName + '.ArtifactResourceFormViewModel', ArtifactResourceFormViewModel) + .controller(moduleName + '.AttributeFormViewModel', AttributeFormViewModel) + .controller(moduleName + '.ResourcePropertiesViewModel', ResourcePropertiesViewModel) + .controller(moduleName + '.CatalogViewModel', CatalogViewModel) + .controller(moduleName + '.OnboardVendorViewModel', OnboardVendorViewModel) + .controller(moduleName + '.DistributionViewModel', DistributionViewModel) + .controller(moduleName + '.SupportViewModel', SupportViewModel) + .controller(moduleName + '.ConfirmationModalViewModel', ConfirmationModalViewModel) + .controller(moduleName + '.EmailModalViewModel', EmailModalViewModel) + .controller(moduleName + '.MessageModalViewModel', MessageModalViewModel) + .controller(moduleName + '.ServerMessageModalViewModel', ServerMessageModalViewModel) + .controller(moduleName + '.ClientMessageModalViewModel', ClientMessageModalViewModel) + .controller(moduleName + '.ErrorViewModel', ErrorViewModel) + .controller(moduleName + '.RelationsViewModel', RelationsViewModel) + .controller(moduleName + '.ResourceInstanceNameViewModel', ResourceInstanceNameViewModel) + .controller(moduleName + '.WelcomeViewModel', WelcomeViewModel) + .controller(moduleName + '.PreLoadingViewModel', PreLoadingViewModel) + .controller(moduleName + '.TutorialEndViewModel', TutorialEndViewModel) + .controller(moduleName + '.AdminDashboardViewModel', AdminDashboardViewModel) + .controller(moduleName + '.EnvParametersFormViewModel', EnvParametersFormViewModel) + .controller(moduleName + '.StructureViewModel', StructureViewModel) + .controller(moduleName + '.AddCategoryModalViewModel', AddCategoryModalViewModel) + .controller(moduleName + '.UserManagementViewModel', UserManagementViewModel) + .controller(moduleName + '.CategoryManagementViewModel', CategoryManagementViewModel) + .controller(moduleName + '.OnboardingModalViewModel', OnboardingModalViewModel) + .controller(moduleName + '.DistributionStatusModalViewModel', DistributionStatusModalViewModel) + .controller(moduleName + '.DcaeAppViewModel', DcaeAppViewModel) + // + // //NEW + .controller(moduleName + '.WorkspaceViewModel', WorkspaceViewModel) + .controller(moduleName + '.ConformanceLevelModalViewModel', ConformanceLevelModalViewModel) + .controller(moduleName + '.CompositionViewModel', CompositionViewModel) + .controller(moduleName + '.GeneralViewModel', GeneralViewModel) + .controller(moduleName + '.IconsViewModel', IconsViewModel) + .controller(moduleName + '.DeploymentArtifactsViewModel', DeploymentArtifactsViewModel) + .controller(moduleName + '.InformationArtifactsViewModel', InformationArtifactsViewModel) + .controller(moduleName + '.ToscaArtifactsViewModel', ToscaArtifactsViewModel) + .controller(moduleName + '.PropertiesViewModel', PropertiesViewModel) + .controller(moduleName + '.AttributesViewModel', AttributesViewModel) + .controller(moduleName + '.ProductHierarchyViewModel', ProductHierarchyViewModel) + .controller(moduleName + '.ActivityLogViewModel', ActivityLogViewModel) + .controller(moduleName + '.ManagementWorkflowViewModel', ManagementWorkflowViewModel) + .controller(moduleName + '.NetworkCallFlowViewModel', NetworkCallFlowViewModel) + .controller(moduleName + '.DeploymentViewModel', DeploymentViewModel) + .controller(moduleName + '.ResourceInputsViewModel', ResourceInputsViewModel) + .controller(moduleName + '.ServiceInputsViewModel', ServiceInputsViewModel) + .controller(moduleName + '.ReqAndCapabilitiesViewModel', ReqAndCapabilitiesViewModel) + .controller(moduleName + '.InputFormViewModel', InputFormViewModel) + // + // //TABS + .controller(moduleName + '.HierarchyViewModel', HierarchyViewModel) + + // NG2 + //.controller(moduleName + '.NG2Example', downgradeComponent({component: NG2Example2Component}) ); diff --git a/catalog-ui/src/app/ng2/app.component.css b/catalog-ui/src/app/ng2/app.component.css new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/catalog-ui/src/app/ng2/app.component.css diff --git a/catalog-ui/src/app/ng2/app.component.html b/catalog-ui/src/app/ng2/app.component.html new file mode 100644 index 0000000000..ed90b3deda --- /dev/null +++ b/catalog-ui/src/app/ng2/app.component.html @@ -0,0 +1,6 @@ +<!--<nav> + <app-navbar></app-navbar> +</nav> +<main> + <router-outlet></router-outlet> +</main>-->
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/app.component.ts b/catalog-ui/src/app/ng2/app.component.ts new file mode 100644 index 0000000000..0499045a79 --- /dev/null +++ b/catalog-ui/src/app/ng2/app.component.ts @@ -0,0 +1,15 @@ +import { Component, Inject } from '@angular/core'; +import { AuthenticationService } from './services/authentication.service'; + +@Component({ + selector: 'app-root', + templateUrl: './app.component.html', + styleUrls: ['./app.component.css'] +}) +export class AppComponent { + + constructor(auth:AuthenticationService){ + + } + +} diff --git a/catalog-ui/src/app/ng2/app.module.ts b/catalog-ui/src/app/ng2/app.module.ts new file mode 100644 index 0000000000..9e122cd288 --- /dev/null +++ b/catalog-ui/src/app/ng2/app.module.ts @@ -0,0 +1,74 @@ +import {BrowserModule} from '@angular/platform-browser'; +import {NgModule, APP_INITIALIZER} from '@angular/core'; +import {FormsModule} from '@angular/forms'; +import {forwardRef} from '@angular/core'; +import {AppComponent} from './app.component'; +import {UpgradeAdapter} from '@angular/upgrade'; +import {UpgradeModule} from '@angular/upgrade/static'; +import {PropertiesAssignmentModule} from './pages/properties-assignment/properties-assignment.module'; +import { + DataTypesServiceProvider, SharingServiceProvider, CookieServiceProvider, + StateParamsServiceFactory +} from "./utils/ng1-upgraded-provider"; +import {ConfigService} from "./services/config.service"; +import {HttpService} from "./services/http.service"; +import {HttpModule} from '@angular/http'; +import {AuthenticationService} from './services/authentication.service'; +import {Cookie2Service} from "./services/cookie.service"; +import {ComponentServiceNg2} from "./services/component-services/component.service"; +import {ServiceServiceNg2} from "./services/component-services/service.service"; +import {ComponentInstanceServiceNg2} from "./services/component-instance-services/component-instance.service"; + +export const upgradeAdapter = new UpgradeAdapter(forwardRef(() => AppModule)); + +export function configServiceFactory(config:ConfigService) { + return () => config.loadValidationConfiguration(); +} + +// export function httpServiceFactory(backend: XHRBackend, options: RequestOptions) { +// return new HttpService(backend, options); +// } + +@NgModule({ + declarations: [ + AppComponent + ], + imports: [ + BrowserModule, + UpgradeModule, + FormsModule, + HttpModule, + PropertiesAssignmentModule + ], + exports: [], + entryComponents: [], + providers: [ + HttpService, + DataTypesServiceProvider, + SharingServiceProvider, + CookieServiceProvider, + StateParamsServiceFactory, + AuthenticationService, + Cookie2Service, + ConfigService, + ComponentServiceNg2, + ServiceServiceNg2, + ComponentInstanceServiceNg2, + { + provide: APP_INITIALIZER, + useFactory: configServiceFactory, + deps: [ConfigService], + multi: true + } + ], + bootstrap: [AppComponent] +}) + + +export class AppModule { + // ngDoBootstrap() {} + constructor(public upgrade:UpgradeModule) { + + + } +} diff --git a/catalog-ui/src/app/ng2/app.routing.ts b/catalog-ui/src/app/ng2/app.routing.ts new file mode 100644 index 0000000000..38bc92619f --- /dev/null +++ b/catalog-ui/src/app/ng2/app.routing.ts @@ -0,0 +1,20 @@ +import { RouterModule, Route } from '@angular/router'; +import { ModuleWithProviders } from '@angular/core'; +// import { Page1Component } from "./pages/page1/page1.component"; +// import { Page2Component } from "./pages/page2/page2.component"; +import { PageNotFoundComponent } from "./pages/page404/page404.component"; + +const routes: Route[] = [ + // { path: 'page1', component: Page1Component }, + // { path: 'page2', component: Page2Component }, + // { path: '', pathMatch: 'full', redirectTo: 'page1'}, + { path: '**', component: PageNotFoundComponent } + /*{ loadChildren: './pages/dashboard/dashboard.module#DashboardModule', path: 'dashboard' }*/ +]; + +export const routing: ModuleWithProviders = RouterModule.forRoot( + routes, + { + useHash: true + } +); diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/dynamic-element.component.less b/catalog-ui/src/app/ng2/components/dynamic-element/dynamic-element.component.less new file mode 100644 index 0000000000..e219d49aa4 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/dynamic-element/dynamic-element.component.less @@ -0,0 +1,3 @@ +dynamic-element { + +} diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/dynamic-element.component.ts b/catalog-ui/src/app/ng2/components/dynamic-element/dynamic-element.component.ts new file mode 100644 index 0000000000..de5965e488 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/dynamic-element/dynamic-element.component.ts @@ -0,0 +1,117 @@ +import { Component, Compiler, EventEmitter, ViewContainerRef, ViewChild, Input, Output, ElementRef, ComponentRef, ComponentFactory, ComponentFactoryResolver } from '@angular/core' +import { UiElementCheckBoxComponent } from './elements-ui/checkbox/ui-element-checkbox.component'; +import { UiElementDropDownComponent, DropdownValue } from './elements-ui/dropdown/ui-element-dropdown.component'; +import { UiElementInputComponent } from './elements-ui/input/ui-element-input.component'; +import {UiElementPopoverInputComponent} from "./elements-ui/popover-input/ui-element-popover-input.component"; +import {ValidationConfiguration} from "app/models"; + +@Component({ + selector: 'dynamic-element', + template: `<div #target></div>`, + styleUrls: ['./dynamic-element.component.less'], + entryComponents: [ + UiElementInputComponent, + UiElementDropDownComponent, + UiElementCheckBoxComponent, + UiElementPopoverInputComponent + ] +}) +export class DynamicElementComponent { + + @ViewChild('target', { read: ViewContainerRef }) target: any; + @Input() type: any; + @Input() name: string; + value:any; + + // Two way binding for value (need to write the "Change" word like this) + @Output('valueChange') emitter: EventEmitter<string> = new EventEmitter<any>(); + @Input('value') set setValueValue(value) { + this.value = value; + } + + cmpRef: ComponentRef<any>; + private isViewInitialized: boolean = false; + validation = ValidationConfiguration.validation; + + constructor( + private componentFactoryResolver: ComponentFactoryResolver, + private compiler: Compiler, + private el: ElementRef) { + } + + updateComponent() { + if (!this.isViewInitialized) { + return; + } + if (this.cmpRef) { + this.cmpRef.destroy(); + } + + // Factory to create component based on type or peroperty name. + switch(this.type) { + case 'list': + case 'integer': + this.createComponent(UiElementInputComponent); + this.cmpRef.instance.pattern = this.validation.validationPatterns.integer; + break; + case 'string': + switch(this.name.toUpperCase()) { + case 'SUBNETPOOLID': + this.createComponent(UiElementPopoverInputComponent); + break; + default: + this.createComponent(UiElementInputComponent); + } + break; + case 'boolean': + //this.createComponent(UiElementCheckBoxComponent); + + this.createComponent(UiElementDropDownComponent); + + // Build drop down values + let tmp = []; + tmp.push(new DropdownValue('true','TRUE')); + tmp.push(new DropdownValue('false','FALSE')); + this.cmpRef.instance.values = tmp; + break; + default: + this.createComponent(UiElementInputComponent); + console.log("ERROR: No ui component to handle type: " + this.type); + } + + // Additional attributes in base element class + if (this.cmpRef) { + this.cmpRef.instance.name = this.name; + this.cmpRef.instance.type = this.type; + this.cmpRef.instance.value = this.value; + } + + // Subscribe to change event of of ui-element-basic and fire event to change the value + this.cmpRef.instance.baseEmitter.subscribe((value):void => { + this.emitter.emit(value) + }); + + } + + createComponent(ComponentToCreate:any):void { + let factory = this.componentFactoryResolver.resolveComponentFactory(ComponentToCreate); + this.cmpRef = this.target.createComponent(factory); + } + + ngOnChanges() { + this.updateComponent(); + } + + ngAfterContentInit() { + //console.log("DynamicElementComponent: ngAfterContentInit: type: " + this.type + " value: " + this.value); + this.isViewInitialized = true; + this.updateComponent(); + } + + ngOnDestroy() { + if (this.cmpRef) { + this.cmpRef.destroy(); + } + } + +} diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/dynamic-element.module.ts b/catalog-ui/src/app/ng2/components/dynamic-element/dynamic-element.module.ts new file mode 100644 index 0000000000..18b044bc9d --- /dev/null +++ b/catalog-ui/src/app/ng2/components/dynamic-element/dynamic-element.module.ts @@ -0,0 +1,34 @@ +import { NgModule } from "@angular/core"; +import { UiElementCheckBoxComponent } from './elements-ui/checkbox/ui-element-checkbox.component'; +import { UiElementDropDownComponent } from './elements-ui/dropdown/ui-element-dropdown.component'; +import { UiElementInputComponent } from './elements-ui/input/ui-element-input.component'; +import { DynamicElementComponent } from "app/ng2/components/dynamic-element/dynamic-element.component"; +import { BrowserModule } from '@angular/platform-browser' +import { FormsModule, ReactiveFormsModule } from '@angular/forms' +import { UiElementPopoverInputComponent } from "./elements-ui/popover-input/ui-element-popover-input.component"; +import {PopoverModule} from "../popover/popover.module"; +import {TooltipModule} from "../tooltip/tooltip.module"; + +@NgModule({ + declarations: [ + DynamicElementComponent, + UiElementInputComponent, + UiElementCheckBoxComponent, + UiElementDropDownComponent, + UiElementPopoverInputComponent + ], + imports: [ + BrowserModule, + FormsModule, + PopoverModule, + ReactiveFormsModule, + TooltipModule + ], + exports: [ + DynamicElementComponent + ], + providers: [] +}) +export class DynamicElementModule { + +} diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/checkbox/ui-element-checkbox.component.html b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/checkbox/ui-element-checkbox.component.html new file mode 100644 index 0000000000..2ad3d8e94a --- /dev/null +++ b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/checkbox/ui-element-checkbox.component.html @@ -0,0 +1 @@ +<input #{{name}} [(ngModel)]="value" type="checkbox" (change)="onSave(value)" /> diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/checkbox/ui-element-checkbox.component.less b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/checkbox/ui-element-checkbox.component.less new file mode 100644 index 0000000000..bed097fe5e --- /dev/null +++ b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/checkbox/ui-element-checkbox.component.less @@ -0,0 +1,2 @@ +/deep/ ui-element-checkbox { +} diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/checkbox/ui-element-checkbox.component.ts b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/checkbox/ui-element-checkbox.component.ts new file mode 100644 index 0000000000..152303aee7 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/checkbox/ui-element-checkbox.component.ts @@ -0,0 +1,27 @@ +import { Component, ViewChild, ElementRef, ContentChildren, Input } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser' +import { UiElementBase, UiElementBaseInterface } from './../ui-element-base.component'; + +@Component({ + selector: 'ui-element-checkbox', + templateUrl: './ui-element-checkbox.component.html', + styleUrls: ['./ui-element-checkbox.component.less'], +}) +export class UiElementCheckBoxComponent extends UiElementBase implements UiElementBaseInterface { + + constructor() { + super(); + } + + ngAfterContentInit() { + // Convert the value to boolean (instanceOf does not work, the type is undefined). + if (this.value==='true' || this.value==='false') { + this.value = this.value==='true'?true:false; + } + } + + onSave() { + this.baseEmitter.emit(this.value); + } + +} diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/dropdown/ui-element-dropdown.component.html b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/dropdown/ui-element-dropdown.component.html new file mode 100644 index 0000000000..589d00e42d --- /dev/null +++ b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/dropdown/ui-element-dropdown.component.html @@ -0,0 +1,3 @@ +<select name='{{name}}' [(ngModel)]="value" #t (change)="onSave()"> + <option *ngFor="let ddvalue of values" [value]="ddvalue.value">{{ddvalue.label}}</option> +</select> diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/dropdown/ui-element-dropdown.component.less b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/dropdown/ui-element-dropdown.component.less new file mode 100644 index 0000000000..ea3e35140e --- /dev/null +++ b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/dropdown/ui-element-dropdown.component.less @@ -0,0 +1,11 @@ +@import '../../../../../../assets/styles/variables'; + +/deep/ ui-element-dropdown { + + select { + text-indent: 6px; + border: solid 1px @main_color_o; + width: 100%; + } + +} diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/dropdown/ui-element-dropdown.component.ts b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/dropdown/ui-element-dropdown.component.ts new file mode 100644 index 0000000000..208bf54983 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/dropdown/ui-element-dropdown.component.ts @@ -0,0 +1,33 @@ +import { Component, EventEmitter, Output, Input } from '@angular/core' +import { BrowserModule } from '@angular/platform-browser' +import { UiElementBase, UiElementBaseInterface } from './../ui-element-base.component'; + +export class DropdownValue { + value:string; + label:string; + + constructor(value:string,label:string) { + this.value = value; + this.label = label; + } +} + +@Component({ + selector: 'ui-element-dropdown', + templateUrl: './ui-element-dropdown.component.html', + styleUrls: ['./ui-element-dropdown.component.less'], +}) +export class UiElementDropDownComponent extends UiElementBase implements UiElementBaseInterface { + + @Input() + values: DropdownValue[]; + + constructor() { + super(); + } + + onSave() { + this.baseEmitter.emit(this.value); + } + +} diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/input/ui-element-input.component.html b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/input/ui-element-input.component.html new file mode 100644 index 0000000000..00fea65b72 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/input/ui-element-input.component.html @@ -0,0 +1,13 @@ +<input + class="value-input" + [ngClass]="{'error': control.invalid}" + type="text" + [name]="name" + [(ngModel)]="value" + (change)="onSave()" + [attr.maxlength]="validation.propertyValue.max" + [attr.minlength]="validation.propertyValue.min" + [pattern]="pattern" + [formControl]="control" + tooltip="{{value}}" + /> diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/input/ui-element-input.component.less b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/input/ui-element-input.component.less new file mode 100644 index 0000000000..d320c7ff8b --- /dev/null +++ b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/input/ui-element-input.component.less @@ -0,0 +1,17 @@ +@import '../../../../../../assets/styles/variables'; + +/deep/ ui-element-input { + + input { + text-indent: 6px; + border: solid 1px @main_color_o; + } + + .error { + border: solid 1px @func_color_q; + color: @func_color_q; + outline: none; + box-sizing: border-box; + } + +} diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/input/ui-element-input.component.ts b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/input/ui-element-input.component.ts new file mode 100644 index 0000000000..5a14d8f206 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/input/ui-element-input.component.ts @@ -0,0 +1,22 @@ +import { Component, ViewChild, ElementRef, ContentChildren } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser' +import { UiElementBase, UiElementBaseInterface } from './../ui-element-base.component'; + +@Component({ + selector: 'ui-element-input', + templateUrl: './ui-element-input.component.html', + styleUrls: ['./ui-element-input.component.less'], +}) +export class UiElementInputComponent extends UiElementBase implements UiElementBaseInterface { + + constructor() { + super(); + this.pattern = this.validation.validationPatterns.comment; + } + + onSave() { + if (!this.control.invalid){ + this.baseEmitter.emit(this.value); + } + } +} diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/popover-input/ui-element-popover-input.component.html b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/popover-input/ui-element-popover-input.component.html new file mode 100644 index 0000000000..5adceb49a0 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/popover-input/ui-element-popover-input.component.html @@ -0,0 +1,26 @@ +<div class="popover-input-wrapper" tooltip="{{value}}"> + <input + class="value-input" + type="text" + [ngClass]="{'error': control.invalid}" + [name]="name" + [value]="value" + disabled + /> + <button [popover]="popoverForm">Edit</button> +</div> + +<popover-content #popoverForm [title]="name" [buttons]="buttonsArray" [placement]="'top'" [closeOnClickOutside]="true"> + <div class="edit-subnet-wrapper"> + <textarea rows="5" + #textArea + class="subnet-value" + [ngClass]="{'error': control.invalid}" + [(ngModel)]="value" + [attr.maxlength]="validation.propertyValue.max" + [attr.minlength]="validation.propertyValue.min" + [pattern]="pattern" + [formControl]="control" + ></textarea> + </div> +</popover-content> diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/popover-input/ui-element-popover-input.component.less b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/popover-input/ui-element-popover-input.component.less new file mode 100644 index 0000000000..5be443f7b6 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/popover-input/ui-element-popover-input.component.less @@ -0,0 +1,36 @@ +@import '../../../../../../assets/styles/variables'; + +.popover-input-wrapper { + display: flex; +} + +/deep/ ui-element-popover-input { + + .popover { + max-width: 350px; + width: 350px; + } + + .edit-subnet-wrapper { + padding: 12px; + + .subnet-value { + width: 100%; + resize: none; + } + } + + input { + padding-right: 6px; + padding-left: 6px; + border: solid 1px @main_color_o; + } + + .error { + border: solid 1px @func_color_q; + color: @func_color_q; + outline: none; + box-sizing: border-box; + } + +} diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/popover-input/ui-element-popover-input.component.ts b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/popover-input/ui-element-popover-input.component.ts new file mode 100644 index 0000000000..7300417686 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/popover-input/ui-element-popover-input.component.ts @@ -0,0 +1,39 @@ +import {Component, ViewChild, ElementRef} from '@angular/core'; +import {UiElementBase, UiElementBaseInterface} from "../ui-element-base.component"; +import {ButtonsModelMap, ButtonModel} from "app/models"; +import { PopoverContentComponent } from "app/ng2/components/popover/popover-content.component" +import { PopoverComponent } from "app/ng2/components/popover/popover.component" + +@Component({ + selector: 'ui-element-popover-input', + templateUrl: './ui-element-popover-input.component.html', + styleUrls: ['./ui-element-popover-input.component.less'] +}) +export class UiElementPopoverInputComponent extends UiElementBase implements UiElementBaseInterface { + + @ViewChild('textArea') textArea: ElementRef; + @ViewChild('popoverForm') popoverContentComponent: PopoverContentComponent; + + saveButton: ButtonModel; + buttonsArray: ButtonsModelMap; + + onSave = ():void => { + if (!this.control.invalid){ + this.baseEmitter.emit(this.value); + this.popoverContentComponent.hide(); + } + } + + constructor() { + super(); + // Create Save button and insert to buttons map + this.saveButton = new ButtonModel('save', 'blue', this.onSave); + this.buttonsArray = { 'test': this.saveButton }; + + // Define the regex pattern for this controller + this.pattern = this.validation.validationPatterns.comment; + + // Disable / Enable button according to validation + //this.control.valueChanges.subscribe(data => this.saveButton.disabled = this.control.invalid); + } +} diff --git a/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/ui-element-base.component.ts b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/ui-element-base.component.ts new file mode 100644 index 0000000000..b60271f6f0 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/dynamic-element/elements-ui/ui-element-base.component.ts @@ -0,0 +1,34 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core' +import { ValidationConfiguration } from "app/models"; +import { FormControl, Validators } from '@angular/forms'; + +export interface UiElementBaseInterface { + onSave(); +} + +@Component({ + template: ``, + styles: [] +}) +export class UiElementBase { + + protected validation = ValidationConfiguration.validation; + protected control: FormControl; + + // Two way binding for value (need to write the "Change" word like this) + @Output('valueChange') baseEmitter: EventEmitter<string> = new EventEmitter<any>(); + @Input('value') set setValueValue(value) { + this.value = value; + } + + protected name: string; + protected type: string; + protected value: any; + protected pattern: any; + + constructor() { + //this.control = new FormControl('', [Validators.required]); + this.control = new FormControl('', []); + } + +} diff --git a/catalog-ui/src/app/ng2/components/filter-properties-assignment/filter-properties-assignment.component.html b/catalog-ui/src/app/ng2/components/filter-properties-assignment/filter-properties-assignment.component.html new file mode 100644 index 0000000000..4d2b91f3b4 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/filter-properties-assignment/filter-properties-assignment.component.html @@ -0,0 +1,26 @@ +<popover-content #filterPopover [title]="'Filters'" [buttons]="footerButtons" placement="bottom-right" [hideArrow]="true"> + <!--<form [formGroup]="filterForm">--> + <form> + <div class="field"> + <label>Resource Type</label> + <div> + <checkbox [label]="'All'" [(checked)]="allSelected" (checkedChange)="selectAll()"></checkbox> + </div> + <div *ngFor="let type of typesOptions"> + <checkbox [label]="type" [(checked)]="selectedTypes[type]" (checkedChange)="onTypeSelected(type)"></checkbox> + </div> + </div> + <div class="field"> + <label>Property Name</label> + <input class="i-sdc-form-input" + name="propertyName" + [(ngModel)]="filterData.propertyName" + placeholder="Type here" + required + /> + </div> + </form> +</popover-content> +<div class="open-filter-button" [popover]="filterPopover" [ngClass]="{'open':showPopover}" (onShown)="showPopover = true" (onHidden)="showPopover = false"> + <div class="sprite-new filter-icon"></div> +</div> diff --git a/catalog-ui/src/app/ng2/components/filter-properties-assignment/filter-properties-assignment.component.less b/catalog-ui/src/app/ng2/components/filter-properties-assignment/filter-properties-assignment.component.less new file mode 100644 index 0000000000..c1341b8dcf --- /dev/null +++ b/catalog-ui/src/app/ng2/components/filter-properties-assignment/filter-properties-assignment.component.less @@ -0,0 +1,38 @@ +@import '../../../../assets/styles/variables'; +form{ + margin: 0 20px; + .field{ + padding:20px 0; + &:not(:last-child){ + border-bottom: solid 1px @main_color_o; + } + } + /deep/ [ng-reflect-checked="true"]{ + /deep/ .checkbox-label-content{ + color: @main_color_a; + } + } +} + +.open-filter-button{ + cursor: pointer; + width: 25px; + height: 34px; + display: inline-block; + &.open{ + z-index: 1061; + position: relative; + top: 2px; + background-color: @main_color_p; + border: solid 1px @main_color_c; + border-bottom: none; + } + .filter-icon{ + top: 8px; + position: relative; + } +} + +/deep/ .popover{ + border: solid 1px @main_color_c !important; +} diff --git a/catalog-ui/src/app/ng2/components/filter-properties-assignment/filter-properties-assignment.component.ts b/catalog-ui/src/app/ng2/components/filter-properties-assignment/filter-properties-assignment.component.ts new file mode 100644 index 0000000000..c23e08bc0d --- /dev/null +++ b/catalog-ui/src/app/ng2/components/filter-properties-assignment/filter-properties-assignment.component.ts @@ -0,0 +1,77 @@ +/** + * Created by rc2122 on 5/16/2017. + */ +import {Component, Input, Output, EventEmitter, ViewChild} from '@angular/core'; +import {ButtonModel, ButtonsModelMap, FilterPropertiesAssignmentData} from "app/models"; +import {PopoverComponent} from "../popover/popover.component"; +import * as sdcConfig from "../../../../../configurations/dev" + +@Component({ + selector: 'filter-properties-assignment', + templateUrl: './filter-properties-assignment.component.html', + styleUrls: ['./filter-properties-assignment.component.less'] +}) + +export class FilterPropertiesAssignmentComponent { + @Input() componentType: string; + @Output() searchProperties: EventEmitter<FilterPropertiesAssignmentData> = new EventEmitter<FilterPropertiesAssignmentData>(); + footerButtons:ButtonsModelMap = {}; + typesOptions:Array<string>;//All optional types + selectedTypes:Object = {}; + allSelected:boolean = false;//if all option selected + filterData:FilterPropertiesAssignmentData = new FilterPropertiesAssignmentData(); + @ViewChild('filterPopover') filterPopover: PopoverComponent; + + ngOnInit() { + this.footerButtons['Apply'] = new ButtonModel('Apply', 'blue', this.search, this.someTypesSelectedAndThereIsPropertyName); + this.footerButtons['Close'] = new ButtonModel('Close', 'grey', this.close); + this.componentType = this.componentType.toLocaleLowerCase(); + this.typesOptions = sdcConfig.resourceTypesFilter[this.componentType]; + } + + selectAll = () => { + _.forEach(this.typesOptions, (type) => { + this.selectedTypes[type] = this.allSelected; + }); + } + + onTypeSelected = (type:string) => { + if(!this.selectedTypes[type]){ + this.allSelected = false;//unselected 'All' + } + }; + + search = () => { + console.log('search props'); + this.filterData.selectedTypes = []; + _.forEach(sdcConfig.resourceTypesFilter[this.componentType], (type) => { + if(this.selectedTypes[type]){ + this.filterData.selectedTypes.push(type); + } + }); + this.searchProperties.emit(this.filterData); + this.filterPopover.hide(); + } + + close = () => { + this.filterPopover.hide(); + } + + someTypesSelectedAndThereIsPropertyName = ():boolean => { + if( _.find(Object.keys(this.selectedTypes),(key) => { + return this.selectedTypes[key]; + }) && this.filterData.propertyName ){ + return null + } + return true; + } + + clearAll = ():void => { + this.filterData.propertyName = ""; + _.forEach(this.selectedTypes,(value, key) => { + this.selectedTypes[key] = false; + }); + this.allSelected = false; + } + +} diff --git a/catalog-ui/src/app/ng2/components/hierarchy-navigtion/hierarchy-display-options.ts b/catalog-ui/src/app/ng2/components/hierarchy-navigtion/hierarchy-display-options.ts new file mode 100644 index 0000000000..7045286ccd --- /dev/null +++ b/catalog-ui/src/app/ng2/components/hierarchy-navigtion/hierarchy-display-options.ts @@ -0,0 +1,12 @@ +export class HierarchyDisplayOptions { + idProperty: string; + valueProperty: string; + childrenProperty: string; + searchText:string; + constructor(idProperty:string, valueProperty:string, childrenProperty?:string, searchText?:string) { + this.idProperty = idProperty; + this.valueProperty = valueProperty; + this.childrenProperty = childrenProperty; + this.searchText = searchText; + } +} diff --git a/catalog-ui/src/app/ng2/components/hierarchy-navigtion/hierarchy-navigation.component.html b/catalog-ui/src/app/ng2/components/hierarchy-navigtion/hierarchy-navigation.component.html new file mode 100644 index 0000000000..40a1c37cee --- /dev/null +++ b/catalog-ui/src/app/ng2/components/hierarchy-navigtion/hierarchy-navigation.component.html @@ -0,0 +1,13 @@ +<div class="navigation-wrapper"> + <div class="node-item" *ngFor="let item of displayData" (click)="onClick($event, item)"> + <div class="node-data-wrapper" [ngClass]="{'selected': selectedItem && selectedItem === item[displayOptions.idProperty]}"> + <span class="node-data" [ngClass]="{'mark':item[displayOptions.valueProperty] === displayOptions.searchText}">{{item[displayOptions.valueProperty]}}</span> + </div> + <div class="children-node" *ngIf="item[displayOptions.childrenProperty]"> + <hierarchy-navigation class="children-hierarchy" [displayData]="item[displayOptions.childrenProperty]" + [selectedItem]="selectedItem" + [displayOptions]="displayOptions" + (updateSelected)="onSelectedUpdate($event)"></hierarchy-navigation> + </div> + </div> +</div> diff --git a/catalog-ui/src/app/ng2/components/hierarchy-navigtion/hierarchy-navigation.component.less b/catalog-ui/src/app/ng2/components/hierarchy-navigtion/hierarchy-navigation.component.less new file mode 100644 index 0000000000..57d51616a8 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/hierarchy-navigtion/hierarchy-navigation.component.less @@ -0,0 +1,51 @@ +.navigation-wrapper { + text-align: left; +} + +.node-item { + border: 1px dotted; + border-right: none; + border-bottom: none; +} + +.node-item:last-child { + border-left: none; +} + +.node-data-wrapper { + cursor: default; + height: 30px; + line-height: 2.6em;; + position: relative; + top: -1.1em; + background-color: white; + margin-left: 0.7em; +} + +.children-node { + padding-left: 40px; +} + +.node-data { + margin-left: 10px; + margin-right: 10px; +} + +.node-data-wrapper.selected { + background-color: #e6f6fb; + + .node-data { + color: #009fdb; + } +} + +.node-data-wrapper:hover { + background-color: #eaeaea; +} + +.mark{ + background-color: yellow; +} + + + diff --git a/catalog-ui/src/app/ng2/components/hierarchy-navigtion/hierarchy-navigation.component.ts b/catalog-ui/src/app/ng2/components/hierarchy-navigtion/hierarchy-navigation.component.ts new file mode 100644 index 0000000000..428bbb4b04 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/hierarchy-navigtion/hierarchy-navigation.component.ts @@ -0,0 +1,28 @@ +import {Component, Input, Output, EventEmitter} from '@angular/core'; +import {HierarchyDisplayOptions} from './hierarchy-display-options'; + + +@Component({ + selector: 'hierarchy-navigation', + templateUrl: './hierarchy-navigation.component.html', + styleUrls: ['./hierarchy-navigation.component.less'] +}) + +export class HierarchyNavigationComponent { + @Input() displayData: Array<any>; + @Input() selectedItem: any; + @Input() displayOptions: HierarchyDisplayOptions; + + @Output() updateSelected:EventEmitter<any> = new EventEmitter(); + + onClick = ($event, item) => { + $event.stopPropagation(); + this.selectedItem = item; + this.updateSelected.emit(item); + }; + + onSelectedUpdate = ($event) => { + this.selectedItem = $event; + this.updateSelected.emit($event); + } +} diff --git a/catalog-ui/src/app/ng2/components/inputs-table/inputs-table.component.html b/catalog-ui/src/app/ng2/components/inputs-table/inputs-table.component.html new file mode 100644 index 0000000000..e7801b82cf --- /dev/null +++ b/catalog-ui/src/app/ng2/components/inputs-table/inputs-table.component.html @@ -0,0 +1,37 @@ +<div class="properties-table"> + <div class="table-header"> + <div class="table-cell col1">Property Name</div> + <div class="table-cell col2">Type</div> + <div class="table-cell col3">ES</div> + <div class="table-cell valueCol">Value</div> + </div> + <div class="table-body"> + <div class="no-data" *ngIf="!inputs || !inputs.length">No data to display</div> + <div class="table-row" *ngFor="let input of inputs"> + <div class="table-cell col1"> + <span tooltip="{{input.name}}" >{{input.name}}</span> + <span *ngIf="input.description" + class="property-description-icon sprite-new show-desc" + tooltip="{{input.description}}"></span> + </div> + <div class="table-cell col2">{{input.type | contentAfterLastDot}}</div> + <div class="table-cell col3">{{input.schema && input.schema.property && input.schema.property.type ? (input.schema.property.type | contentAfterLastDot) : ''}}</div> + <div class="table-cell valueCol input-value-col" [class.inner-table-container]="input.childrenProperties || !input.isSimpleType"> + <dynamic-element class="value-input" + *ngIf="input.isSimpleType" + pattern="validationUtils.getValidationPattern(input.type)" + [(value)]="input.defaultValue" + [type]="input.type" + [name]="input.name" + (change)="onInputValueChanged(input);"> + </dynamic-element> + <div class="delete-button-container"> + <span class="sprite-new delete-btn" (click)="onDeleteInput(input)"></span> + </div> + </div> + + </div> + </div> +</div> + + diff --git a/catalog-ui/src/app/ng2/components/inputs-table/inputs-table.component.ts b/catalog-ui/src/app/ng2/components/inputs-table/inputs-table.component.ts new file mode 100644 index 0000000000..ea01edf043 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/inputs-table/inputs-table.component.ts @@ -0,0 +1,33 @@ +/** + * Created by rc2122 on 5/4/2017. + */ +import {Component, Input, Output, EventEmitter} from "@angular/core"; +import {InputFEModel} from "app/models"; + +@Component({ + selector: 'inputs-table', + templateUrl: './inputs-table.component.html', + styleUrls: ['../properties-table/properties-table.component.less'] +}) +export class InputsTableComponent { + + @Input() inputs: Array<InputFEModel>; + + @Output() inputValueChanged: EventEmitter<any> = new EventEmitter<any>(); + @Output() deleteInput: EventEmitter<any> = new EventEmitter<any>(); + + constructor (){ + } + + onInputValueChanged = (input) => { + this.inputValueChanged.emit(input); + }; + + onDeleteInput = (input) => { + this.deleteInput.emit(input); + } + + +} + + diff --git a/catalog-ui/src/app/ng2/components/popover/popover-content.component.html b/catalog-ui/src/app/ng2/components/popover/popover-content.component.html new file mode 100644 index 0000000000..6d76f0ad06 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/popover/popover-content.component.html @@ -0,0 +1,24 @@ +<div #popoverDiv class="popover {{ effectivePlacement }}" + [style.top]="top + 'px'" + [style.left]="left + 'px'" + [class.in]="isIn" + [class.fade]="animation" + style="display: block" + role="popover" + [ngClass]="{'hide-arrow':hideArrow}"> + <div [hidden]="!closeOnMouseOutside" class="virtual-area"></div> + <div class="arrow" *ngIf="!hideArrow"></div> + <div class="popover-header"> + <span class="title">{{ title }}</span> + <span class="close-button" (click)="popover.hide()"></span> + </div> + <ng-content></ng-content> + <div class="popover-footer"> + <button *ngFor="let buttonName of buttonsNames" + class="tlv-btn {{buttons[buttonName].cssClass}}" + [disabled] = "buttons[buttonName].getDisabled && buttons[buttonName].getDisabled()" + (click) = "buttons[buttonName].callback()">{{buttons[buttonName].text}}</button> + </div> +</div> + + diff --git a/catalog-ui/src/app/ng2/components/popover/popover-content.component.less b/catalog-ui/src/app/ng2/components/popover/popover-content.component.less new file mode 100644 index 0000000000..f7b62e91f7 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/popover/popover-content.component.less @@ -0,0 +1,73 @@ +@import '../../../../assets/styles/variables'; +@import '../../../../assets/styles/mixins'; +@import '../../../../assets/styles/sprite-old'; +.popover .virtual-area { + height: 11px; + width: 100%; + position: absolute; +} +.popover.top .virtual-area { + bottom: -11px; +} +.popover.bottom .virtual-area { + top: -11px; +} +.popover.left .virtual-area { + right: -11px; +} +.popover.right .virtual-area { + left: -11px; +} +.popover.hide-arrow{ + margin: 0; +} + +.popover-header{ + .m_14_m; + font-weight: bold; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + height: 40px; + line-height: 48px; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + text-align: left; + border-bottom: solid 1px @main_color_o; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + margin: 0px 20px; + .title{ + -webkit-box-flex: 999; + -ms-flex-positive: 999; + flex-grow: 999; + } + .close-button{ + .sprite; + .sprite.x-btn-black; + cursor: pointer; + } +} + +.popover-footer{ + background-color: @tlv_color_t; + height: 40px; + clear: both; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: end; + -ms-flex-pack: end; + justify-content: flex-end; + button{ + margin: 8px 12px 8px 6px; + } +} diff --git a/catalog-ui/src/app/ng2/components/popover/popover-content.component.ts b/catalog-ui/src/app/ng2/components/popover/popover-content.component.ts new file mode 100644 index 0000000000..c4489f59b9 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/popover/popover-content.component.ts @@ -0,0 +1,258 @@ +import {Component, Input, Output, AfterViewInit, ElementRef, ChangeDetectorRef, OnDestroy, ViewChild, EventEmitter, Renderer } from "@angular/core"; +import {ButtonsModelMap} from "app/models"; +import {PopoverComponent} from "./popover.component"; + +@Component({ + selector: "popover-content", + templateUrl:'./popover-content.component.html', + styleUrls:['popover-content.component.less'] +}) +export class PopoverContentComponent implements AfterViewInit, OnDestroy { + + @Input() public title: string; + @Input() public buttons:ButtonsModelMap; + + @Input() + content: string; + + @Input() + placement: "top"|"bottom"|"left"|"right"|"auto"|"auto top"|"auto bottom"|"auto left"|"auto right" = "bottom"; + + @Input() + animation: boolean = true; + + @Input() + closeOnClickOutside: boolean = false; + + @Input() + closeOnMouseOutside: boolean = false; + + @Input() + hideArrow: boolean = false; + + @ViewChild("popoverDiv") + popoverDiv: ElementRef; + + buttonsNames:Array<string>; + + popover: PopoverComponent; + onCloseFromOutside = new EventEmitter(); + top: number = -10000; + left: number = -10000; + isIn: boolean = false; + displayType: string = "none"; + effectivePlacement: string; + + onDocumentMouseDown = (event: any) => { + const element = this.element.nativeElement; + if (!element || !this.popover) return; + if (element.contains(event.target) || this.popover.getElement().contains(event.target)) return; + this.hide(); + this.onCloseFromOutside.emit(undefined); + }; + + + constructor(protected element: ElementRef, + protected cdr: ChangeDetectorRef, + protected renderer: Renderer) { + } + + listenClickFunc: any; + listenMouseFunc: any; + + ngAfterViewInit(): void { + this.buttonsNames = Object.keys(this.buttons); + if (this.closeOnClickOutside) + this.listenClickFunc = this.renderer.listenGlobal("document", "mousedown", (event: any) => this.onDocumentMouseDown(event)); + if (this.closeOnMouseOutside) + this.listenMouseFunc = this.renderer.listenGlobal("document", "mouseover", (event: any) => this.onDocumentMouseDown(event)); + + this.show(); + this.cdr.detectChanges(); + } + + ngOnDestroy() { + if (this.closeOnClickOutside) + this.listenClickFunc(); + if (this.closeOnMouseOutside) + this.listenMouseFunc(); + } + + // ------------------------------------------------------------------------- + // Public Methods + // ------------------------------------------------------------------------- + + show(): void { + if (!this.popover || !this.popover.getElement()) + return; + + const p = this.positionElements(this.popover.getElement(), this.popoverDiv.nativeElement, this.placement); + this.displayType = "block"; + this.top = p.top; + this.left = p.left; + this.isIn = true; + } + + hide(): void { + this.top = -10000; + this.left = -10000; + this.isIn = true; + this.popover.hide(); + } + + hideFromPopover() { + this.top = -10000; + this.left = -10000; + this.isIn = true; + } + + // ------------------------------------------------------------------------- + // Protected Methods + // ------------------------------------------------------------------------- + + protected positionElements(hostEl: HTMLElement, targetEl: HTMLElement, positionStr: string, appendToBody: boolean = false): { top: number, left: number } { + let positionStrParts = positionStr.split("-"); + let pos0 = positionStrParts[0]; + let pos1 = positionStrParts[1] || "center"; + let hostElPos = appendToBody ? this.offset(hostEl) : this.position(hostEl); + let targetElWidth = targetEl.offsetWidth; + let targetElHeight = targetEl.offsetHeight; + + this.effectivePlacement = pos0 = this.getEffectivePlacement(pos0, hostEl, targetEl); + + let shiftWidth: any = { + center: function (): number { + return hostElPos.left + hostElPos.width / 2 - targetElWidth / 2; + }, + left: function (): number { + return hostElPos.left; + }, + right: function (): number { + return hostElPos.left + hostElPos.width - targetElWidth; + } + }; + + let shiftHeight: any = { + center: function (): number { + return hostElPos.top + hostElPos.height / 2 - targetElHeight / 2; + }, + top: function (): number { + return hostElPos.top; + }, + bottom: function (): number { + return hostElPos.top + hostElPos.height - targetElHeight; + } + }; + + let targetElPos: { top: number, left: number }; + switch (pos0) { + case "right": + targetElPos = { + top: shiftHeight[pos1](), + left: hostElPos.left + hostElPos.width + }; + break; + + case "left": + targetElPos = { + top: shiftHeight[pos1](), + left: hostElPos.left - targetElWidth + }; + break; + + case "bottom": + targetElPos = { + top: hostElPos.top + hostElPos.height, + left: shiftWidth[pos1]() + }; + break; + + default: + targetElPos = { + top: hostElPos.top - targetElHeight, + left: shiftWidth[pos1]() + }; + break; + } + + return targetElPos; + } + + protected position(nativeEl: HTMLElement): { width: number, height: number, top: number, left: number } { + let offsetParentBCR = { top: 0, left: 0 }; + const elBCR = this.offset(nativeEl); + const offsetParentEl = this.parentOffsetEl(nativeEl); + if (offsetParentEl !== window.document) { + offsetParentBCR = this.offset(offsetParentEl); + offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop; + offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft; + } + + const boundingClientRect = nativeEl.getBoundingClientRect(); + return { + width: boundingClientRect.width || nativeEl.offsetWidth, + height: boundingClientRect.height || nativeEl.offsetHeight, + top: elBCR.top - offsetParentBCR.top, + left: elBCR.left - offsetParentBCR.left + }; + } + + protected offset(nativeEl: any): { width: number, height: number, top: number, left: number } { + const boundingClientRect = nativeEl.getBoundingClientRect(); + return { + width: boundingClientRect.width || nativeEl.offsetWidth, + height: boundingClientRect.height || nativeEl.offsetHeight, + top: boundingClientRect.top + (window.pageYOffset || window.document.documentElement.scrollTop), + left: boundingClientRect.left + (window.pageXOffset || window.document.documentElement.scrollLeft) + }; + } + + protected getStyle(nativeEl: HTMLElement, cssProp: string): string { + if ((nativeEl as any).currentStyle) // IE + return (nativeEl as any).currentStyle[cssProp]; + + if (window.getComputedStyle) + return (window.getComputedStyle as any)(nativeEl)[cssProp]; + + // finally try and get inline style + return (nativeEl.style as any)[cssProp]; + } + + protected isStaticPositioned(nativeEl: HTMLElement): boolean { + return (this.getStyle(nativeEl, "position") || "static" ) === "static"; + } + + protected parentOffsetEl(nativeEl: HTMLElement): any { + let offsetParent: any = nativeEl.offsetParent || window.document; + while (offsetParent && offsetParent !== window.document && this.isStaticPositioned(offsetParent)) { + offsetParent = offsetParent.offsetParent; + } + return offsetParent || window.document; + } + + protected getEffectivePlacement(placement: string, hostElement: HTMLElement, targetElement: HTMLElement): string { + const placementParts = placement.split(" "); + if (placementParts[0] !== "auto") { + return placement; + } + + const hostElBoundingRect = hostElement.getBoundingClientRect(); + + const desiredPlacement = placementParts[1] || "bottom"; + + if (desiredPlacement === "top" && hostElBoundingRect.top - targetElement.offsetHeight < 0) { + return "bottom"; + } + if (desiredPlacement === "bottom" && hostElBoundingRect.bottom + targetElement.offsetHeight > window.innerHeight) { + return "top"; + } + if (desiredPlacement === "left" && hostElBoundingRect.left - targetElement.offsetWidth < 0) { + return "right"; + } + if (desiredPlacement === "right" && hostElBoundingRect.right + targetElement.offsetWidth > window.innerWidth) { + return "left"; + } + + return desiredPlacement; + } +} diff --git a/catalog-ui/src/app/ng2/components/popover/popover.component.ts b/catalog-ui/src/app/ng2/components/popover/popover.component.ts new file mode 100644 index 0000000000..a7e2881b29 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/popover/popover.component.ts @@ -0,0 +1,159 @@ +import { Directive, HostListener, ComponentRef, ViewContainerRef, ComponentFactoryResolver, ComponentFactory, Input, OnChanges, SimpleChange, Output, EventEmitter } from "@angular/core"; +import {PopoverContentComponent} from "./popover-content.component"; + +@Directive({ + selector: "[popover]", + exportAs: "popover" +}) +export class PopoverComponent implements OnChanges { + + protected PopoverComponent = PopoverContentComponent; + protected popover: ComponentRef<PopoverContentComponent>; + protected visible: boolean; + + + constructor(protected viewContainerRef: ViewContainerRef, + protected resolver: ComponentFactoryResolver) { + } + + @Input("popover") + content: string|PopoverContentComponent; + + @Input() + popoverDisabled: boolean; + + @Input() + popoverAnimation: boolean; + + @Input() + popoverPlacement: "top"|"bottom"|"left"|"right"|"auto"|"auto top"|"auto bottom"|"auto left"|"auto right"; + + @Input() + popoverTitle: string; + + @Input() + popoverOnHover: boolean = false; + + @Input() + popoverCloseOnClickOutside: boolean; + + @Input() + popoverCloseOnMouseOutside: boolean; + + @Input() + popoverDismissTimeout: number = 0; + + @Output() + onShown = new EventEmitter<PopoverComponent>(); + + @Output() + onHidden = new EventEmitter<PopoverComponent>(); + + @HostListener("click") + showOrHideOnClick(): void { + if (this.popoverOnHover) return; + if (this.popoverDisabled) return; + this.toggle(); + } + + @HostListener("focusin") + @HostListener("mouseenter") + showOnHover(): void { + if (!this.popoverOnHover) return; + if (this.popoverDisabled) return; + this.show(); + } + + @HostListener("focusout") + @HostListener("mouseleave") + hideOnHover(): void { + if (this.popoverCloseOnMouseOutside) return; + if (!this.popoverOnHover) return; + if (this.popoverDisabled) return; + this.hide(); + } + + ngOnChanges(changes: {[propertyName: string]: SimpleChange}) { + if (changes["popoverDisabled"]) { + if (changes["popoverDisabled"].currentValue) { + this.hide(); + } + } + } + + toggle() { + if (!this.visible) { + this.show(); + } else { + this.hide(); + } + } + + show() { + if (this.visible) return; + + this.visible = true; + if (typeof this.content === "string") { + const factory = this.resolver.resolveComponentFactory(this.PopoverComponent); + if (!this.visible) + return; + + this.popover = this.viewContainerRef.createComponent(factory); + const popover = this.popover.instance as PopoverContentComponent; + popover.popover = this; + popover.content = this.content as string; + if (this.popoverPlacement !== undefined) + popover.placement = this.popoverPlacement; + if (this.popoverAnimation !== undefined) + popover.animation = this.popoverAnimation; + if (this.popoverTitle !== undefined) + popover.title = this.popoverTitle; + if (this.popoverCloseOnClickOutside !== undefined) + popover.closeOnClickOutside = this.popoverCloseOnClickOutside; + if (this.popoverCloseOnMouseOutside !== undefined) + popover.closeOnMouseOutside = this.popoverCloseOnMouseOutside; + + popover.onCloseFromOutside.subscribe(() => this.hide()); + if (this.popoverDismissTimeout > 0) + setTimeout(() => this.hide(), this.popoverDismissTimeout); + } else { + const popover = this.content as PopoverContentComponent; + popover.popover = this; + if (this.popoverPlacement !== undefined) + popover.placement = this.popoverPlacement; + if (this.popoverAnimation !== undefined) + popover.animation = this.popoverAnimation; + if (this.popoverTitle !== undefined) + popover.title = this.popoverTitle; + if (this.popoverCloseOnClickOutside !== undefined) + popover.closeOnClickOutside = this.popoverCloseOnClickOutside; + if (this.popoverCloseOnMouseOutside !== undefined) + popover.closeOnMouseOutside = this.popoverCloseOnMouseOutside; + + popover.onCloseFromOutside.subscribe(() => this.hide()); + if (this.popoverDismissTimeout > 0) + setTimeout(() => this.hide(), this.popoverDismissTimeout); + popover.show(); + } + + this.onShown.emit(this); + } + + hide() { + if (!this.visible) return; + + this.visible = false; + if (this.popover) + this.popover.destroy(); + + if (this.content instanceof PopoverContentComponent) + (this.content as PopoverContentComponent).hideFromPopover(); + + this.onHidden.emit(this); + } + + getElement() { + return this.viewContainerRef.element.nativeElement; + } + +} diff --git a/catalog-ui/src/app/ng2/components/popover/popover.module.ts b/catalog-ui/src/app/ng2/components/popover/popover.module.ts new file mode 100644 index 0000000000..4bd8426ce1 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/popover/popover.module.ts @@ -0,0 +1,27 @@ +/** + * Created by rc2122 on 5/17/2017. + */ +import {NgModule} from "@angular/core"; +import { CommonModule } from '@angular/common'; +import {PopoverComponent} from "./popover.component"; +import {PopoverContentComponent} from "./popover-content.component"; + +@NgModule({ + declarations: [ + PopoverComponent, + PopoverContentComponent + ], + imports: [ + // PopoverComponent, + // PopoverContentComponent + CommonModule + ], + exports: [ + PopoverComponent, + PopoverContentComponent + ], + providers: [] +}) +export class PopoverModule { + +} diff --git a/catalog-ui/src/app/ng2/components/properties-table/derived-property/derived-property.component.html b/catalog-ui/src/app/ng2/components/properties-table/derived-property/derived-property.component.html new file mode 100644 index 0000000000..6e7d4e882b --- /dev/null +++ b/catalog-ui/src/app/ng2/components/properties-table/derived-property/derived-property.component.html @@ -0,0 +1,24 @@ +<!-- TODO: refactor this to be dynamic --> +<div class="derived-property-row" *ngIf="propertyObj.isDeclared || propertyObj.derivedDataType == derivedPropertyTypes.SIMPLE"> + <div class="table-cell" [ngClass]="{'iltered':propertyObj.name === propertyNameSearchText}"><checkbox [label]="propertyObj.name" [(checked)]="propertyObj.isSelected" [disabled]="propertyObj.isDisabled ||propertyObj.isDeclared" (checkedChange)="checkedChange($event)"></checkbox></div> + + <div class="table-cell simple"> + <input type="text" [(ngModel)]="propertyObj.value" [disabled]="propertyObj.isDisabled || propertyObj.isDeclared" (change)="valueChanged.emit()" /> + </div> +</div> +<div class="derived-property-row" *ngIf="!propertyObj.isDeclared && propertyObj.derivedDataType == derivedPropertyTypes.COMPLEX"> + <div class="table-cell" [ngClass]="{'filtered':propertyObj.name === propertyNameSearchText}"><checkbox [label]="propertyObj.name" [(checked)]="propertyObj.isSelected" [disabled]="propertyObj.isDisabled ||propertyObj.isDeclared" (checkedChange)="checkedChange($event)"></checkbox></div> + <div class="table-cell complex"> + <span class="datatype-text">{{propertyObj.type | contentAfterLastDot }}</span> + <span (click)="expandChildById(propertyObj.propertiesName)" class="expand-icon" [class.expanded]="expanded">V</span> + </div> +</div> +<div class="derived-property-row" *ngIf="!propertyObj.isDeclared && propertyObj.derivedDataType == derivedPropertyTypes.SIMPLELIST ||propertyObj.derivedDataType == derivedPropertyTypes.SIMPLEMAP"> + <div class="table-cell"> + <span [ngClass]="{'filtered':propertyObj.name === propertyNameSearchText}">{{propertyObj.name}}</span> + </div> + <div class="table-cell simple2"> + <div class="sprite-new add-item-icon" (click)="addRows(propertyObj)"></div> + </div> +</div> + diff --git a/catalog-ui/src/app/ng2/components/properties-table/derived-property/derived-property.component.less b/catalog-ui/src/app/ng2/components/properties-table/derived-property/derived-property.component.less new file mode 100644 index 0000000000..3102c5ceb8 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/properties-table/derived-property/derived-property.component.less @@ -0,0 +1,35 @@ +.derived-property-row { + display:flex; + flex-direction:row; + position:relative; + border-top: #d2d2d2 solid 1px; + + &:first-child { + border-top:none; + } + .table-cell { + flex: 0 0 50%; + padding:5px; + position:relative; + text-overflow: ellipsis; + white-space: nowrap; + + &:first-child { + border-right:#d2d2d2 solid 1px; + overflow:hidden; + } + span.expand-icon { + position: absolute; + right: 10px; + transition: 200ms transform ease-in-out; + } + span.expand-icon.expanded { + transform: rotate(-180deg); + } + } +} +.filtered { + /deep/ .checkbox-label-content{ + background-color: yellow; + } +} diff --git a/catalog-ui/src/app/ng2/components/properties-table/derived-property/derived-property.component.ts b/catalog-ui/src/app/ng2/components/properties-table/derived-property/derived-property.component.ts new file mode 100644 index 0000000000..16f069a685 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/properties-table/derived-property/derived-property.component.ts @@ -0,0 +1,46 @@ +/** + * Created by rc2122 on 4/20/2017. + */ +import {Component, Input, Output, EventEmitter} from "@angular/core"; +import { DerivedFEProperty, DerivedPropertyType} from "app/models"; +import {PropertiesService} from "../../../services/properties.service"; + +@Component({ + selector: 'derived-property', + templateUrl: './derived-property.component.html', + styleUrls: ['./derived-property.component.less'] +}) +export class DerivedPropertyComponent { + + derivedPropertyTypes = DerivedPropertyType; //http://stackoverflow.com/questions/35835984/how-to-use-a-typescript-enum-value-in-an-angular2-ngswitch-statement + + @Input() propertyObj: DerivedFEProperty; + @Input() propertyNameSearchText: string; + @Input() expanded: boolean; + @Output() valueChanged: EventEmitter<any> = new EventEmitter<any>(); + @Output() expandChild: EventEmitter<string> = new EventEmitter<string>(); + @Output() selectProperty: EventEmitter<boolean> = new EventEmitter<boolean>(); + + + constructor ( private propertiesService:PropertiesService){ + } + + + propValueChanged = () => { + this.valueChanged.emit(this.propertyObj); + }; + + expandChildById = (id: string) => { + this.expandChild.emit(id); + } + + checkedChange = (isChecked:boolean) => { + this.selectProperty.emit(isChecked); + } + + addRows = (flatProperty: DerivedFEProperty): void => { + console.log("ADDING A ROW OF TYPE " + flatProperty.type); + console.log(flatProperty); + } + +} diff --git a/catalog-ui/src/app/ng2/components/properties-table/dynamic-property/dynamic-property.component.html b/catalog-ui/src/app/ng2/components/properties-table/dynamic-property/dynamic-property.component.html new file mode 100644 index 0000000000..17e4002ae5 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/properties-table/dynamic-property/dynamic-property.component.html @@ -0,0 +1,66 @@ +<div class="dynamic-property-row" + [ngClass]="{'selected': selectedPropertyId === property.uniqueId }" + [class.with-top-border]="property.isChildOfListOrMap" + (click)="onClickPropertyRow(property, $event)"> + <!-- LEFT CELL - for all NON-PropFEModel properties except for MAP)--> + <ng-container *ngIf="!isPropertyFEModel"> + <div class="table-cell" *ngIf="canBeDeclared"> <!-- simple children of complex type --> + <checkbox [label]="property.name" [(checked)]="property.isSelected" [disabled]="property.isDisabled ||property.isDeclared" (checkedChange)="checkProperty.emit(property.propertiesName)" + ></checkbox> + </div> + <div class="table-cell" *ngIf="!canBeDeclared && !property.isChildOfListOrMap">{{property.name}}</div> <!-- simple children of complex type within map or list --> + </ng-container> + <!-- RIGHT CELL --> + <ng-container *ngIf="propType == derivedPropertyTypes.SIMPLE || property.isDeclared"> + <div class="table-cell" [class.full-width]="isPropertyFEModel || property.isChildOfListOrMap"> + <dynamic-element class="value-input" + pattern="validationUtils.getValidationPattern(property.type)" + [(value)]="property.value" + [type]="property.type" + [name]="property.name" + (valueChange)="propValueChanged(property);" + > <!--[disabled]="property.isDisabled || property.isDeclared" --> + </dynamic-element> + </div> + </ng-container> + <ng-container *ngIf="!property.isDeclared"> <!-- other cases need to fail if isDeclared, in which case we need to use full width value above instead) --> + + <ng-container *ngIf="propType == derivedPropertyTypes.COMPLEX && !isPropertyFEModel"> + <div class="table-cell complex" [class.full-width]="property.isChildOfListOrMap"> + <span>{{property.type | contentAfterLastDot }}</span> + </div> + </ng-container> + <ng-container *ngIf="property.isChildOfListOrMap && propType == derivedPropertyTypes.MAP"> + <div class="table-cell map-entry"><input type="text" /></div> + <div class="table-cell" *ngIf="property.schema.property.isSimpleType"> + <input type="text" [(ngModel)]="property.value" /></div> + <div class="table-cell" *ngIf="!property.schema.property.isSimpleType"> + <span>{{property.schema.property.type | contentAfterLastDot }}</span> + </div> + </ng-container> + + <!-- ICONS: add, delete, and expand --> + <div *ngIf="(propType == derivedPropertyTypes.LIST || propType == derivedPropertyTypes.MAP) && !property.isChildOfListOrMap" class="table-cell"><span class="sprite-new add-item-icon" (click)="addRows()"></span></div> + <span *ngIf="property.isChildOfListOrMap" class="delete-span sprite-new delete-item-icon" (click)="deleteItem.emit(property.propertiesName)"></span> + <span *ngIf="propType == derivedPropertyTypes.COMPLEX || (property.isChildOfListOrMap && propChildren)" (click)="expandChildById(propPath)" class="expand-icon" [class.expanded]="propPath == expandedChildId">V</span> + </ng-container> + +</div> +<!-- Now, recurse into children --> +<ng-container *ngIf="propChildren && !property.isDeclared"> + <ng-container *ngFor="let prop of propChildren | filterChildProperties: expandedChildId; trackBy:prop?.propertiesName"> + <dynamic-property + [selectedPropertyId]="selectedPropertyId" + [canBeDeclared]="childrenCanBeDeclared" + [property]="prop" + [expandedChildId]="expandedChildId" + [propChildren]="" + (valueChanged)="propValueChanged(prop);" + (expandChild)="expandChildById($event)" + (deleteItem)="deleteListOrMapItem($event)" + (clickOnPropertyRow)="onClickPropertyRow($event)" + (checkProperty)="checkedChange($event)" + > + </dynamic-property> + </ng-container> +</ng-container> diff --git a/catalog-ui/src/app/ng2/components/properties-table/dynamic-property/dynamic-property.component.less b/catalog-ui/src/app/ng2/components/properties-table/dynamic-property/dynamic-property.component.less new file mode 100644 index 0000000000..53cde3035a --- /dev/null +++ b/catalog-ui/src/app/ng2/components/properties-table/dynamic-property/dynamic-property.component.less @@ -0,0 +1,48 @@ +.dynamic-property-row { + display:flex; + flex-direction:row; + position:relative; + + .table-cell { + flex: 1 0 50%; + padding:5px; + position:relative; + text-overflow: ellipsis; + white-space: nowrap; + + + &.full-width { + border-right:none; + flex: 0 0 100%; + } + + &:first-child:not(:only-child) { + border-right:#d2d2d2 solid 1px; + overflow:hidden; + } + + + } + span.delete-item-icon{ + position: absolute; + right: 25px; + top: 10px; + } + span.add-item-icon { + float:right; + } + span.expand-icon { + position: absolute; + top:6px; + right: 10px; + transition: 200ms transform ease-in-out; + } + span.expand-icon.expanded { + transform: rotate(-180deg); + } +} +.filtered { + /deep/ .checkbox-label-content{ + background-color: yellow; + } +} diff --git a/catalog-ui/src/app/ng2/components/properties-table/dynamic-property/dynamic-property.component.ts b/catalog-ui/src/app/ng2/components/properties-table/dynamic-property/dynamic-property.component.ts new file mode 100644 index 0000000000..0ca93a773f --- /dev/null +++ b/catalog-ui/src/app/ng2/components/properties-table/dynamic-property/dynamic-property.component.ts @@ -0,0 +1,149 @@ +import {Component, Input, Output, EventEmitter} from "@angular/core"; +import { PropertyBEModel, PropertyFEModel, DerivedFEProperty, DerivedPropertyType, SchemaPropertyGroupModel, DataTypeModel } from "app/models"; +import { PROPERTY_DATA, PROPERTY_TYPES } from 'app/utils'; +import { PropertiesUtils } from "app/ng2/pages/properties-assignment/properties.utils"; +import { DataTypeService } from "../../../services/data-type.service"; +import { UUID } from "angular2-uuid"; + + +@Component({ + selector: 'dynamic-property', + templateUrl: './dynamic-property.component.html', + styleUrls: ['./dynamic-property.component.less'] +}) +export class DynamicPropertyComponent { + + derivedPropertyTypes = DerivedPropertyType; //http://stackoverflow.com/questions/35835984/how-to-use-a-typescript-enum-value-in-an-angular2-ngswitch-statement + propType: DerivedPropertyType; + propPath: string; + isPropertyFEModel: boolean; + mapOfIDsAndKeys: Map<string, string> = new Map(); //used for map and list + + childrenCanBeDeclared: boolean; + @Input() canBeDeclared: boolean; + @Input() property: PropertyFEModel | DerivedFEProperty; + @Input() propChildren: Array<DerivedFEProperty>; + @Input() expandedChildId: string; + @Input() selectedPropertyId: string; + + @Output() valueChanged: EventEmitter<any> = new EventEmitter<any>(); + @Output() expandChild: EventEmitter<string> = new EventEmitter<string>(); + @Output() checkProperty: EventEmitter<string> = new EventEmitter<string>(); + @Output() deleteItem: EventEmitter<string> = new EventEmitter<string>(); + @Output() clickOnPropertyRow: EventEmitter<PropertyFEModel | DerivedFEProperty> = new EventEmitter<PropertyFEModel | DerivedFEProperty>(); + + constructor(private propertiesUtils: PropertiesUtils, private dataTypeService: DataTypeService) { + } + + ngOnInit() { + this.isPropertyFEModel = this.property instanceof PropertyFEModel; + if (this.property instanceof PropertyFEModel) { + this.propType = this.getDerivedPropertyType(this.property.type); + this.propPath = this.property.name; + } else { + this.propType = this.property.derivedDataType; + this.propPath = this.property.propertiesName; + } + + this.childrenCanBeDeclared = this.canBeDeclared && this.propType != this.derivedPropertyTypes.MAP && this.propType != this.derivedPropertyTypes.LIST; + + if (this.propType == this.derivedPropertyTypes.LIST || this.propType == this.derivedPropertyTypes.MAP) { + this.initializeValues(); + } + + } + + initializeValues = () => { + let tempValue: any; + if (this.property.value) { + tempValue = JSON.parse(this.property.value); + if (!_.isEmpty(tempValue)) { + tempValue.forEach((element, key) => { + let newChildID: string = this.createNewChildProperty(JSON.stringify(element)); + this.mapOfIDsAndKeys[newChildID] = key; + console.log(this.mapOfIDsAndKeys); + }); + } + } + //this.pseudoChildren = []; + //this.valueObjRef = []; + //TODO: generate necessary elements for existing values here + // if (this.propType == this.derivedPropertyTypes.LIST) { + // this.valueObjRef = (this.property.value) ? JSON.parse(this.property.value) : []; + // } else if (this.propType == this.derivedPropertyTypes.MAP) { + // this.valueObjRef = (this.property.value)? JSON.parse(this.property.value) : {}; + // } + console.log(this.property.value); + } + + onClickPropertyRow = (property, event) => { + // Because DynamicPropertyComponent is recrusive second time the event is fire event.stopPropagation = undefined + event && event.stopPropagation && event.stopPropagation(); + this.clickOnPropertyRow.emit(property); + } + + deleteListOrMapItem = (itemName: string) => { + this.propChildren = this.propChildren.filter(prop => prop.propertiesName.indexOf(itemName) != 0); //remove item and children; + } + + propValueChanged = (property) => { + console.log("property value change!! Prop type: " + property.type + " New value: " + property.value); + this.valueChanged.emit(property); + }; + + expandChildById = (id: string) => { + this.expandedChildId = id; + this.expandChild.emit(id); + } + + checkedChange = (propName: string) => { + this.checkProperty.emit(propName); + } + + + + addRows = (): void => { //from within the template, when creating empty item + let childPropId = this.createNewChildProperty(); + this.expandChildById(this.propPath + "#" + childPropId); + } + + createNewChildProperty = (value?:string):string => { + let propUUID:string = UUID.UUID(); + let newProp: DerivedFEProperty; + if (this.propType == this.derivedPropertyTypes.LIST) { //for list - create new prop of schema type + newProp = new DerivedFEProperty(propUUID, this.propPath, this.property.schema.property.type, value, true); + } else { //for map - create new prop of type map, with schema, but with flag that its a child + newProp = new DerivedFEProperty(propUUID, this.propPath, this.property.type, value, true, this.property.schema); + } + + + this.propChildren = this.propChildren || []; + this.propChildren.push(newProp); + + //if it's a complex type, add children properties + if (!this.property.schema.property.isSimpleType) { + let schemaDataType: DataTypeModel = this.dataTypeService.getDataTypeByTypeName(this.property.schema.property.type); + this.dataTypeService.getDerivedDataTypeProperties(schemaDataType, this.propChildren, newProp.propertiesName); + this.propertiesUtils.assignValuesRecursively(JSON.parse(value), this.propChildren, newProp.propertiesName); + console.log(JSON.stringify(this.propChildren)); + } + + return propUUID; + } + + + + //TODO: remove this and move to somewhere central!! (or make all properties be the same type...) + getDerivedPropertyType = (type) => { + if (PROPERTY_DATA.SIMPLE_TYPES.indexOf(type) > -1) { + return DerivedPropertyType.SIMPLE; + } else if (type == PROPERTY_TYPES.LIST) { + return DerivedPropertyType.LIST; + } else if (type == PROPERTY_TYPES.MAP) { + return DerivedPropertyType.MAP; + } else { + return DerivedPropertyType.COMPLEX; + } + } + +} diff --git a/catalog-ui/src/app/ng2/components/properties-table/list-property/list-property.component.html b/catalog-ui/src/app/ng2/components/properties-table/list-property/list-property.component.html new file mode 100644 index 0000000000..a251d33649 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/properties-table/list-property/list-property.component.html @@ -0,0 +1,33 @@ +<div class="add-data-row"> + <div class="sprite-new add-item-icon" (click)="addListItem()"></div> +</div> +<div *ngIf="property.schema.property.isSimpleType && property.valueObjectRef && property.valueObjectRef.length"> + <!--the ngFor runs on dummy array in the list array length and not on list array in order to prevent from page do the ngFor again when user changes an item--> + <div class="simple-list-item" *ngFor="let item of getNumber(property.valueObjectRef); let i = index"> + <input class="value-input" [(ngModel)]="property.valueObjectRef[i]" + type="property.schema.property.derivedFromSimpleTypeName || property.schema.property.type" + (change)="propValueChanged()"/> + <span class="delete-span sprite-new delete-item-icon" (click)="deleteListItem(i)"></span> + </div> +</div> +<div class="test" *ngIf="property.schema.property.isDataType && property.childrenProperties && property.childrenProperties.length"> + <template ngFor let-item [ngForOf]="property.childrenProperties" let-i="index"> + <div class="table-inner-row" (click)="onChildPropertySelected(item)" [ngClass]="{'selected': selectedPropertyId === item.treeNodeId}"> + <div class="table-cell">{{item.name}}</div> + <div class="table-cell"> + <span class="delete-span sprite-new delete-item-icon" (click)="deleteListItem(i)"></span> + <span (click)="property.updateExpandedChildPropertyId(item.treeNodeId)">V</span> + </div> + </div> + <div class="table-inner-row" *ngIf="property.expandedChildPropertyId == item.treeNodeId"> + <div class="inner-table-container"> + <properties-value-inner-table [property]="item" + (selectChildProperty)="onChildPropertySelected($event)" + [selectedPropertyId]="selectedPropertyId" + [propertyNameSearchText]="propertyNameSearchText" + (valueChanged)="propValueChanged()"></properties-value-inner-table> + </div> + </div> + </template> + +</div> diff --git a/catalog-ui/src/app/ng2/components/properties-table/list-property/list-property.component.less b/catalog-ui/src/app/ng2/components/properties-table/list-property/list-property.component.less new file mode 100644 index 0000000000..7c4d90a38d --- /dev/null +++ b/catalog-ui/src/app/ng2/components/properties-table/list-property/list-property.component.less @@ -0,0 +1,3 @@ +.simple-list-item{ + position: relative; +} diff --git a/catalog-ui/src/app/ng2/components/properties-table/list-property/list-property.component.ts b/catalog-ui/src/app/ng2/components/properties-table/list-property/list-property.component.ts new file mode 100644 index 0000000000..96f8c680a2 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/properties-table/list-property/list-property.component.ts @@ -0,0 +1,85 @@ +/** + * Created by rc2122 on 4/23/2017. + */ +import {Component, Input, Output, EventEmitter} from "@angular/core"; +import { PropertyFEModel} from "app/models"; +import {PropertiesService} from "app/ng2/services/properties.service"; +import { ContentAfterLastDotPipe } from "app/ng2/pipes/contentAfterLastDot.pipe"; +import {UUID} from "angular2-uuid"; +import {ComponentType} from "app/utils"; + +@Component({ + selector: 'list-property', + templateUrl: './list-property.component.html', + styleUrls: ['../properties-value-inner-table/properties-value-inner-table.component.less', './list-property.component.less'] +}) +export class ListPropertyComponent { + + @Input() property: PropertyFEModel; + @Input() selectedPropertyId: string; + @Input() propertyNameSearchText:string; + + @Output() valueChanged: EventEmitter<any> = new EventEmitter<any>(); + @Output() selectChildProperty: EventEmitter<any> = new EventEmitter<PropertyFEModel>(); + + constructor ( private propertiesService:PropertiesService, private contentAfterLastDotPipe:ContentAfterLastDotPipe ){ + } + + propValueChanged = () => { + this.valueChanged.emit(this.property); + }; + + onChildPropertySelected = (property) => { + this.selectChildProperty.emit(property); + }; + + getNumber = (valueObjectRef: any): Array<any> => { + let num: number = (valueObjectRef) ? valueObjectRef.length : 0; + return new Array(num); + } + + createNewChildProperty = ():void => { + let newProperty: PropertyFEModel = new PropertyFEModel(this.contentAfterLastDotPipe.transform(this.property.schema.property.type), + this.property.schema.property.type, + UUID.UUID(), + this.property, + this.property.valueObjectRef[this.property.childrenProperties.length] + ); + this.propertiesService.createPropertiesTreeForProp(newProperty); + this.property.childrenProperties.push(newProperty); + } + + addListItem = ():void => { + this.property.valueObjectRef = this.property.valueObjectRef || []; + this.property.childrenProperties = this.property.childrenProperties || []; + if (this.property.schema.property.isSimpleType){ + if( this.property.valueObjectRef.indexOf("") == -1 ) {//prevent insert multiple empty simple type items to list + this.property.valueObjectRef.push(""); + } + }else{ + this.property.valueObjectRef[this.property.childrenProperties.length] = {}; + this.property.childrenProperties = this.property.childrenProperties || []; + this.createNewChildProperty(); + this.valueChanged.emit(this.property); + } + } + + deleteListItem = (indexInList:number):void => { + this.property.valueObjectRef.splice(indexInList, 1); + if(this.property.childrenProperties){ + this.property.childrenProperties.splice(indexInList, 1); + } + if (!this.property.valueObjectRef.length) {//only when user removes all items from list - put the default + if ( this.property.defaultValue ) { + angular.copy(JSON.parse(this.property.defaultValue), this.property.valueObjectRef); + if (this.property.schema.property.isDataType){ + _.forEach(this.property.valueObjectRef, () => { + this.createNewChildProperty(); + }); + } + } + } + this.valueChanged.emit(this.property); + } + +} diff --git a/catalog-ui/src/app/ng2/components/properties-table/map-property/map-property.component.html b/catalog-ui/src/app/ng2/components/properties-table/map-property/map-property.component.html new file mode 100644 index 0000000000..e1975175a8 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/properties-table/map-property/map-property.component.html @@ -0,0 +1,38 @@ +<div class="add-data-row"> + <div class="sprite-new add-item-icon" (click)="addMapItemFields()"></div> +</div> + +<!--the ngFor runs on dummy array in mapKey array length and not on maKeys array in order to prevent from page do the ngFor again when user changes a key--> +<template ngFor let-num [ngForOf]="getNumber(mapKeys.length)" let-i="index"> + <div class="table-inner-row" + [ngClass]="{'selected': property.schema.property.isDataType && selectedPropertyId === property.childrenProperties[i].treeNodeId}" + (click)="property.schema.property.isDataType && onChildPropertySelected(property.childrenProperties[i])"> + <div class="table-cell"> + <input class="value-input" (keyup)="changeKeyOfMap(mapKeys[i], i);$event.stopPropagation();" + [(ngModel)]="mapKeys[i]" + name="mapKey{{property.treeNodeId}}{{i}}" + (change)="propValueChanged()"/> + </div> + <div class="table-cell"> + <input class="value-input" *ngIf="property.schema.property.isSimpleType" + [(ngModel)]="property.valueObjectRef[mapKeys[i]]" + type="property.schema.property.derivedFromSimpleTypeName || property.schema.property.type" + (change)="propValueChanged()"/> + <div *ngIf="property.schema.property.isDataType"> + <div>{{ property.schema.property.type | contentAfterLastDot }}</div> + <span (click)="property.updateExpandedChildPropertyId(property.childrenProperties[i].treeNodeId)">V</span> + </div> + <span class="delete-span sprite-new delete-item-icon" (click)="deleteMapItem(i)"></span> + </div> + </div> + <div class="table-inner-row" *ngIf="property.schema.property.isDataType && property.expandedChildPropertyId == property.childrenProperties[i].treeNodeId"> + <div class="inner-table-container"> + <properties-value-inner-table [property]="property.childrenProperties[i]" + [selectedPropertyId]="selectedPropertyId" + [propertyNameSearchText]="propertyNameSearchText" + (selectChildProperty)="onChildPropertySelected($event)" + (valueChanged)="propValueChanged()"></properties-value-inner-table> + </div> + </div> +</template> + diff --git a/catalog-ui/src/app/ng2/components/properties-table/map-property/map-property.component.ts b/catalog-ui/src/app/ng2/components/properties-table/map-property/map-property.component.ts new file mode 100644 index 0000000000..d62d0b94e3 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/properties-table/map-property/map-property.component.ts @@ -0,0 +1,121 @@ +/** + * Created by rc2122 on 4/24/2017. + */ +/** + * Created by rc2122 on 4/23/2017. + */ +import {Component, Input, Output, EventEmitter} from "@angular/core"; +import { PropertyFEModel} from "app/models"; +import { PropertiesService } from "../../../services/properties.service"; +import {ComponentType} from "app/utils"; +import {UUID} from "angular2-uuid"; + +@Component({ + selector: 'map-property', + templateUrl: './map-property.component.html', + styleUrls: ['../properties-value-inner-table/properties-value-inner-table.component.less'] +}) +export class MapPropertyComponent { + + @Input() property: PropertyFEModel; + @Input() selectedPropertyId: string; + @Input() propertyNameSearchText:string; + + @Output() valueChanged: EventEmitter<any> = new EventEmitter<any>(); + @Output() selectChildProperty: EventEmitter<any> = new EventEmitter<PropertyFEModel>(); + + constructor ( private propertiesService:PropertiesService){ + } + + mapKeys:Array<string>; + + ngOnInit() { + this.mapKeys = Object.keys(this.property.valueObjectRef); + } + + propValueChanged = () => { + this.valueChanged.emit(this.property); + }; + + onChildPropertySelected = (property) => { + this.selectChildProperty.emit(property); + }; + + getNumber = (num:number):Array<any> => { + return new Array(num); + } + + createNewChildProperty = (mapKey:string):void => { + + let newProperty: PropertyFEModel = new PropertyFEModel(mapKey, + this.property.schema.property.type, + UUID.UUID(), this.property, + this.property.valueObjectRef[mapKey]); + this.propertiesService.createPropertiesTreeForProp(newProperty); + this.property.childrenProperties = this.property.childrenProperties || []; + this.property.childrenProperties.push(newProperty); + } + + //get: new key and the index of this item in the map + //This method checks if the new key isn't exist already in the map and update the object and the children array with the new key + changeKeyOfMap = (newKey:string, index:number):void => { + //let fieldName:string = "mapKey" + this.property.treeNodeId + index; + let oldKey:string = Object.keys(this.property.valueObjectRef)[index]; + let existsKeyIndex:number = Object.keys(this.property.valueObjectRef).indexOf(newKey); + if (existsKeyIndex > -1 && existsKeyIndex != index) { + //error for exists key validation + } else { + //remove error for exists key validation and if the form is valid - update the map object + let newObj = {}; + angular.forEach(this.property.valueObjectRef,function(value:any,key:string){ + if(key == oldKey){ + newObj[newKey] = value; + }else{ + newObj[key] = value; + } + }); + this.property.valueObjectRef = newObj; + this.property.parent.valueObjectRef[this.property.name] = this.property.valueObjectRef;//in order to prevent break ref + if(this.property.childrenProperties){ + this.property.childrenProperties[index].name = newKey;//update this property childrenProperties with the new key + } + } + } + + //get: index of the item in the map + //This method removes item from map. + deleteMapItem = (index:number):void=> { + delete this.property.valueObjectRef[this.mapKeys[index]]; + this.mapKeys.splice(index, 1); + if(this.property.childrenProperties){ + this.property.childrenProperties.splice(index, 1); + } + if (!this.mapKeys.length) {//only when user removes all pairs of key-value fields - put the default + if (this.property.defaultValue) { + angular.copy(JSON.parse(this.property.defaultValue), this.property.valueObjectRef); + this.mapKeys = Object.keys(this.property.valueObjectRef); + if (this.property.schema.property.isDataType){ + angular.forEach(this.property.valueObjectRef, (value, key) => { + this.createNewChildProperty(key); + }, this); + } + } + } + this.valueChanged.emit(this.property); + } + + //This method inserts new empty item to map + addMapItemFields = ():void => { + this.property.valueObjectRef = this.property.valueObjectRef || {}; + if (this.property.schema.property.isSimpleType){ + this.property.valueObjectRef[''] = null; + }else{ + if(!this.property.valueObjectRef['']){ + this.property.valueObjectRef[''] = {}; + this.createNewChildProperty(''); + } + } + this.mapKeys = Object.keys(this.property.valueObjectRef); + } +} + diff --git a/catalog-ui/src/app/ng2/components/properties-table/properties-table.component.html b/catalog-ui/src/app/ng2/components/properties-table/properties-table.component.html new file mode 100644 index 0000000000..3ab47074e7 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/properties-table/properties-table.component.html @@ -0,0 +1,135 @@ + + +<div class="properties-table"> + <div class="table-header"> + <div class="table-cell col1">Property Name</div> + <div class="table-cell col2">Type</div> + <div class="table-cell col3">ES</div> + <div class="table-cell valueCol">Value</div> + </div> + <div class="table-body"> + <div class="no-data" *ngIf="!feInstancesNames">No data to display</div> + + <div *ngFor="let instanceName of feInstancesNames; trackBy:instanceName"> + <div class="table-rows-header">{{instanceName | contentAfterLastDot}}</div> + + <div class="table-row" *ngFor="let property of fePropertiesMap[instanceName]; trackBy:property?.name" (click)="onClickPropertyRow(property, instanceName, $event)" [ngClass]="{'selected': selectedPropertyId === property.uniqueId}"> + <div class="table-cell col1"> + <checkbox [label]="property.name" + [(checked)]="property.isSelected" + [disabled]="property.isDisabled || property.isDeclared" + (checkedChange)="propertyChecked(property)" + tooltip="{{property.name}}"></checkbox> + <span *ngIf="property.description" class="property-description-icon sprite-new show-desc" tooltip="{{property.description}}"></span> + </div> + <div class="table-cell col2">{{property.type | contentAfterLastDot}}</div> + <div class="table-cell col3">{{property.schema && property.schema.property && property.schema.property.type ? (property.schema.property.type + | contentAfterLastDot) : ''}}</div> + <div class="table-cell valueCol"> + <!-- [ngClass]="{'filtered':property.name === propertyNameSearchText}" (selectProperty)="propertySelected(property, $event, flatProperty.propertiesName)" [propType]="property.type" [propSchema]="property.schema" [propKey]="" [propValue]="property.value"--> + <dynamic-property + [selectedPropertyId]="selectedPropertyId" + [canBeDeclared]="true" + [property]="property" + [expandedChildId]="property.expandedChildPropertyId" + [propChildren]="property.flattenedChildren | filterChildProperties : property.expandedChildPropertyId" + (valueChanged)="propValueChanged(property);" + (expandChild)="property.updateExpandedChildPropertyId($event)" + (clickOnPropertyRow)="onClickPropertyInnerRow($event, instanceName)" + (checkProperty)="propertyChecked(property, $event)" + > + </dynamic-property> + + </div> + </div> +<!-- + <div *ngIf="!property.isSimpleType && !property.isDeclared" class="table-cell valueCol child-property-container"> + + <div class="child-property-row" *ngFor="let flatProperty of property.flattenedChildren | filterChildProperties : property.expandedChildPropertyId" + [ngClass]="{'selected': selectedPropertyId === flatProperty.uniqueId}" (click)="clickOnPropertyRow(flatProperty, instanceName, $event)" [@fadeIn]="''"> + <derived-property [propertyObj]="flatProperty" [propertyNameSearchText]="propertyNameSearchText" [expanded]="flatProperty.propertiesName == property.expandedChildPropertyId" + (selectProperty)="propertySelected(property, $event, flatProperty.propertiesName)" (valueChanged)="propValueChanged(property)" (expandChild)="property.updateExpandedChildPropertyId($event)"></derived-property> + </div> + + </div> + <!-- + + + <property [propertyObj]="flatProperty" + [expanded]="flatProperty.parentName == property.expandedChildPropertyId" + (valueChanged)="propValueChanged($event)" + (selectChildProperty)="onChildPropertySelected($event)"></property> + <list-property *ngIf="property.type == 'list'" + [property]="property" + [selectedPropertyId]="selectedPropertyId" + (valueChanged)="propValueChanged($event)" + (selectChildProperty)="onChildPropertySelected($event)"></list-property> + <map-property *ngIf="property.type == 'map'" + [property]="property" + [selectedPropertyId]="selectedPropertyId" + (valueChanged)="propValueChanged($event)" + (selectChildProperty)="onChildPropertySelected($event)"></map-property> + <properties-value-inner-table *ngIf="property.isDataType" + [property]="property" + [selectedPropertyId]="selectedPropertyId" + (selectChildProperty)="onChildPropertySelected($event)" + (valueChanged)="propValueChanged($event)"></properties-value-inner-table> + --> + + + + </div> + + </div> +</div> +<!-- +<div class="properties-table" style="display:block"> + <div class="table-header"> + <div class="table-cell col1">Property Name</div> + <div class="table-cell col2">Type</div> + <div class="table-cell col3">ES</div> + <div class="table-cell valueCol">Value</div> + </div> + <div class="table-body"> + <div class="no-data" *ngIf="!properties || !properties.length">No data to display</div> + <div class="table-row" *ngFor="let property of properties" (click)="clickOnPropertyRow(property)"> + <div class="table-cell col1"><checkbox [label]="property.name" [(checked)]="property.isSelected" (checkedChange)="propSelected(property)" [disabled]="property.isDisabled"></checkbox></div> + <div class="table-cell col2">{{property.type | contentAfterLastDot}}</div> + <div class="table-cell col3">{{property.schema && property.schema.property && property.schema.property.type ? (property.schema.property.type | contentAfterLastDot) : ''}}</div> + <div class="table-cell valueCol" [class.inner-table-container]="property.childrenProperties || !property.isSimpleType"> + <!--<input class="value-input" *ngIf="property.isSimpleType" + type="property.derivedFromSimpleTypeName || property.type" + [(ngModel)]="property.value" + (change)="putDefaultValueInEmptyProperty(property);propValueChanged(property);" + pattern="validationUtils.getValidationPattern(property.type)"/> + <dynamic-element + class="value-input" + *ngIf="property.isSimpleType" + [(value)]="property.value" + (valueChange)="putDefaultValueInEmptyProperty(property);propValueChanged(property);" + [type]="property.derivedFromSimpleTypeName || property.type" + [name]="property.name"> + </dynamic-element> + <list-property *ngIf="property.type == 'list'" + [property]="property" + [selectedPropertyId]="selectedPropertyId" + (valueChanged)="propValueChanged($event)" + (selectChildProperty)="onChildPropertySelected($event)"></list-property> + <map-property *ngIf="property.type == 'map'" + [property]="property" + [selectedPropertyId]="selectedPropertyId" + (valueChanged)="propValueChanged($event)" + (selectChildProperty)="onChildPropertySelected($event)"></map-property> + <properties-value-inner-table *ngIf="property.isDataType" + [property]="property" + [selectedPropertyId]="selectedPropertyId" + (selectChildProperty)="onChildPropertySelected($event)" + (valueChanged)="propValueChanged($event)"></properties-value-inner-table> + </div> + + </div> + </div> +</div> --> + + + diff --git a/catalog-ui/src/app/ng2/components/properties-table/properties-table.component.less b/catalog-ui/src/app/ng2/components/properties-table/properties-table.component.less new file mode 100644 index 0000000000..de080dfdc9 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/properties-table/properties-table.component.less @@ -0,0 +1,188 @@ + +@import './../../../../assets/styles/variables.less'; + +:host /deep/ input { width:100%;} + +.properties-table { + display:flex; + flex-direction:column; + flex: 1; + height:100%; + text-align:left; + + + .child-property-container { + display:flex; + flex-direction:column; + + &.table-cell { + padding:0; + } + + .child-property-row { + border-bottom: #d2d2d2 solid 1px; + &:last-child { + border-bottom:none; + } + } + } + + + derived-property, dynamic-property { + width:100%; + } + + /deep/ dynamic-property dynamic-property .dynamic-property-row { + border-top:solid #d2d2d2 1px; + } + + /deep/ dynamic-property dynamic-property:first-of-type .dynamic-property-row:not(.with-top-border) { + border-top: none; + } + + properties-value-inner-table { + width: 100%; + } + + .table-header { + font-weight:bold; + border-top: #d2d2d2 solid 1px; + background-color: #eaeaea; + + .valueCol { + justify-content: flex-start; + padding: 5px; + } + } + .table-header, .table-row { + display: flex; + flex-direction:row; + flex: 0 0 auto; + } + + .table-body { + display:flex; + flex-direction: column; + overflow-y:auto; + flex: 1; + + .no-data { + border: #d2d2d2 solid 1px; + border-top:none; + text-align: center; + height: 100%; + padding: 20px; + } + /deep/.selected{ + background-color: #e6f6fb; + color: #009fdb; + } + } + + .table-rows-header { + font-size:16px; + flex:1; + border: #d2d2d2 solid 1px; + border-top:none; + padding: 5px; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + background-color: @tlv_color_v; + } + + .table-row { + &:hover { + background-color:#f8f8f8; cursor:pointer; + } + + &:last-child { + flex: 1 0 auto; + } + .selected-row { + background-color:#e6f6fb; + } + } + .table-cell { + font-size:13px; + flex:1; + border: #d2d2d2 solid 1px; + border-right:none; + border-top:none; + padding: 5px; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + + &:last-child { + border-right:#d2d2d2 solid 1px; + } + &.col1 { + flex: 0 0 300px; + max-width:300px; + + .property-description-icon { + float: right; + margin-top: 4px; + } + } + &.col2 { + flex: 0 0 150px; + max-width:150px; + } + + &.col3 { + flex:0 0 120px; + max-width:120px; + } + + &.valueCol { + flex: 1 0 auto; + min-width: 350px; + display: flex; + justify-content: flex-end; + padding: 0px; + + .value-input { + flex: 1; + max-height: 24px; + border: none; + background-color: inherit; + + &:focus, &:active { + border:none; + outline:none; + } + } + + .delete-btn { + flex: 0 0 auto; + } + + .delete-button-container { + max-height: 24px; + } + + &.inner-table-container { + padding: 0px; + + .delete-button-container { + padding: 3px 5px 0 0 ; + } + } + } + + &.input-value-col { + padding: 5px; + } + + + } + + .filtered { + /deep/ .checkbox-label-content{ + background-color: yellow; + } + } + +} diff --git a/catalog-ui/src/app/ng2/components/properties-table/properties-table.component.ts b/catalog-ui/src/app/ng2/components/properties-table/properties-table.component.ts new file mode 100644 index 0000000000..d5a9b40425 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/properties-table/properties-table.component.ts @@ -0,0 +1,93 @@ +import { Component, Input, Output, EventEmitter, SimpleChanges, ViewChild, ElementRef } from "@angular/core"; +import { trigger, state, style, transition, animate} from '@angular/core'; +import {PropertyFEModel, DerivedFEProperty, DerivedPropertyType, InstanceFePropertiesMap} from "app/models"; +import {PropertiesService} from "../../services/properties.service"; +// import { GroupByPipe } from 'app/ng2/pipes/groupBy.pipe'; +//import {PropertiesValueInnerTableComponent} from "./properties-table/properties-value-inner-table/properties-value-inner-table"; +import { DynamicElementComponent } from 'app/ng2/components/dynamic-element/dynamic-element.component'; +import { KeysPipe } from 'app/ng2/pipes/keys.pipe'; + +@Component({ + selector: 'properties-table', + templateUrl: './properties-table.component.html', + styleUrls: ['./properties-table.component.less'], + animations: [trigger('fadeIn', [transition(':enter', [style({ opacity: '0' }), animate('.3s ease-out', style({ opacity: '1' }))]) ])] +}) +export class PropertiesTableComponent { + + @Input() fePropertiesMap: InstanceFePropertiesMap; + @Input() selectedPropertyId: string; + @Input() displayDeleteButton: boolean; + @Input() propertyNameSearchText:string; + + @Output() valueChanged: EventEmitter<any> = new EventEmitter<any>(); + @Output() selectPropertyRow: EventEmitter<PropertyRowSelectedEvent> = new EventEmitter<PropertyRowSelectedEvent>(); + @Output() updateCheckedPropertyCount: EventEmitter<boolean> = new EventEmitter<boolean>(); + //@Output() selectInstanceRow: EventEmitter<string> = new EventEmitter<string>(); + + feInstancesNames: Array<string>; + + constructor ( private propertiesService:PropertiesService ){ + } + + /** + * Update feInstancesNames when fePropertiesMap: InstanceFePropertiesMap change (after getting response from server) + */ + ngOnChanges(changes: SimpleChanges) { + if (changes['fePropertiesMap']) { + if (changes['fePropertiesMap'].currentValue) { + let keysPipe = new KeysPipe(); + let fiteredArr = keysPipe.transform(changes['fePropertiesMap'].currentValue,[]); + this.feInstancesNames = fiteredArr; + } + } + } + + propValueChanged = (property) => { + !property.isDeclared && this.valueChanged.emit(property); + }; + + // Click on main row (row of propertyFEModel) + onClickPropertyRow = (property:PropertyFEModel, instanceName:string, event?) => { + //event && event.stopPropagation(); + let propertyRowSelectedEvent:PropertyRowSelectedEvent = new PropertyRowSelectedEvent(property, instanceName); + this.selectPropertyRow.emit(propertyRowSelectedEvent); + }; + + // Click on inner row (row of DerivedFEProperty) + onClickPropertyInnerRow = (property:DerivedFEProperty, instanceName:string) => { + let propertyRowSelectedEvent:PropertyRowSelectedEvent = new PropertyRowSelectedEvent(property, instanceName); + this.selectPropertyRow.emit(propertyRowSelectedEvent); + } + + propertyChecked = (prop: PropertyFEModel, childPropName?: string) => { + let isChecked: boolean = (!childPropName)? prop.isSelected : prop.flattenedChildren.find(prop => prop.propertiesName == childPropName).isSelected; + + console.log(isChecked, childPropName, prop); + if (!isChecked) { + this.propertiesService.undoDisableRelatedProperties(prop, childPropName); + } else { + this.propertiesService.disableRelatedProperties(prop, childPropName); + } + this.updateCheckedPropertyCount.emit(isChecked); + } + + putDefaultValueInEmptyProperty = (property:PropertyFEModel):void => { + property.value = property.value || property.defaultValue; + } + + // clickOnInstanceRow = (instanceName:string) =>{ + // this.selectInstanceRow.emit(instanceName); + // }; + +} + +export class PropertyRowSelectedEvent { + propertyModel:PropertyFEModel | DerivedFEProperty; + instanceName:string; + constructor ( propertyModel:PropertyFEModel | DerivedFEProperty, instanceName:string ){ + this.propertyModel = propertyModel; + this.instanceName = instanceName; + } +} + diff --git a/catalog-ui/src/app/ng2/components/properties-table/properties-value-inner-table/properties-value-inner-table.component.html b/catalog-ui/src/app/ng2/components/properties-table/properties-value-inner-table/properties-value-inner-table.component.html new file mode 100644 index 0000000000..61555cac50 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/properties-table/properties-value-inner-table/properties-value-inner-table.component.html @@ -0,0 +1,41 @@ + +<template ngFor let-childProp [ngForOf]="property.childrenProperties" let-i="index"> + <div class="table-inner-row" (click)="onChildPropertySelected(childProp)" [ngClass]="{'selected': selectedPropertyId === childProp.treeNodeId}"> + <div class="table-cell" [ngClass]="{'filtered':childProp.name === propertyNameSearchText}"> + <checkbox [label]="childProp.name" [(checked)]="childProp.isSelected" [disabled]="property.isDisabled"></checkbox> + </div> + <div class="table-cell prop-value"> + <input class="value-input" *ngIf="childProp.isSimpleType" + [(ngModel)]="property.valueObjectRef[childProp.name]" + (change)="putDefaultValueInEmptyChildProperty(childProp);propValueChanged();" + type="childProp.derivedFromSimpleTypeName || childProp.type"/> + <span class="datatype-text" *ngIf="childProp.isDataType">{{ childProp.type | contentAfterLastDot }}</span> + <span *ngIf="!childProp.isSimpleType" (click)="property.updateExpandedChildPropertyId(childProp.treeNodeId)">V</span> + </div> + </div> + <div class="table-inner-row" *ngIf="childProp.type === 'list' && property.expandedChildPropertyId === childProp.treeNodeId"> + <list-property [property]="childProp" + [selectedPropertyId]="selectedPropertyId" + [propertyNameSearchText]="propertyNameSearchText" + (valueChanged)="propValueChanged()" + (selectChildProperty)="onChildPropertySelected($event)"></list-property> + + </div> + <div class="table-inner-row" *ngIf="childProp.type === 'map' && property.expandedChildPropertyId == childProp.treeNodeId"> + <map-property [property]="childProp" + [selectedPropertyId]="selectedPropertyId" + [propertyNameSearchText]="propertyNameSearchText" + (valueChanged)="propValueChanged()" + (selectChildProperty)="onChildPropertySelected($event)"></map-property> + + </div> + <div class="table-inner-row" *ngIf="childProp.isDataType && property.expandedChildPropertyId == childProp.treeNodeId"> + <properties-value-inner-table [property]="childProp" + [selectedPropertyId]="selectedPropertyId" + [propertyNameSearchText]="propertyNameSearchText" + (selectChildProperty)="onChildPropertySelected($event)" + (valueChanged)="propValueChanged()"></properties-value-inner-table> + </div> +</template> + + diff --git a/catalog-ui/src/app/ng2/components/properties-table/properties-value-inner-table/properties-value-inner-table.component.less b/catalog-ui/src/app/ng2/components/properties-table/properties-value-inner-table/properties-value-inner-table.component.less new file mode 100644 index 0000000000..1b7f6d4cd1 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/properties-table/properties-value-inner-table/properties-value-inner-table.component.less @@ -0,0 +1,71 @@ +table { width:100%;} +tr {border-bottom: #d2d2d2 solid 1px;} +tr:last-child { border-bottom:none;} +td { border:none; padding:5px;} +td:first-child { border-right:#d2d2d2 solid 1px;} + +.prop-value{ + span { + position: absolute; + top: 5px; + right: 2px; + + &.delete-span { + right:20px; + } + + &.datatype-text { + position:static; + } + + } +} + +.add-data-row { + padding:5px; + text-align:right; + border-bottom: #d2d2d2 solid 1px; + + &:last-child { + border-bottom:none; + } +} +.table-inner-row { + display:flex; + flex-direction:row; + border-bottom: #d2d2d2 solid 1px; + flex: 0 0 100%; + position:relative; + + &:last-child { + border-bottom:none; + } + + .table-cell { + flex: 0 0 50%; + padding:5px; + position:relative; + text-overflow: ellipsis; + white-space: nowrap; + + &:first-child { + border-right:#d2d2d2 solid 1px; + overflow:hidden; + } + + + } + + .table-inner-container, .inner-table-container { + flex: 0 0 100%; + } +} +/deep/ map-property, /deep/ properties-value-inner-table, /deep/ list-property{ + width:100%; +} + +.filtered { + /deep/ .checkbox-label-content{ + background-color: yellow; + } +} diff --git a/catalog-ui/src/app/ng2/components/properties-table/properties-value-inner-table/properties-value-inner-table.component.ts b/catalog-ui/src/app/ng2/components/properties-table/properties-value-inner-table/properties-value-inner-table.component.ts new file mode 100644 index 0000000000..7d0b219ffe --- /dev/null +++ b/catalog-ui/src/app/ng2/components/properties-table/properties-value-inner-table/properties-value-inner-table.component.ts @@ -0,0 +1,37 @@ +/** + * Created by rc2122 on 4/20/2017. + */ +import {Component, Input, Output, EventEmitter} from "@angular/core"; +import {PropertyFEModel} from "app/models"; +import {PropertiesService} from "../../../services/properties.service"; + +@Component({ + selector: 'properties-value-inner-table', + templateUrl: './properties-value-inner-table.component.html', + styleUrls: ['./properties-value-inner-table.component.less'] +}) +export class PropertiesValueInnerTableComponent { + + @Input() property: PropertyFEModel; + @Input() selectedPropertyId: string; + @Input() propertyNameSearchText:string; + + @Output() selectChildProperty: EventEmitter<any> = new EventEmitter<PropertyFEModel>(); + @Output() valueChanged: EventEmitter<any> = new EventEmitter<any>(); + + constructor ( private propertiesService:PropertiesService){ + } + + + onChildPropertySelected = (property) => { + this.selectChildProperty.emit(property); + }; + + propValueChanged = () => { + this.valueChanged.emit(this.property); + }; + + putDefaultValueInEmptyChildProperty = (childProp:PropertyFEModel):void => { + this.property.valueObjectRef[childProp.name] = this.property.valueObjectRef[childProp.name] || childProp.defaultValue; + } +} diff --git a/catalog-ui/src/app/ng2/components/tooltip/tooltip-content.component.html b/catalog-ui/src/app/ng2/components/tooltip/tooltip-content.component.html new file mode 100644 index 0000000000..1fbf45e39f --- /dev/null +++ b/catalog-ui/src/app/ng2/components/tooltip/tooltip-content.component.html @@ -0,0 +1,12 @@ +<div class="tooltip {{ placement }}" + [style.top]="top + 'px'" + [style.left]="left + 'px'" + [class.in]="isIn" + [class.fade]="isFade" + role="tooltip"> + <div class="tooltip-arrow"></div> + <div class="tooltip-inner"> + <ng-content></ng-content> + {{ content }} + </div> +</div> diff --git a/catalog-ui/src/app/ng2/components/tooltip/tooltip-content.component.less b/catalog-ui/src/app/ng2/components/tooltip/tooltip-content.component.less new file mode 100644 index 0000000000..1ff496f840 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/tooltip/tooltip-content.component.less @@ -0,0 +1,11 @@ +.tooltip-inner { + word-wrap: break-word; + max-width: 300px; +} + +.tooltip.bottom .tooltip-arrow { + border-bottom-color: #000 !important; +} + + + diff --git a/catalog-ui/src/app/ng2/components/tooltip/tooltip-content.component.ts b/catalog-ui/src/app/ng2/components/tooltip/tooltip-content.component.ts new file mode 100644 index 0000000000..6e3e8065bb --- /dev/null +++ b/catalog-ui/src/app/ng2/components/tooltip/tooltip-content.component.ts @@ -0,0 +1,195 @@ +import {Component, AfterViewInit, Input, ElementRef, ChangeDetectorRef} from "@angular/core"; + +@Component +({ + selector: "tooltip-content", + templateUrl: "./tooltip-content.component.html", + styleUrls: ["./tooltip-content.component.less"] +}) + +export class TooltipContentComponent implements AfterViewInit { + + // ------------------------------------------------------------------------- + // Inputs / Outputs + // ------------------------------------------------------------------------- + + @Input() hostElement: HTMLElement; + @Input() content: string; + @Input() placement: "top"|"bottom"|"left"|"right" = "bottom"; + @Input() animation: boolean = true; + + // ------------------------------------------------------------------------- + // Properties + // ------------------------------------------------------------------------- + + top: number = -100000; + left: number = -100000; + isIn: boolean = false; + isFade: boolean = false; + + // ------------------------------------------------------------------------- + // Constructor + // ------------------------------------------------------------------------- + + constructor(private element: ElementRef, + private cdr: ChangeDetectorRef) { + } + + // ------------------------------------------------------------------------- + // Lifecycle callbacks + // ------------------------------------------------------------------------- + + ngAfterViewInit(): void { + this.show(); + this.cdr.detectChanges(); + } + + // ------------------------------------------------------------------------- + // Public Methods + // ------------------------------------------------------------------------- + + show(): void { + if(!this.hostElement) { + return; + } + + const position = this.positionElement(this.hostElement, this.element.nativeElement.children[0], this.placement); + this.top = position.top; + this.left = position.left; + this.isIn = true; + if (this.animation) { + this.isFade = true; + } + } + + hide(): void { + this.top = -100000; + this.left = -100000; + this.isIn = true; + if(this.animation) { + this.isFade = false; + } + } + + // ------------------------------------------------------------------------- + // Private Methods + // ------------------------------------------------------------------------- + + private positionElement(hostElem: HTMLElement, targetElem: HTMLElement, positionStr: string, appendToBody: boolean = false): {top: number, left: number} { + let positionStrParts = positionStr.split("-"); + let pos0 = positionStrParts[0]; + let pos1 = positionStrParts[1] || "center"; + let hostElemPosition = appendToBody ? this.offset(hostElem) : this.position(hostElem); + let targetElemWidth = targetElem.offsetWidth; + let targetElemHeight = targetElem.offsetHeight; + let shiftWidth: any = { + center(): number { + return hostElemPosition.left + hostElemPosition.width / 2 - targetElemWidth / 2; + }, + left(): number { + return hostElemPosition.left; + }, + right(): number { + return hostElemPosition.left + hostElemPosition.width; + } + }; + + let shiftHeight: any = { + center: function (): number { + return hostElemPosition.top + hostElemPosition.height / 2 - targetElemHeight / 2; + }, + top: function (): number { + return hostElemPosition.top; + }, + bottom: function (): number { + return hostElemPosition.top + hostElemPosition.height; + } + } + + let targetElemPosition: {top: number, left: number}; + + switch (pos0) { + case "right": + targetElemPosition = { + top: shiftHeight[pos1](), + left: shiftWidth[pos0]() + }; + break; + + case "left": + targetElemPosition = { + top: shiftHeight[pos1](), + left: hostElemPosition.left - targetElemWidth + }; + break; + + case "bottom": + targetElemPosition = { + top: shiftHeight[pos0](), + left: shiftWidth[pos1]() + }; + break; + + default: + targetElemPosition = { + top: hostElemPosition.top - targetElemHeight, + left: shiftWidth[pos1]() + }; + break; + } + + return targetElemPosition; + } + + + private position(nativeElem: HTMLElement): {width: number, height: number, top: number, left: number} { + let offsetParentCBR = {top: 0, left: 0}; + const elemBCR = this.offset(nativeElem); + const offsetParentElem = this.parentOffsetElem(nativeElem); + if(offsetParentElem !== window.document) { + offsetParentCBR = this.offset(offsetParentElem); + offsetParentCBR.top += offsetParentElem.clientTop - offsetParentElem.scrollTop; + offsetParentCBR.left += offsetParentElem.clientLeft - offsetParentElem.scrollTop; + } + + const boundingClientRect = nativeElem.getBoundingClientRect(); + + return { + width: boundingClientRect.width || nativeElem.offsetWidth, + height: boundingClientRect.height || nativeElem.offsetHeight, + top: elemBCR.top - offsetParentCBR.top, + left: elemBCR.left - offsetParentCBR.left + }; + } + + private offset(nativeElem:any): {width: number, height: number, top: number, left: number} { + const boundingClientRect = nativeElem.getBoundingClientRect(); + return { + width: boundingClientRect.width || nativeElem.offsetWidth, + height: boundingClientRect.height || nativeElem.offsetHeight, + top: boundingClientRect.top + (window.pageYOffset || window.document.documentElement.scrollTop), + left: boundingClientRect.left + (window.pageXOffset || window.document.documentElement.scrollLeft) + }; + } + + private getStyle(nativeElem: HTMLElement, cssProperty: string): string { + if(window.getComputedStyle) { + return (window.getComputedStyle(nativeElem) as any)[cssProperty]; + } + + return (nativeElem.style as any)[cssProperty]; + } + + private isStaticPositioned(nativeElem: HTMLElement): boolean { + return (this.getStyle(nativeElem, "position") || "static") === "static"; + } + + private parentOffsetElem(nativeElem: HTMLElement): any { + let offsetParent: any = nativeElem.offsetParent || window.document; + while (offsetParent && offsetParent !== window.document && this.isStaticPositioned(offsetParent)) { + offsetParent = offsetParent.offsetParent; + } + + return offsetParent || window.document; + } +} diff --git a/catalog-ui/src/app/ng2/components/tooltip/tooltip.component.ts b/catalog-ui/src/app/ng2/components/tooltip/tooltip.component.ts new file mode 100644 index 0000000000..e98b69003e --- /dev/null +++ b/catalog-ui/src/app/ng2/components/tooltip/tooltip.component.ts @@ -0,0 +1,81 @@ +import { + Directive, ComponentRef, ViewContainerRef, ComponentFactoryResolver, Input, HostListener +} from "@angular/core"; +import {TooltipContentComponent} from "./tooltip-content.component"; + +@Directive ({ + selector: "[tooltip]" +}) +export class TooltipComponent { + + // ------------------------------------------------------------------------- + // Properties + // ------------------------------------------------------------------------- + + private tooltip: ComponentRef<TooltipContentComponent>; + private visible: boolean; + + // ------------------------------------------------------------------------- + // Constructor + // ------------------------------------------------------------------------- + + constructor(private viewContainerRef: ViewContainerRef, + private resolver: ComponentFactoryResolver) { + } + + // ------------------------------------------------------------------------- + // Inputs / Outputs + // ------------------------------------------------------------------------- + + @Input("tooltip") content: string|TooltipContentComponent; + @Input() tooltipDisabled: boolean; + @Input() tooltipAnimation: boolean = true; + @Input() tooltipPlacement: "top"|"bottom"|"left"|"right" = "bottom"; + + // ------------------------------------------------------------------------- + // Public Methods + // ------------------------------------------------------------------------- + + @HostListener("mouseenter") + show(): void { + if(this.tooltipDisabled || this.visible || this.content === "") { + return; + } + + this.visible = true; + if (typeof this.content === "string") { + const factory = this.resolver.resolveComponentFactory(TooltipContentComponent); + if (!this.visible) { + return; + } + + this.tooltip = this.viewContainerRef.createComponent(factory); + this.tooltip.instance.hostElement = this.viewContainerRef.element.nativeElement; + this.tooltip.instance.content = this.content as string; + this.tooltip.instance.placement = this.tooltipPlacement; + this.tooltip.instance.animation = this.tooltipAnimation; + } else { + const tooltip = this.content as TooltipContentComponent; + tooltip.hostElement = this.viewContainerRef.element.nativeElement; + tooltip.placement = this.tooltipPlacement; + tooltip.animation = this.tooltipAnimation; + tooltip.show(); + } + } + + @HostListener("mouseleave") + hide(): void { + if (!this.visible) { + return; + } + + this.visible = false; + if (this.tooltip) { + this.tooltip.destroy(); + } + if (this.content instanceof TooltipContentComponent) { + (this.content as TooltipContentComponent).hide(); + } + } +} + diff --git a/catalog-ui/src/app/ng2/components/tooltip/tooltip.module.ts b/catalog-ui/src/app/ng2/components/tooltip/tooltip.module.ts new file mode 100644 index 0000000000..69976da6af --- /dev/null +++ b/catalog-ui/src/app/ng2/components/tooltip/tooltip.module.ts @@ -0,0 +1,25 @@ +import {NgModule} from "@angular/core"; +import {TooltipContentComponent} from "./tooltip-content.component"; +import {TooltipComponent} from "./tooltip.component"; +import {CommonModule} from "@angular/common"; + +@NgModule({ + declarations: [ + TooltipComponent, + TooltipContentComponent, + ], + imports: [ + CommonModule + ], + exports: [ + TooltipComponent, + TooltipContentComponent, + ], + entryComponents: [ + TooltipContentComponent + ], + providers: [] +}) +export class TooltipModule { + +} diff --git a/catalog-ui/src/app/ng2/pages/page404/page404.component.html b/catalog-ui/src/app/ng2/pages/page404/page404.component.html new file mode 100644 index 0000000000..d488587154 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/page404/page404.component.html @@ -0,0 +1,3 @@ +<div class="page404"> + Page404 +</div> diff --git a/catalog-ui/src/app/ng2/pages/page404/page404.component.less b/catalog-ui/src/app/ng2/pages/page404/page404.component.less new file mode 100644 index 0000000000..2672f22f27 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/page404/page404.component.less @@ -0,0 +1,4 @@ +.page404 { + font-size: 100px; + color: #ff0000; +} diff --git a/catalog-ui/src/app/ng2/pages/page404/page404.component.ts b/catalog-ui/src/app/ng2/pages/page404/page404.component.ts new file mode 100644 index 0000000000..a3baf4fd02 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/page404/page404.component.ts @@ -0,0 +1,9 @@ +import { Component, Inject } from '@angular/core'; + +@Component({ + templateUrl: './page404.component.html', + styleUrls: ['./page404.component.less'] +}) +export class PageNotFoundComponent { + +} diff --git a/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.module.ts b/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.module.ts new file mode 100644 index 0000000000..6ef3e57678 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.module.ts @@ -0,0 +1,72 @@ +import { NgModule } from "@angular/core"; +import { PropertiesAssignmentComponent } from "./properties-assignment.page.component"; +import { HierarchyNavigationComponent } from "./../../components/hierarchy-navigtion/hierarchy-navigation.component"; +import { BrowserModule } from "@angular/platform-browser"; +import { FormsModule } from "@angular/forms"; +import { HttpModule } from "@angular/http"; +import { TabModule } from '../../shared/tabs/tabs.module'; +import { CheckboxModule} from '../../shared/checkbox/checkbox.module'; +import { PropertiesTableComponent } from '../../components/properties-table/properties-table.component'; +import { InputsTableComponent } from '../../components/inputs-table/inputs-table.component'; +import { ContentAfterLastDotPipe } from "../../pipes/contentAfterLastDot.pipe"; +import { SearchFilterPipe } from "../../pipes/searchFilter.pipe"; +import { FilterChildPropertiesPipe } from "../../pipes/filterChildProperties.pipe"; +import { DataTypeService } from './../../services/data-type.service'; +import { PropertiesService } from './../../services/properties.service'; +import { PropertiesUtils } from './properties.utils'; +import { PostsService } from "../../services/posts.service"; +import { PropertiesValueInnerTableComponent } from "./../../components/properties-table/properties-value-inner-table/properties-value-inner-table.component"; +import { ListPropertyComponent } from "./../../components/properties-table/list-property/list-property.component"; +import { MapPropertyComponent } from "./../../components/properties-table/map-property/map-property.component"; +import { DynamicElementModule } from 'app/ng2/components/dynamic-element/dynamic-element.module'; +import { DynamicPropertyComponent } from './../../components/properties-table/dynamic-property/dynamic-property.component'; +import { DerivedPropertyComponent } from './../../components/properties-table/derived-property/derived-property.component'; +// import {PopoverContentComponent} from "../../components/popover/popover-content.component" +// import {PopoverComponent} from "../../components/popover/popover.component" +import { PopoverModule } from "../../components/popover/popover.module" +import { FilterPropertiesAssignmentComponent } from "./../../components/filter-properties-assignment/filter-properties-assignment.component"; +import { GroupByPipe } from 'app/ng2/pipes/groupBy.pipe'; +import { KeysPipe } from 'app/ng2/pipes/keys.pipe'; +import {TooltipModule} from "../../components/tooltip/tooltip.module"; + +@NgModule({ + declarations: [ + PropertiesAssignmentComponent, + PropertiesTableComponent, + InputsTableComponent, + ContentAfterLastDotPipe, + GroupByPipe, + KeysPipe, + SearchFilterPipe, + FilterChildPropertiesPipe, + HierarchyNavigationComponent, + PropertiesValueInnerTableComponent, + ListPropertyComponent, + MapPropertyComponent, + DerivedPropertyComponent, + DynamicPropertyComponent, + // PopoverContentComponent, + // PopoverComponent, + FilterPropertiesAssignmentComponent + ], + imports: [ + BrowserModule, + FormsModule, + HttpModule, + TabModule, + CheckboxModule, + DynamicElementModule, + PopoverModule, + TooltipModule + ], + entryComponents: [PropertiesAssignmentComponent], + exports: [ + PropertiesAssignmentComponent + // PopoverContentComponent, + // PopoverComponent + ], + providers: [PropertiesService, PropertiesUtils, DataTypeService, PostsService, ContentAfterLastDotPipe, GroupByPipe, KeysPipe] +}) +export class PropertiesAssignmentModule { + +} diff --git a/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.html b/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.html new file mode 100644 index 0000000000..d1b671cff2 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.html @@ -0,0 +1,57 @@ +<div class="properties-assignment-page"> + <div class="main-content"> + <div class="left-column"> + <tabs #propertyInputTabs tabStyle="round-tabs" (tabChanged)="tabChanged($event)" [hideIndicationOnTabChange]="true"> + <tab tabTitle="Properties"> + <properties-table class="properties-table" + [fePropertiesMap]="instanceFePropertiesMap | searchFilter:'name':searchQuery" + [selectedPropertyId]="selectedFlatProperty.uniqueId" + [propertyNameSearchText]="searchPropertyName" + (valueChanged)="propertyValueChanged($event)" + (propertySelected)="propertySelected($event)" + (selectPropertyRow)="selectPropertyRow($event)" + (selectChildProperty)="selectChildProperty($event)" + (updateCheckedPropertyCount)="updateCheckedPropertyCount($event)" + (selectInstanceRow)="selectInstanceRow($event)"> + </properties-table> + </tab> + <tab tabTitle="Inputs"> + <inputs-table class="properties-table" [inputs]="inputs | searchFilter:'name':searchQuery" (deleteInput)="deleteInput($event)" (inputValueChanged)="inputValueChanged($event)"></inputs-table> + </tab> + </tabs> + <div class="header"> + <div class="search-filter-container"> + <input type="text" class="search-box" placeholder="Search" [(ngModel)]="searchQuery" /> + <span class="sprite search-icon"></span> + <filter-properties-assignment *ngIf="!hideAdvanceSearch" #advanceSearch class="advance-search" [componentType]="component.componentType" (searchProperties)="searchPropertiesInstances($event)"></filter-properties-assignment> + <span *ngIf="displayClearSearch && !hideAdvanceSearch" (click)="clickOnClearSearch()" class="clear-filter">Clear All</span> + </div> + <button class="tlv-btn blue declare-button" [disabled]="!checkedPropertiesCount" (click)="declareProperties()">Declare</button> + </div> + </div> + <div class="right-column gray-border"> + <tabs #hierarchyNavTabs tabStyle="simple-tabs"> + <tab tabTitle="Composition"> + <div class="hierarchy-nav-container"> + <div class="hierarchy-header">{{component.name}}</div> + <div *ngIf="!instancesNavigationData || instancesNavigationData.length === 0">No data to display</div> + <hierarchy-navigation class="hierarchy-nav" (updateSelected)="onInstanceSelectedUpdate($event)" + [displayData]="instancesNavigationData" + [selectedItem]="selectedInstanceData.uniqueId" + [displayOptions]="hierarchyInstancesDisplayOptions"></hierarchy-navigation> + </div> + </tab> + <tab tabTitle="Property Structure"> + <div class="hierarchy-nav-container"> + <div class="hierarchy-header">{{propertyStructureHeader || selectedFlatProperty.name || "No Property Selected"}}</div> + <div *ngIf="!propertiesNavigationData || propertiesNavigationData.length === 0">No data to display</div> + <hierarchy-navigation class="hierarchy-nav" (updateSelected)="onPropertySelectedUpdate($event)" + [displayData]="propertiesNavigationData" + [selectedItem]="selectedFlatProperty.uniqueId" + [displayOptions]="hierarchyPropertiesDisplayOptions"></hierarchy-navigation> + </div> + </tab> + </tabs> + </div> + </div> +</div> diff --git a/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.less b/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.less new file mode 100644 index 0000000000..f7a62bbcb5 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.less @@ -0,0 +1,158 @@ +@import '../../../../assets/styles/variables'; +@ng2-shadow-gray: #f8f8f8; +@ng2-light-gray: #eaeaea; +@ng2-medium-gray: #d2d2d2; +@ng2-med-dark-gray: #999999; +@ng2-dark-gray: #5a5a5a; +@ng2-shadow-blue: #e6f6fb; +@ng2-bold-blue: #009fdb; +@ng2-success-green:#4ca90c; +@ng2-title-font-size:16px; +@ng2-text-font-size: 14px; + +:host { display:block; height: 100%; } +/deep/ tabs {display:flex; flex-direction:column; height:100%; } + +.properties-assignment-page { + height: 100%; + + .main-content { + display:flex; + flex-direction:row; + height: 100%; + } + + .left-column { + flex: 1 0 500px; + position: relative; + margin: 0 0 1em 0; + + /deep/ .tabs { + width:25%; + text-align:center; + + } + + /deep/ .tab { + padding: 10px .5em; + + &.active { + color:#009fdb; + border-color: #d2d2d2; + border-top: solid 4px #009fdb; + background-color: white; + padding-top:7px; + } + } + + .header { + position:absolute; + top:0; + right:0; + min-width:200px; + } + + .search-filter-container{ + position: relative; + right: 164px; + } + + .search-box { + border: 1px solid @ng2-medium-gray; + border-radius: 4px; + height: 32px; + margin: 0; + padding: 2px 20px 4px 10px; + outline: none; + font-style: italic; + color:@ng2-med-dark-gray; + margin-right:10px; + + &::-moz-placeholder { color:@ng2-med-dark-gray;} + &::-webkit-input-placeholder{ color:@ng2-med-dark-gray;} + } + .search-icon { + background-position: -48px -3137px; + width: 14px; + height: 14px; + position: relative; + right: 34px; + top: 4px; + } + .advance-search{ + position: relative; + right: 22px; + } + .clear-filter{ + cursor: pointer; + color: @main_color_c; + font-family: @font-omnes-medium-italic; + text-decoration: underline; + position: relative; + top: 4px; + right: 16px; + } + + .declare-button{ + position: absolute; + top: 0; + right: 0; + } + } + + .right-column { + display:flex; + flex:0 0 350px; + flex-direction:column; + margin: 3em 0 1em 1em; + padding: 10px; + overflow:auto; + + /deep/ .tabs { + width: 33%; + } + + /deep/ .tab { + padding: 0.5em 1em 0 1em; + white-space: nowrap; + font-size: 13px; + } + } + + .hierarchy-tabs { + flex: 0 0 40px; + } + + .gray-border { + border: 1px solid #ddd; + } + + + .hierarchy-nav-container { + flex:1; + overflow: auto; + flex-direction: column; + height: 100%; + } + + .hierarchy-header { + height:30px; + line-height: 2.5em; + display: flex; + width: 100%; + padding-left: 14px; + font-weight: bold; + text-align: left; + background-color: @ng2-light-gray; + font-size: 13px; + } + + .hierarchy-nav { + display: grid; + margin-top: 1em; + margin-left: 1em; + font-size: 12px; + padding-top: 1em; + } +} + diff --git a/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.ts b/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.ts new file mode 100644 index 0000000000..299615b122 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.ts @@ -0,0 +1,344 @@ +import {Component, ViewChild, ElementRef, Renderer, Inject} from "@angular/core"; +import {PostsService} from "../../services/posts.service"; +import {PropertiesService, SimpleFlatProperty} from "../../services/properties.service"; +import { PropertiesUtils } from './properties.utils'; +import { PropertyFEModel, InstanceFePropertiesMap, InstanceBePropertiesMap, InstancePropertiesAPIMap, Component as ComponentData, FilterPropertiesAssignmentData } from "app/models"; +import { PROPERTY_TYPES, ResourceType } from "app/utils"; +import property = require("lodash/property"); +import {ComponentServiceNg2} from "../../services/component-services/component.service"; +import {ComponentInstanceServiceNg2} from "../../services/component-instance-services/component-instance.service" +import {InputFEModel, ComponentInstance, PropertyBEModel, DerivedFEProperty, ResourceInstance} from "app/models"; +import {HierarchyDisplayOptions} from "../../components/hierarchy-navigtion/hierarchy-display-options" +import {PropertyRowSelectedEvent} from "./../../components/properties-table/properties-table.component"; +import { KeysPipe } from 'app/ng2/pipes/keys.pipe'; +import {FilterPropertiesAssignmentComponent} from "../../components/filter-properties-assignment/filter-properties-assignment.component"; + +@Component({ + templateUrl: './properties-assignment.page.component.html', + styleUrls: ['./properties-assignment.page.component.less'] +}) +export class PropertiesAssignmentComponent { + title = "Properties & Inputs"; + + component:ComponentData; + + propertiesNavigationData = []; + instancesNavigationData = []; + + instanceFePropertiesMap:InstanceFePropertiesMap; + inputs: Array<InputFEModel> = []; + instances: Array<ComponentInstance> = []; + searchQuery: string; + propertyStructureHeader: string + + selectedFlatProperty: SimpleFlatProperty = new SimpleFlatProperty(); + selectedInstanceType: string; + selectedInstanceData: ComponentInstance = new ComponentInstance(); + checkedPropertiesCount: number = 0; + + hierarchyPropertiesDisplayOptions:HierarchyDisplayOptions = new HierarchyDisplayOptions('uniqueId', 'name', 'childrens'); + hierarchyInstancesDisplayOptions:HierarchyDisplayOptions = new HierarchyDisplayOptions('uniqueId', 'name'); + displayClearSearch = false; + searchPropertyName:string; + hideAdvanceSearch:boolean; + + @ViewChild('hierarchyNavTabs') hierarchyNavTabs: ElementRef; + @ViewChild('propertyInputTabs') propertyInputTabs: ElementRef; + @ViewChild('advanceSearch') advanceSearch: FilterPropertiesAssignmentComponent; + + constructor(private propertiesService:PropertiesService, + private propertiesUtils:PropertiesUtils, + private componentServiceNg2:ComponentServiceNg2, + private componentInstanceServiceNg2:ComponentInstanceServiceNg2, + @Inject("$stateParams") _stateParams, + private renderer: Renderer) { + + this.instanceFePropertiesMap = new InstanceFePropertiesMap(); + + /* This is the way you can access the component data, please do not use any data except metadata, all other data should be received from the new api calls on the first time + than if the data is already exist, no need to call the api again - Ask orit if you have any questions*/ + this.component = _stateParams.component; + } + + ngOnInit() { + console.log("==>" + this.constructor.name + ": ngOnInit"); + this.componentServiceNg2 + .getComponentResourceInstances(this.component) + .subscribe(response => { + this.instances = response.componentInstances; + + _.forEach(this.instances, (instance) => { + this.instancesNavigationData.push(instance); + }); + + this.selectFirstInstanceByDefault(); + }); + + this.componentServiceNg2 + .getComponentInputs(this.component) + .subscribe(response => { + _.forEach(response.inputs, (input: PropertyBEModel) => { + this.inputs.push(new InputFEModel(input)); + }); + }) + } + + selectFirstInstanceByDefault = () => { + if (this.instancesNavigationData[0] !== undefined) { + this.onInstanceSelectedUpdate(this.instancesNavigationData[0]); + } + } + + propertyValueChanged = (event) => { + console.log("==>" + this.constructor.name + ": propertyValueChanged " + event); + + if(this.selectedInstanceData.originType === ResourceType.VF) { + console.log("I want to update input value on the resource instance"); + let inputToUpdate = new PropertyBEModel(event); + this.componentInstanceServiceNg2 + .updateInstanceInput(this.component, this.selectedInstanceData.uniqueId, inputToUpdate) + .subscribe(response => { + console.log("update resource instance input and got this response: ",response); + }) + } + else { + // Copying the actual value from the object ref into the value if it's from a complex type + if(event.isDataType) { + event.value = JSON.stringify(event.valueObjectRef); + } + let propertyBe = new PropertyBEModel(event); + this.componentInstanceServiceNg2 + .updateInstanceProperty(this.component, this.selectedInstanceData.uniqueId, propertyBe) + .subscribe(response => { + console.log("updated resource instance property and got this response: ",response); + }); + console.log(event); + } + + }; + + inputValueChanged = (event) => { + console.log("==>" + this.constructor.name + ": inputValueChanged"); + let inputToUpdate = new PropertyBEModel(event); + + this.componentServiceNg2 + .updateComponentInput(this.component, inputToUpdate) + .subscribe(response => { + console.log("updated the component input and got this response: ", response); + }) + }; + + declareProperties = ():void => { + console.log("==>" + this.constructor.name + ": declareProperties"); + + let selectedProperties: InstanceBePropertiesMap = new InstanceBePropertiesMap(); + + let instancesNames = new KeysPipe().transform(this.instanceFePropertiesMap,[]); + angular.forEach(instancesNames, (instanceName:string):void=>{ + selectedProperties[instanceName] = this.propertiesService.getCheckedProperties(this.instanceFePropertiesMap[instanceName]); + //selectedProperties[this.selectedInstanceData.uniqueId] = this.propertiesService.getCheckedProperties(this.properties); + }); + + let inputsToCreate: InstancePropertiesAPIMap; + if (this.selectedInstanceType !== ResourceType.VF) { + inputsToCreate = new InstancePropertiesAPIMap(null, selectedProperties); + } else { + inputsToCreate = new InstancePropertiesAPIMap(selectedProperties, null); + } + this.componentServiceNg2 + .createInput(this.component, inputsToCreate) + .subscribe(response => { + this.setInputTabIndication(response.length); + this.checkedPropertiesCount = 0; + _.forEach(response, (input: PropertyBEModel) => { this.inputs.push(new InputFEModel(input)); }); + this.findAndDisableDeclaredProperties(); + }); + } + + //TODO: Can remove? no one use it + // getSelectedFEProps = (): Array<PropertyFEModel> => { + // return this.properties.filter(prop => prop.isSelected && !prop.isDeclared); + // } + + onInstanceSelectedUpdate = (resourceInstance: ResourceInstance) => { + console.log("==>" + this.constructor.name + ": onInstanceSelectedUpdate"); + let instanceBePropertiesMap: InstanceBePropertiesMap = new InstanceBePropertiesMap(); + this.selectedInstanceData = resourceInstance; + this.selectedInstanceType = resourceInstance.originType; + + if(resourceInstance.originType === ResourceType.VF) { + this.componentInstanceServiceNg2 + .getComponentInstanceInputs(this.component, resourceInstance) + .subscribe(response => { + instanceBePropertiesMap[resourceInstance.uniqueId] = response; + this.processInstancePropertiesResponse(instanceBePropertiesMap); + }); + } else { + this.componentInstanceServiceNg2 + .getComponentInstanceProperties(this.component, resourceInstance.uniqueId) + .subscribe(response => { + instanceBePropertiesMap[resourceInstance.uniqueId] = response; + this.processInstancePropertiesResponse(instanceBePropertiesMap); + }); + } + + if( this.searchPropertyName ){ + this.clearSearch(); + } + }; + + /** + * Entry point handling response from server + */ + processInstancePropertiesResponse = (instanceBePropertiesMap:InstanceBePropertiesMap) => { + this.instanceFePropertiesMap = this.propertiesUtils.convertPropertiesMapToFEAndCreateChildren(instanceBePropertiesMap); //create flattened children + this.findAndDisableDeclaredProperties(); //disable properties or flattened children that are declared + this.checkedPropertiesCount = 0; + }; + + /** + * Handle select node in navigation area, and select the row in table + */ + onPropertySelectedUpdate = ($event) => { + console.log("==>" + this.constructor.name + ": onPropertySelectedUpdate"); + this.selectedFlatProperty = $event; + let parentProperty:PropertyFEModel = this.propertiesService.getParentPropertyFEModelFromPath(this.instanceFePropertiesMap[this.selectedFlatProperty.instanceName], this.selectedFlatProperty.path); + parentProperty.expandedChildPropertyId = this.selectedFlatProperty.path; + }; + + /** + * When user select row in table, this will prepare the hirarchy object for the tree. + */ + selectPropertyRow = (propertyRowSelectedEvent:PropertyRowSelectedEvent) => { + console.log("==>" + this.constructor.name + ": selectPropertyRow " + propertyRowSelectedEvent.propertyModel.name); + let property = propertyRowSelectedEvent.propertyModel; + let instanceName = propertyRowSelectedEvent.instanceName; + this.propertyStructureHeader = null; + + // Build hirarchy tree for the navigation and update propertiesNavigationData with it. + if(this.selectedInstanceData.originType !== ResourceType.VF) { + let simpleFlatProperty:Array<SimpleFlatProperty>; + if (property instanceof PropertyFEModel) { + simpleFlatProperty = this.propertiesService.getSimplePropertiesTree(property, instanceName); + } else if (property instanceof DerivedFEProperty) { + // Need to find parent PropertyFEModel + let parentPropertyFEModel:PropertyFEModel = _.find(this.instanceFePropertiesMap[instanceName], (tmpFeProperty):boolean => { + return property.propertiesName.indexOf(tmpFeProperty.name)===0; + }); + simpleFlatProperty = this.propertiesService.getSimplePropertiesTree(parentPropertyFEModel, instanceName); + } + this.propertiesNavigationData = simpleFlatProperty; + } + + // Updatet the header in the navigation tree with property name. + if(property instanceof DerivedFEProperty) { + this.propertyStructureHeader = (property.propertiesName.split('#'))[0]; + } + + // Set selected property in table + this.selectedFlatProperty = new SimpleFlatProperty(property.uniqueId, null, property.name, null); + this.renderer.invokeElementMethod(this.hierarchyNavTabs, 'triggerTabChange', ['Property Structure']); + }; + + //TODO: Can remove? no one use it + // findParentProperty = (childProp: DerivedFEProperty): PropertyFEModel => { + // return this.properties.find(prop => prop.name == childProp.propertiesName.substring(0, childProp.propertiesName.indexOf("#"))); + // } + + //used for declare button, to keep count of newly checked properties (and ignore declared properties) + updateCheckedPropertyCount = (increment: boolean):void => { + this.checkedPropertiesCount += (increment) ? 1 : -1; + console.log("CheckedProperties count is now.... " + this.checkedPropertiesCount); + } + + selectInstanceRow = ($event) => {//get instance name + this.selectedInstanceData = _.find(this.instancesNavigationData, (instance:ComponentInstance) => { + return instance.name == $event; + }); + this.renderer.invokeElementMethod( + this.hierarchyNavTabs, 'triggerTabChange', ['Composition']); + } + + tabChanged = (event) => { + console.log("==>" + this.constructor.name + ": tabChanged " + event); + this.hideAdvanceSearch = event.title !== "Properties"; + this.searchQuery = ''; + }; + + deleteInput = (input:InputFEModel) => { + console.log("==>" + this.constructor.name + ": deleteInput"); + let inputToDelete = new PropertyBEModel(input); + + this.componentServiceNg2 + .deleteInput(this.component, inputToDelete) + .subscribe(response => { + this.inputs = this.inputs.filter(input => input.uniqueId !== response.uniqueId); + let propToEnable: PropertyFEModel = this.instanceFePropertiesMap[input.instanceName].find(prop => prop.name == input.propertyName); + propToEnable.setNonDeclared(response.inputPath); + this.propertiesService.undoDisableRelatedProperties(propToEnable, response.inputPath); + //this.propertiesService.initValueObjectRef(propToEnable); //TODO:speak to BE about value returned by server + }); + } + + setInputTabIndication = (numInputs: number): void => { + this.renderer.invokeElementMethod( this.propertyInputTabs, 'setTabIndication', ['Inputs', numInputs]); + } + + findAndDisableDeclaredProperties = () => { + this.inputs.filter(input => input.instanceName === this.selectedInstanceData.normalizedName).forEach(input => { + let prop: PropertyFEModel = this.instanceFePropertiesMap[this.selectedInstanceData.uniqueId].find(prop => prop.name === input.propertyName); + if (prop) { + prop.setAsDeclared(input.inputPath); //if a path was sent, its a child prop. this param is optional + this.propertiesService.disableRelatedProperties(prop, input.inputPath) + //this.propertiesService.initValueObjectRef(prop); + } + }); + }; + + searchPropertiesInstances = (filterData:FilterPropertiesAssignmentData) => { + //let filteredProperties = this.componentServiceNg2.filterComponentInstanceProperties(this.component, filterData); + let instanceBePropertiesMap:InstanceBePropertiesMap; + this.componentServiceNg2 + .filterComponentInstanceProperties(this.component, filterData) + .subscribe(response => { + //instanceBePropertiesMap=response; + //console.log("================filter results============="); + //console.table(instanceBePropertiesMap); + this.processInstancePropertiesResponse(response); + + + //this.properties = []; + // _.forEach(instanceBePropertiesMap, (InstanceProperties:Array<PropertyBEModel>, instanceName:string) => { + // this.properties = this.properties.concat(this.propertiesService.convertPropertiesToFEAndCreateChildren(InstanceProperties, instanceName)); + // }); + + + // this.instancesNavigationData = _.filter(this.instancesNavigationData, (instance:ComponentInstance) => { + // return instanceBePropertiesMap[instance.name]; + // }); + + // this.hierarchyPropertiesDisplayOptions.searchText = filterData.propertyName;//mark results in tree + this.searchPropertyName = filterData.propertyName;//mark in table + this.renderer.invokeElementMethod(this.hierarchyNavTabs, 'triggerTabChange', ['Composition']); + this.propertiesNavigationData = []; + this.displayClearSearch = true; + }); + + }; + + clearSearch = () => { + this.instancesNavigationData = this.instances; + this.searchPropertyName = ""; + this.hierarchyPropertiesDisplayOptions.searchText = ""; + this.displayClearSearch = false; + this.advanceSearch.clearAll(); + } + + clickOnClearSearch = () => { + this.clearSearch(); + this.selectFirstInstanceByDefault(); + this.renderer.invokeElementMethod( + this.hierarchyNavTabs, 'triggerTabChange', ['Composition']); + } + +} diff --git a/catalog-ui/src/app/ng2/pages/properties-assignment/properties.utils.ts b/catalog-ui/src/app/ng2/pages/properties-assignment/properties.utils.ts new file mode 100644 index 0000000000..79769e21b5 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/properties-assignment/properties.utils.ts @@ -0,0 +1,78 @@ +import { Injectable } from '@angular/core'; +import { DataTypeModel, PropertyFEModel, PropertyBEModel, InstanceBePropertiesMap, InstanceFePropertiesMap, SchemaProperty, DerivedFEProperty, DerivedFEPropertyMap, DerivedPropertyType, InputFEModel} from "app/models"; +import { DataTypeService } from "app/ng2/services/data-type.service"; +import { PROPERTY_TYPES } from "app/utils"; +import { UUID } from "angular2-uuid"; + +@Injectable() +export class PropertiesUtils { + + constructor(private dataTypeService:DataTypeService) {} + + /** + * Entry point when getting properties from server + * Returning InstanceFePropertiesMap + */ + public convertPropertiesMapToFEAndCreateChildren = (instancePropertiesMap:InstanceBePropertiesMap): InstanceFePropertiesMap => { + let instanceFePropertiesMap:InstanceFePropertiesMap = new InstanceFePropertiesMap(); + angular.forEach(instancePropertiesMap, (properties:Array<PropertyBEModel>, instanceName:string) => { + instanceFePropertiesMap[instanceName] = this.convertPropertiesToFEAndCreateChildren(properties); + }); + return instanceFePropertiesMap; + } + + /** + * Convert the properties Array<PropertyBEModel> to Array<PropertyFEModel> + */ + private convertPropertiesToFEAndCreateChildren = (properties: Array<PropertyBEModel>): Array<PropertyFEModel> => { + let propertyFeArray: Array<PropertyFEModel> = []; + _.forEach(properties, (property: PropertyBEModel, index: number) => { + //console.log("=======" + property.name + "========"); + if(!this.dataTypeService.getDataTypeByTypeName(property.type)){ // if type not exist in data types remove property from list + console.log("ERROR: missing type " + property.type + " in dataTypes , of property ",property); + return; + } + let propertyFe:PropertyFEModel = new PropertyFEModel(property); + if (propertyFe.isDataType) { //prop is not simple, list, or map. Need to create children. + let tempProps: Array<DerivedFEProperty> = []; + let dataTypeObj: DataTypeModel = this.dataTypeService.getDataTypeByTypeName(propertyFe.type); + this.dataTypeService.getDerivedDataTypeProperties(dataTypeObj, tempProps, propertyFe.name); + propertyFe.flattenedChildren = tempProps; + propertyFe.expandedChildPropertyId = propertyFe.name; + this.initValueObjectRef(propertyFe); + } + propertyFeArray.push(propertyFe); + + + }); + return propertyFeArray; + + //TODO: need to look at schema to create the nested properties for the following cases: + // 1 - when value is populated for a complex type (list or map) + // 2 - when adding new entries to a complex type (eg. adding a new entry to a list of AddressRequirements) + } + + public initValueObjectRef = (property: PropertyFEModel): void => { + //console.log("Property " + property.name + " has value: " + property.value); + if (!property.isDataType || property.isDeclared) { //if property is declared, it gets a simple input instead. List and map values and pseudo-children will be handled in property component + property.value = property.value || property.defaultValue; + } else if (property.value){ //we have a complex property with a value. Lets parse property.value and populate our flattened children with those values + this.assignValuesRecursively(JSON.parse(property.value), property.flattenedChildren, property.name); + } + } + + public assignValuesRecursively = (valueJSON: any, derivedPropArray: Array<DerivedFEProperty>, propName: string) => { + if (valueJSON && Object.keys(valueJSON)) { + Object.keys(valueJSON).forEach(valueKey => { + let childProp: DerivedFEProperty = derivedPropArray.find(prop => prop.propertiesName == propName + "#" + valueKey); + if (!childProp) return; + if (childProp.isDeclared || (childProp.derivedDataType != DerivedPropertyType.COMPLEX && !_.isEmpty(valueJSON[valueKey]))) { + childProp.value = (typeof valueJSON[valueKey] === 'object')? JSON.stringify(valueJSON[valueKey]) : valueJSON[valueKey]; + } else { + this.assignValuesRecursively(valueJSON[valueKey], derivedPropArray, childProp.propertiesName) + } + }); + } + } + +} diff --git a/catalog-ui/src/app/ng2/pipes/contentAfterLastDot.pipe.ts b/catalog-ui/src/app/ng2/pipes/contentAfterLastDot.pipe.ts new file mode 100644 index 0000000000..68fba92b77 --- /dev/null +++ b/catalog-ui/src/app/ng2/pipes/contentAfterLastDot.pipe.ts @@ -0,0 +1,8 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ name: 'contentAfterLastDot' }) +export class ContentAfterLastDotPipe implements PipeTransform { + transform(value:string): string { + return value.split('.').pop(); + } +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pipes/filterChildProperties.pipe.ts b/catalog-ui/src/app/ng2/pipes/filterChildProperties.pipe.ts new file mode 100644 index 0000000000..d2eaef0391 --- /dev/null +++ b/catalog-ui/src/app/ng2/pipes/filterChildProperties.pipe.ts @@ -0,0 +1,18 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { DerivedFEProperty } from 'app/models'; + +@Pipe({ + name: 'filterChildProperties', +}) +export class FilterChildPropertiesPipe implements PipeTransform { + public transform(childProperties: Array<DerivedFEProperty>, parentId: string) { + if (!parentId || !childProperties) return childProperties; + + let validParents: Array<string> = [parentId]; + while (parentId.lastIndexOf('#') > 0) { + parentId = parentId.substring(0, parentId.lastIndexOf('#')); + validParents.push(parentId); + } + return childProperties.filter(derivedProp => validParents.indexOf(derivedProp.parentName) > -1); + } +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pipes/groupBy.pipe.ts b/catalog-ui/src/app/ng2/pipes/groupBy.pipe.ts new file mode 100644 index 0000000000..17ccc0ca75 --- /dev/null +++ b/catalog-ui/src/app/ng2/pipes/groupBy.pipe.ts @@ -0,0 +1,19 @@ +/** + * Created by rc2122 on 5/17/2017. + */ +import {Pipe, PipeTransform} from '@angular/core'; + +@Pipe({name: 'groupBy'}) +export class GroupByPipe implements PipeTransform { + transform(value: Array<any>, field: string): Array<any> { + const groupedObj = value.reduce((prev, cur)=> { + if(!prev[cur[field]]) { + prev[cur[field]] = [cur]; + } else { + prev[cur[field]].push(cur); + } + return prev; + }, {}); + return Object.keys(groupedObj).map((key:string) => {return { key, value: groupedObj[key] }; }); + } +} diff --git a/catalog-ui/src/app/ng2/pipes/keys.pipe.ts b/catalog-ui/src/app/ng2/pipes/keys.pipe.ts new file mode 100644 index 0000000000..13bd26969c --- /dev/null +++ b/catalog-ui/src/app/ng2/pipes/keys.pipe.ts @@ -0,0 +1,12 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({name: 'keys'}) +export class KeysPipe implements PipeTransform { + transform(value, args:string[]) : any { + let keys = []; + for (let key in value) { + keys.push(key); + } + return keys; + } +} diff --git a/catalog-ui/src/app/ng2/pipes/searchFilter.pipe.ts b/catalog-ui/src/app/ng2/pipes/searchFilter.pipe.ts new file mode 100644 index 0000000000..7e017e8590 --- /dev/null +++ b/catalog-ui/src/app/ng2/pipes/searchFilter.pipe.ts @@ -0,0 +1,18 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'searchFilter', +}) +export class SearchFilterPipe implements PipeTransform { + public transform(value, key: string, term: string) { + if (!term || !term.length) return value; + return value.filter((item) => { + if (item.hasOwnProperty(key)) { + let regExp = new RegExp(term, 'gi'); + return regExp.test(item[key]); + } else { + return false; + } + }); + } +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/services/authentication.service.ts b/catalog-ui/src/app/ng2/services/authentication.service.ts new file mode 100644 index 0000000000..7fe3e22f4c --- /dev/null +++ b/catalog-ui/src/app/ng2/services/authentication.service.ts @@ -0,0 +1,40 @@ +import { Injectable } from '@angular/core'; +import { sdc2Config } from './../../../main'; +import {IAppConfigurtaion, ICookie} from "../../models/app-config"; +import {Response, Headers, RequestOptions, Http} from '@angular/http'; +import {Cookie2Service} from "./cookie.service"; +import { Observable } from 'rxjs/Observable'; + +@Injectable() +export class AuthenticationService { + + private cookieService:Cookie2Service; + private http:Http; + + constructor(cookieService:Cookie2Service, http: Http) { + this.cookieService = cookieService; + this.http = http; + } + + private getAuthHeaders():any { + let cookie:ICookie = sdc2Config.cookie; + let authHeaders:any = {}; + authHeaders[cookie.userFirstName] = this.cookieService.getFirstName(); + authHeaders[cookie.userLastName] = this.cookieService.getLastName(); + authHeaders[cookie.userEmail] = this.cookieService.getEmail(); + authHeaders[cookie.userIdSuffix] = this.cookieService.getUserId(); + return authHeaders; + } + + public authenticate(): Observable<JSON> { + let options = new RequestOptions({ + headers: new Headers(this.getAuthHeaders()) + }); + + let authUrl = sdc2Config.api.root + sdc2Config.api.GET_user_authorize; + return this.http + .get(authUrl, options) + .map((res: Response) => res.json()); + } + +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/services/component-instance-services/component-instance.service.ts b/catalog-ui/src/app/ng2/services/component-instance-services/component-instance.service.ts new file mode 100644 index 0000000000..85709894ff --- /dev/null +++ b/catalog-ui/src/app/ng2/services/component-instance-services/component-instance.service.ts @@ -0,0 +1,51 @@ +import {Injectable} from '@angular/core'; +import {Response, RequestOptions, Headers} from '@angular/http'; +import { Observable } from 'rxjs/Observable'; +import {HttpService} from "../http.service"; +import {sdc2Config} from "../../../../main"; +import {PropertyBEModel} from "app/models"; +import {CommonUtils} from "app/utils"; +import {Component, ComponentInstance, InputModel} from "app/models"; + +@Injectable() +export class ComponentInstanceServiceNg2 { + + protected baseUrl; + + constructor(private http: HttpService) { + this.baseUrl = sdc2Config.api.root + sdc2Config.api.component_api_root; + } + + getComponentInstanceProperties(component: Component, componentInstanceId: string): Observable<Array<PropertyBEModel>> { + + return this.http.get(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/componentInstances/' + componentInstanceId + '/properties') + .map((res: Response) => { + return CommonUtils.initBeProperties(res.json()); + }) + } + + getComponentInstanceInputs(component: Component, componentInstance: ComponentInstance): Observable<Array<PropertyBEModel>> { + return this.http.get(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/componentInstances/' + componentInstance.uniqueId + '/' + componentInstance.componentUid + '/inputs') + .map((res: Response) => { + return CommonUtils.initInputs(res.json()); + }) + } + + updateInstanceProperty(component: Component, componentInstanceId: string, property: PropertyBEModel): Observable<PropertyBEModel> { + + return this.http.post(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/resourceInstance/' + componentInstanceId + '/property', property) + .map((res: Response) => { + return new PropertyBEModel(res.json()); + }) + } + + updateInstanceInput(component: Component, componentInstanceId: string, input: PropertyBEModel): Observable<PropertyBEModel> { + + return this.http.post(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/resourceInstance/' + componentInstanceId + '/input', input) + .map((res: Response) => { + return new PropertyBEModel(res.json()); + }) + } + + +} diff --git a/catalog-ui/src/app/ng2/services/component-services/component.service.ts b/catalog-ui/src/app/ng2/services/component-services/component.service.ts new file mode 100644 index 0000000000..3fa9fde40c --- /dev/null +++ b/catalog-ui/src/app/ng2/services/component-services/component.service.ts @@ -0,0 +1,149 @@ +import {Injectable, Query} from '@angular/core'; +import {Observable} from 'rxjs/Observable'; +import 'rxjs/add/operator/map'; +import 'rxjs/add/operator/toPromise'; +import {Response, URLSearchParams} from '@angular/http'; +import { Component, PropertyBEModel, InstancePropertiesAPIMap, FilterPropertiesAssignmentData} from "app/models"; +import {downgradeInjectable} from '@angular/upgrade/static'; +import {HttpService} from "../http.service"; +import {COMPONENT_FIELDS} from "app/utils"; +import {ComponentGenericResponse} from "../responses/component-generic-response"; +import {sdc2Config} from "../../../../main"; +import {InstanceBePropertiesMap} from "../../../models/properties-inputs/property-fe-map"; +import {API_QUERY_PARAMS} from "app/utils"; +import {ComponentType, ServerTypeUrl} from "../../../utils/constants"; + +declare var angular:angular.IAngularStatic; + +@Injectable() +export class ComponentServiceNg2 { + + protected baseUrl; + + constructor(private http:HttpService) { + this.baseUrl = sdc2Config.api.root + sdc2Config.api.component_api_root; + } + + private getComponentDataByFieldsName(componentType:string, componentId: string, fields:Array<string>):Observable<ComponentGenericResponse> { + + let params:URLSearchParams = new URLSearchParams(); + _.forEach(fields, (field:string):void => { + params.append(API_QUERY_PARAMS.INCLUDE, field); + }); + + return this.http.get(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/filteredDataByParams', {search: params}) + .map((res:Response) => { + return new ComponentGenericResponse().deserialize(res.json()); + }).do(error => console.log('server data:', error)); + } + + private getServerTypeUrl = (componentType:string):string => { + switch (componentType) { + case ComponentType.PRODUCT: + return ServerTypeUrl.PRODUCTS; + case ComponentType.SERVICE: + return ServerTypeUrl.SERVICES; + default: + return ServerTypeUrl.RESOURCES; + } + } + + getComponentMetadata(component:Component):Observable<ComponentGenericResponse> { + return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_METADATA]); + } + + getComponentInstanceAttributesAndProperties(component:Component):Observable<ComponentGenericResponse> { + return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_INSTANCES_PROPERTIES, COMPONENT_FIELDS.COMPONENT_INSTANCES_ATTRIBUTES]); + } + + getComponentAttributes(component:Component):Observable<ComponentGenericResponse> { + return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_ATTRIBUTES]); + } + + getComponentInstancesAndRelation(component:Component):Observable<ComponentGenericResponse> { + return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_INSTANCES_RELATION, COMPONENT_FIELDS.COMPONENT_INSTANCES]); + } + + getComponentResourceInstances(component:Component):Observable<ComponentGenericResponse> { + return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_INSTANCES]); + } + + getComponentInputs(component:Component):Observable<ComponentGenericResponse> { + return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_INPUTS]); + } + + getComponentDeploymentArtifacts(component:Component):Observable<ComponentGenericResponse> { + return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_DEPLOYMENT_ARTIFACTS]); + } + + getComponentInformationalArtifacts(component:Component):Observable<ComponentGenericResponse> { + return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_INFORMATIONAL_ARTIFACTS]); + } + + getComponentInformationalArtifactsAndInstances(component:Component):Observable<ComponentGenericResponse> { + return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_INFORMATIONAL_ARTIFACTS, COMPONENT_FIELDS.COMPONENT_INSTANCES]); + } + + getComponentToscaArtifacts(component:Component):Observable<ComponentGenericResponse> { + return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_TOSCA_ARTIFACTS]); + } + + getComponentProperties(component:Component):Observable<ComponentGenericResponse> { + return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_PROPERTIES]); + } + + getCapabilitiesAndRequirements(componentType: string, componentId:string):Observable<ComponentGenericResponse> { + return this.getComponentDataByFieldsName(componentType, componentId, [COMPONENT_FIELDS.COMPONENT_REQUIREMENTS, COMPONENT_FIELDS.COMPONENT_CAPABILITIES]); + } + + getDeploymentGraphData(component:Component):Observable<ComponentGenericResponse> { + return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_INSTANCES_RELATION, COMPONENT_FIELDS.COMPONENT_INSTANCES, COMPONENT_FIELDS.COMPONENT_GROUPS]); + } + + createInput(component:Component, inputsToCreate:InstancePropertiesAPIMap):Observable<any> { + return this.http.post(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/create/inputs', inputsToCreate) + .map(res => { + return res.json(); + }) + } + + deleteInput(component:Component, input:PropertyBEModel):Observable<PropertyBEModel> { + + return this.http.delete(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/delete/' + input.uniqueId + '/input') + .map((res:Response) => { + return new PropertyBEModel(res.json()); + }) + } + + updateComponentInput(component:Component, input:PropertyBEModel):Observable<PropertyBEModel> { + + return this.http.post(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/update/inputs', input) + .map((res:Response) => { + return new PropertyBEModel(res.json()) + }) + } + + filterComponentInstanceProperties(component: Component, filterData:FilterPropertiesAssignmentData): Observable<InstanceBePropertiesMap> {//instance-property-be-map + let params: URLSearchParams = new URLSearchParams(); + _.forEach(filterData.selectedTypes, (type:string) => { + params.append('resourceType', type); + }); + + return this.http.get(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/filteredproperties/' + filterData.propertyName, {search: params}) + .map((res: Response) => { + return res.json(); + }); + + // return {'ExtVL 0':[{definition: false,name:"network_assignments",password:false,required:true,type:"org.openecomp.datatypes.network.NetworkAssignments",uniqueId:"623cca1c-d605-4c9c-9f2b-935ec85ebcf8.network_assignments"}, + // {definition: false,name: "exVL_naming",password: false,required: true,type: "org.openecomp.datatypes.Naming",uniqueId: "623cca1c-d605-4c9c-9f2b-935ec85ebcf8.exVL_naming"}, + // {definition: false,name: "network_flows",password: false,required: false,type: "org.openecomp.datatypes.network.NetworkFlows",uniqueId: "623cca1c-d605-4c9c-9f2b-935ec85ebcf8.network_flows"}, + // {definition: false,name: "provider_network",password: false,required: true,type: "org.openecomp.datatypes.network.ProviderNetwork",uniqueId: "623cca1c-d605-4c9c-9f2b-935ec85ebcf8.provider_network"}, + // {definition: false,name: "network_homing",password: false,required: true,type: "org.openecomp.datatypes.EcompHoming",uniqueId: "623cca1c-d605-4c9c-9f2b-935ec85ebcf8.network_homing"}], + // 'NetworkCP 0':[{definition: false,description: "identifies MAC address assignments to the CP",name: "mac_requirements",password: false,required: false,type: "org.openecomp.datatypes.network.MacRequirements",uniqueId: "26ec2bfd-b904-46c7-87ed-b32775120f2c.mac_requirements"}], + // 'NetworkCP 1':[{definition: false,description: "identifies MAC address assignments to the CP",name: "mac_requirements",password: false,required: false,type: "org.openecomp.datatypes.network.MacRequirements",uniqueId: "26ec2bfd-b904-46c7-87ed-b32775120f2c.mac_requirements"}]}; + + + } +} + +angular.module('Sdc.Services').factory('ComponentServiceNg2', downgradeInjectable(ComponentServiceNg2)); // This is in order to use the service in angular1 till we finish remove all angular1 code diff --git a/catalog-ui/src/app/ng2/services/component-services/resource.service.ts b/catalog-ui/src/app/ng2/services/component-services/resource.service.ts new file mode 100644 index 0000000000..650f244d38 --- /dev/null +++ b/catalog-ui/src/app/ng2/services/component-services/resource.service.ts @@ -0,0 +1,18 @@ +import { Injectable } from '@angular/core'; +import 'rxjs/add/operator/map'; +import 'rxjs/add/operator/toPromise'; +import { Http, Response, Headers, RequestOptions } from '@angular/http'; + +@Injectable() +export class ResourceServiceNg2 { + + protected baseUrl = ""; + + constructor(private http: Http) { + + } + + + + +} diff --git a/catalog-ui/src/app/ng2/services/component-services/service.service.ts b/catalog-ui/src/app/ng2/services/component-services/service.service.ts new file mode 100644 index 0000000000..b47b64c5c2 --- /dev/null +++ b/catalog-ui/src/app/ng2/services/component-services/service.service.ts @@ -0,0 +1,31 @@ +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs/Observable'; +import 'rxjs/add/operator/map'; +import 'rxjs/add/operator/toPromise'; +import { Response } from '@angular/http'; +import {Service} from "app/models"; +import { downgradeInjectable } from '@angular/upgrade/static'; +import {sdc2Config} from "../../../../main"; +import {HttpService} from "../http.service"; + + +@Injectable() +export class ServiceServiceNg2 { + + protected baseUrl = ""; + + constructor(private http: HttpService) { + this.baseUrl = sdc2Config.api.root + sdc2Config.api.component_api_root; + } + + validateConformanceLevel(service: Service): Observable<boolean> { + + return this.http.get(this.baseUrl + service.getTypeUrl() + service.uniqueId + '/conformanceLevelValidation') + .map((res: Response) => { + return res.json(); + }); + } + +} + +angular.module('Sdc.Services').factory('ServiceServiceNg2', downgradeInjectable(ServiceServiceNg2)); // This is in order to use the service in angular1 till we finish remove all angular1 code diff --git a/catalog-ui/src/app/ng2/services/config.service.ts b/catalog-ui/src/app/ng2/services/config.service.ts new file mode 100644 index 0000000000..0ac3b5a397 --- /dev/null +++ b/catalog-ui/src/app/ng2/services/config.service.ts @@ -0,0 +1,51 @@ +/** + * Created by ob0695 on 4/9/2017. + */ + +import { Injectable } from '@angular/core'; +import { Http, Response } from '@angular/http'; +import 'rxjs/add/operator/toPromise'; +import {IAppConfigurtaion, ValidationConfiguration, Validations} from "app/models"; +import {sdc2Config} from './../../../main'; + +declare var __ENV__: string; + +@Injectable() +export class ConfigService { + + private baseUrl; + public configuration: IAppConfigurtaion; + + constructor(private http: Http) { + this.baseUrl = sdc2Config.api.root + sdc2Config.api.component_api_root; + } + + loadValidationConfiguration(): Promise<ValidationConfiguration> { + let url: string = sdc2Config.validationConfigPath; + let promise: Promise<ValidationConfiguration> = this.http.get(url).map((res: Response) => res.json()).toPromise(); + promise.then((validationData: Validations) => { + ValidationConfiguration.validation = validationData; + }).catch((ex) => { + console.error('Error loading validation.json configuration file, using fallback data', ex); + + let fallback:Validations = { + "propertyValue": { + "max": 2500, + "min": 0 + }, + + "validationPatterns": { + "string": "^[\\sa-zA-Z0-9+-]+$", + "comment": "^[\\sa-zA-Z0-9+-_\\{\\}\"]+$", + "integer": "^(([-+]?\\d+)|([-+]?0x[0-9a-fA-F]+))$" + } + }; + + ValidationConfiguration.validation = fallback; + + }); + + return promise; + } + +} diff --git a/catalog-ui/src/app/ng2/services/cookie.service.ts b/catalog-ui/src/app/ng2/services/cookie.service.ts new file mode 100644 index 0000000000..2dc2ac3e6b --- /dev/null +++ b/catalog-ui/src/app/ng2/services/cookie.service.ts @@ -0,0 +1,65 @@ +import { Injectable } from '@angular/core'; +import {IAppConfigurtaion, ICookie} from "../../models/app-config"; +import {sdc2Config} from './../../../main'; + +@Injectable() +export class Cookie2Service { + + private cookie:ICookie; + private cookiePrefix:string; + + constructor() { + this.cookie = sdc2Config.cookie; + + this.cookiePrefix = ''; + let junctionName:string = this.getCookieByName(this.cookie.junctionName); + if ((junctionName !== null) && (junctionName !== '')) { + this.cookiePrefix = this.cookie.prefix + junctionName + '!'; + } + console.log("junctionName:" + junctionName); + } + + private getCookieByName = (cookieName:string):string => { + cookieName += '='; + let cookies:Array<string> = document.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/src/app/ng2/services/data-type.service.ts b/catalog-ui/src/app/ng2/services/data-type.service.ts new file mode 100644 index 0000000000..e7ea1a8430 --- /dev/null +++ b/catalog-ui/src/app/ng2/services/data-type.service.ts @@ -0,0 +1,69 @@ +import { Injectable } from '@angular/core'; +import { DataTypeModel, DataTypesMap, PropertyBEModel, PropertyFEModel, DerivedFEProperty, DerivedFEPropertyMap } from "app/models"; +import { DataTypesService } from "app/services/data-types-service"; +import { PROPERTY_DATA, PROPERTY_TYPES } from "app/utils"; + +/** This is a new service for NG2, to eventually replace app/services/data-types-service.ts + * + * This service is a singleton that holds a map of all DataTypes, recieved from server on load. + * It also contains convenience methods to check if a string is a valid dataType, and to retrieve a dataType's properties recursively + */ + +@Injectable() +export class DataTypeService { + private dataTypes: DataTypesMap; + + constructor(private dataTypeService: DataTypesService) { + this.dataTypes = dataTypeService.getAllDataTypes(); //This should eventually be replaced by an NG2 call to the backend instead of utilizing Angular1 downgraded component. + } + + public getDataTypeByTypeName(typeName: string): DataTypeModel { + return this.dataTypes[typeName]; + } +/* + //if the dt derived from simple- return the first parent type, else- return null + public getTypeForDataTypeDerivedFromSimple = (dataTypeName:string):string => { + /////////temporary hack for tosca primitives/////////////////////// + if (!this.dataTypes[dataTypeName]) { + return PROPERTY_TYPES.STRING; + } + /////////////////////////////////////////////////////////////////// + if (this.dataTypes[dataTypeName].derivedFromName == PROPERTY_DATA.ROOT_DATA_TYPE || this.dataTypes[dataTypeName].properties) { + return null; + } + if (PROPERTY_DATA.SIMPLE_TYPES.indexOf(this.dataTypes[dataTypeName].derivedFromName) > -1) { + return this.dataTypes[dataTypeName].derivedFromName + } + return this.getTypeForDataTypeDerivedFromSimple(this.dataTypes[dataTypeName].derivedFromName); + }; + + /** + * The function returns all properties for the DataType passed in, and recurses through parent dataTypes (derivedFrom) to retrieve their properties as well + * @param dataTypeObj + * + public getDataTypePropertiesRecursively(dataTypeObj: DataTypeModel): Array<PropertyBEModel> { + let propertiesArray: Array<PropertyBEModel> = dataTypeObj.properties || []; + if (PROPERTY_DATA.ROOT_DATA_TYPE !== dataTypeObj.derivedFromName) { + propertiesArray = propertiesArray.concat(this.getDataTypePropertiesRecursively(dataTypeObj.derivedFrom)); + } + return propertiesArray; + } +*/ + + public getDerivedDataTypeProperties(dataTypeObj: DataTypeModel, propertiesArray: Array<DerivedFEProperty>, parentName: string) { + //push all child properties to array + if (dataTypeObj.properties) { + dataTypeObj.properties.forEach((derivedProperty) => { + propertiesArray.push(new DerivedFEProperty(derivedProperty, parentName)); + let derivedDataTypeObj: DataTypeModel = this.getDataTypeByTypeName(derivedProperty.type); + this.getDerivedDataTypeProperties(derivedDataTypeObj, propertiesArray, parentName + "#" + derivedProperty.name); + }); + } + //recurse parent (derivedFrom), in case one of parents contains properties + if (PROPERTY_DATA.ROOT_DATA_TYPE !== dataTypeObj.derivedFrom.name) { + this.getDerivedDataTypeProperties(dataTypeObj.derivedFrom, propertiesArray, parentName); + } + } + +} + diff --git a/catalog-ui/src/app/ng2/services/http.service.ts b/catalog-ui/src/app/ng2/services/http.service.ts new file mode 100644 index 0000000000..92e8ced142 --- /dev/null +++ b/catalog-ui/src/app/ng2/services/http.service.ts @@ -0,0 +1,73 @@ +import {Injectable} from '@angular/core'; +import {Http, XHRBackend, RequestOptions, Request, RequestOptionsArgs, Response, Headers} from '@angular/http'; +import {Observable} from 'rxjs/Observable'; +import {UUID} from 'angular2-uuid'; +import 'rxjs/add/operator/map'; +import 'rxjs/add/operator/catch'; +import {Dictionary} from "../../utils/dictionary/dictionary"; +import {SharingService, CookieService} from "app/services"; +import {sdc2Config} from './../../../main'; + +@Injectable() +export class HttpService extends Http { + + constructor(backend:XHRBackend, options:RequestOptions, private sharingService:SharingService, private cookieService: CookieService) { + super(backend, options); + this._defaultOptions.withCredentials = true; + this._defaultOptions.headers.append(cookieService.getUserIdSuffix(), cookieService.getUserId()); + } + + request(request:string|Request, options?:RequestOptionsArgs):Observable<Response> { + /** + * 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. + */ + if (typeof request === 'string') { // meaning we have to add the token to the options, not in url + if (!options) { + // make option object + options = {headers: new Headers()}; + } + + var uuidValue = this.getUuidValue(request); + if(uuidValue!= ''){ + options.headers['X-ECOMP-ServiceID'] = uuidValue; + + } + options.headers.set('X-ECOMP-RequestID', UUID.UUID()); + + } else { + // we have to add the token to the url object + var uuidValue = this.getUuidValue((<Request>request).url); + if(uuidValue!= ''){ + request.headers.set('X-ECOMP-ServiceID',uuidValue); + + } + request.headers.set('X-ECOMP-RequestID', UUID.UUID()); + } + return super.request(request, options).catch(this.catchAuthError(this)); + } + + private getUuidValue = (url: string) :string => { + let map:Dictionary<string, string> = this.sharingService.getUuidMap(); + if (map && url.indexOf(sdc2Config.api.root) > 0) { + map.forEach((key:string) => { + if (url.indexOf(key) !== -1) { + return this.sharingService.getUuidValue(key); + } + }); + } + return ''; + } + + private catchAuthError(self:HttpService) { + // we have to pass HttpService's own instance here as `self` + return (res:Response) => { + console.log(res); + if (res.status === 401 || res.status === 403) { + // if not authenticated + console.log(res); + } + return Observable.throw(res); + }; + } +} diff --git a/catalog-ui/src/app/ng2/services/mocks/properties.mock.ts b/catalog-ui/src/app/ng2/services/mocks/properties.mock.ts new file mode 100644 index 0000000000..0ce253e80b --- /dev/null +++ b/catalog-ui/src/app/ng2/services/mocks/properties.mock.ts @@ -0,0 +1,16 @@ +// import { PropertiesResponse } from './../../services/responses/properties.response'; +// +// export const PROPERTIES_RESPONSE: PropertiesResponse = { +// "status":"ok", +// "errorcode":0, +// "properties": [ +// {"name": "name1"}, +// {"name": "name2"} +// ] +// }; + + +export const COMPONENT_INSTANCE_RESPONSE: any = [{"uniqueId":"23b78e64-1734-4898-889d-eab9eba50019.724ef51f-7595-4336-a8ef-b144e1c937da.contrailv2vlansubinterface5","name":"CP2","normalizedName":"cp2","componentUid":"724ef51f-7595-4336-a8ef-b144e1c937da","creationTime":1489586037851,"modificationTime":1489586037851,"posX":"549","posY":"531","propertyValueCounter":1,"inputValueCounter":1,"originType":"CP","customizationUUID":"0222925a-06c6-48ca-8573-13ba3b650539"},{"uniqueId":"23b78e64-1734-4898-889d-eab9eba50019.8ac5a229-60d8-4d7f-8e0f-b630da55c3ae.ldsa_os27","name":"VF22","normalizedName":"vf22","componentUid":"8ac5a229-60d8-4d7f-8e0f-b630da55c3ae","creationTime":1489590640842,"modificationTime":1489590640842,"posX":"644","posY":"359","propertyValueCounter":1,"inputValueCounter":1,"originType":"VF","customizationUUID":"fb34e6a1-f0cc-42e3-853f-178fb122d670"},{"uniqueId":"23b78e64-1734-4898-889d-eab9eba50019.00ac92c4-a0c5-4567-aefb-f2b7f3fecba9.vl4","name":"VL1","normalizedName":"vl1","componentUid":"00ac92c4-a0c5-4567-aefb-f2b7f3fecba9","creationTime":1489586006630,"modificationTime":1489586006630,"posX":"486","posY":"530","propertyValueCounter":1,"inputValueCounter":1,"originType":"VL","customizationUUID":"8d156a97-38ca-4637-a6a6-9268dd708431"},{"uniqueId":"23b78e64-1734-4898-889d-eab9eba50019.39bf293d-2aa9-4596-b85d-a6fdef18aadc.newvf428","name":"VF11","normalizedName":"vf11","componentUid":"39bf293d-2aa9-4596-b85d-a6fdef18aadc","creationTime":1489590742501,"modificationTime":1489590742501,"posX":"350","posY":"76","propertyValueCounter":1,"inputValueCounter":1,"originType":"VF","customizationUUID":"253f646d-87d0-4b84-bc20-20343b6e28a2"},{"uniqueId":"23b78e64-1734-4898-889d-eab9eba50019.f8f30484-5761-4de8-9de8-12f288ee0a54.contrailport7","name":"CP1","normalizedName":"cp1","componentUid":"f8f30484-5761-4de8-9de8-12f288ee0a54","creationTime":1489587207447,"modificationTime":1489587207447,"posX":"418","posY":"531","propertyValueCounter":1,"inputValueCounter":1,"originType":"CP","customizationUUID":"206b4ffa-e520-496a-b57b-0c103eff8c17"},{"uniqueId":"23b78e64-1734-4898-889d-eab9eba50019.16022d8c-80cf-46e5-a730-5b4f634ea905.extcp38","name":"ExtCP 38","normalizedName":"extcp38","componentUid":"16022d8c-80cf-46e5-a730-5b4f634ea905","creationTime":1489654844116,"modificationTime":1489654844116,"posX":"473","posY":"419","propertyValueCounter":5,"inputValueCounter":1,"originType":"CP","customizationUUID":"21ebc94a-65b9-463e-b696-07cea08f789a"},{"uniqueId":"23b78e64-1734-4898-889d-eab9eba50019.f8f30484-5761-4de8-9de8-12f288ee0a54.contrailport44","name":"ContrailPort 44","normalizedName":"contrailport44","componentUid":"f8f30484-5761-4de8-9de8-12f288ee0a54","creationTime":1489939897435,"modificationTime":1489939897435,"posX":"480","posY":"265","propertyValueCounter":1,"inputValueCounter":1,"originType":"CP","customizationUUID":"607e5819-fa19-4aac-97f6-04f092493a02"},{"uniqueId":"23b78e64-1734-4898-889d-eab9eba50019.8ac5a229-60d8-4d7f-8e0f-b630da55c3ae.ldsa_os6","name":"VF2","normalizedName":"vf2","componentUid":"8ac5a229-60d8-4d7f-8e0f-b630da55c3ae","creationTime":1489586613957,"modificationTime":1489586613957,"posX":"638","posY":"531","propertyValueCounter":1,"inputValueCounter":1,"originType":"VF","customizationUUID":"12501356-44cc-427b-8749-c114d3434271"},{"uniqueId":"23b78e64-1734-4898-889d-eab9eba50019.8ac5a229-60d8-4d7f-8e0f-b630da55c3ae.ldsa_os8","name":"VF1","normalizedName":"vf1","componentUid":"8ac5a229-60d8-4d7f-8e0f-b630da55c3ae","creationTime":1489587393612,"modificationTime":1489587393612,"posX":"334","posY":"529","propertyValueCounter":1,"inputValueCounter":1,"originType":"VF","customizationUUID":"cbca1972-e64c-4119-a430-ec90aa1397a7"},{"uniqueId":"23b78e64-1734-4898-889d-eab9eba50019.8ac5a229-60d8-4d7f-8e0f-b630da55c3ae.ldsa_os32","name":"VF12","normalizedName":"vf12","componentUid":"8ac5a229-60d8-4d7f-8e0f-b630da55c3ae","creationTime":1489591429630,"modificationTime":1489591429630,"posX":"367","posY":"269","propertyValueCounter":1,"inputValueCounter":1,"originType":"VF","customizationUUID":"ac9c77c1-e84d-4967-9ea2-d929b56d10a8"}]; +export const COMPONENT_INPUT_RESPONSE: any = [{"uniqueId":"23b78e64-1734-4898-889d-eab9eba50019.extcp38_mac_requirements_mac_range_plan","type":"string","required":true,"definition":false,"description":"reference to a MAC address range plan","password":false,"name":"extcp38_mac_requirements_mac_range_plan","parentUniqueId":"23b78e64-1734-4898-889d-eab9eba50019"},{"uniqueId":"23b78e64-1734-4898-889d-eab9eba50019.extcp38_order","type":"integer","required":true,"definition":false,"description":"The order of the CP on the compute instance (e.g. eth2).","schema":{"property":{"definition":true,"password":false}},"password":false,"name":"extcp38_order","parentUniqueId":"23b78e64-1734-4898-889d-eab9eba50019"},{"uniqueId":"23b78e64-1734-4898-889d-eab9eba50019.extcp38_network_role_tag","type":"string","required":true,"definition":false,"description":"Must correlate to the set of defined “network-role” tag identifiers from the associated HEAT template","schema":{"property":{"definition":true,"password":false}},"password":false,"name":"extcp38_network_role_tag","parentUniqueId":"23b78e64-1734-4898-889d-eab9eba50019"}]; +export const COMPONENT_PROPERTIES_RESPONSE: any =[{"schema":{"property":{"password":false,"definition":false}},"password":false,"parentUniqueId":"b80765f0-ef2b-43b1-b658-04e13970b5cf","defaultValue":"{\"naming_policy\":\"ggg\",\"ecomp_generated_naming\":false,\"supplemental_data\":{\"fff\":\"44\",\"uuu\":\"56\"}}","name":"inner-map-1","definition":false,"type":"org.openecomp.datatypes.EcompNaming","uniqueId":"property.b80765f0-ef2b-43b1-b658-04e13970b5cf.inner-map-1","required":false},{"schema":{"property":{"password":false,"definition":false}},"password":false,"parentUniqueId":"b80765f0-ef2b-43b1-b658-04e13970b5cf","defaultValue":"{\"min_subnets_count\":1,\"supplemental_data\":{\"aa\":\"11\",\"bb\":\"22\"},\"cidr_mask\":23,\"dhcp_enabled\":false,\"ip_version\":3}","name":"inner-simple-map","definition":false,"type":"org.openecomp.datatypes.network.IPv4SubnetAssignments","uniqueId":"property.b80765f0-ef2b-43b1-b658-04e13970b5cf.inner-simple-map","required":false},{"schema":{"property":{"password":false,"definition":false,"type":"org.openecomp.datatypes.heat.network.AddressPair"}},"password":false,"parentUniqueId":"b80765f0-ef2b-43b1-b658-04e13970b5cf","defaultValue":"{\"aaa\":{\"mac_address\":\"34\",\"ip_address\":\"56\"},\"bbb\":{\"mac_address\":\"sddf\",\"ip_address\":\"dfdg\"}}","name":"data-type-map","definition":false,"type":"map","uniqueId":"property.b80765f0-ef2b-43b1-b658-04e13970b5cf.data-type-map","required":false},{"schema":{"property":{"password":false,"definition":false,"type":"string"}},"password":false,"parentUniqueId":"b80765f0-ef2b-43b1-b658-04e13970b5cf","defaultValue":"{\"www\":\"dfsdf\",\"aaaa\":\"ert\"}","name":"simple-map","definition":false,"type":"map","uniqueId":"property.b80765f0-ef2b-43b1-b658-04e13970b5cf.simple-map","required":false}]; + diff --git a/catalog-ui/src/app/ng2/services/posts.service.ts b/catalog-ui/src/app/ng2/services/posts.service.ts new file mode 100644 index 0000000000..dbfd44f219 --- /dev/null +++ b/catalog-ui/src/app/ng2/services/posts.service.ts @@ -0,0 +1,54 @@ +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs/Observable'; +import 'rxjs/add/operator/map'; +import 'rxjs/add/operator/toPromise'; +import 'rxjs/Rx'; +import {Response, Headers, RequestOptions, Http} from '@angular/http'; +import { COMPONENT_INSTANCE_RESPONSE,COMPONENT_INPUT_RESPONSE,COMPONENT_PROPERTIES_RESPONSE } from './mocks/properties.mock'; +import { HttpService } from './http.service'; +import { sdc2Config } from './../../../main'; +import {IAppConfigurtaion} from "../../models/app-config"; + +@Injectable() +export class PostsService { + + private base; + + constructor(private http: HttpService) { + this.base = sdc2Config.api.root; + } + + getAppVersion(): Observable<JSON> { + return this.http + .get(this.base + sdc2Config.api.GET_SDC_Version) + .map((res: Response) => res.json()); + } + + // getProperties(id:string): Observable<any> { + // return this.http + // .get(this.base + sdc2Config.api.GET_SDC_Version) + // .map((res: Response) => res.json()); + // } + + getProperties(): Observable<any> { + return Observable.create(observer => { + observer.next(COMPONENT_PROPERTIES_RESPONSE); + observer.complete(); + }); + } + + getInstance(): Observable<any> { + return Observable.create(observer => { + observer.next(COMPONENT_INSTANCE_RESPONSE); + observer.complete(); + }); + } + + getInputs(): Observable<any> { + return Observable.create(observer => { + observer.next(COMPONENT_INPUT_RESPONSE); + observer.complete(); + }); + } + +} diff --git a/catalog-ui/src/app/ng2/services/properties.service.ts b/catalog-ui/src/app/ng2/services/properties.service.ts new file mode 100644 index 0000000000..638e537cd1 --- /dev/null +++ b/catalog-ui/src/app/ng2/services/properties.service.ts @@ -0,0 +1,249 @@ +import { Injectable } from '@angular/core'; +import { DataTypeModel, PropertyFEModel, PropertyBEModel, SchemaProperty, DerivedFEProperty, DerivedFEPropertyMap, DerivedPropertyType, InputFEModel} from "app/models"; +import { DataTypeService } from "./data-type.service"; +import { PROPERTY_TYPES } from "app/utils"; +import { ContentAfterLastDotPipe } from "../pipes/contentAfterLastDot.pipe"; +import { UUID } from "angular2-uuid"; + +@Injectable() +export class PropertiesService { + + constructor(private dataTypeService:DataTypeService, private contentAfterLastDotPipe:ContentAfterLastDotPipe) { + } + + public getParentPropertyFEModelFromPath = (properties:Array<PropertyFEModel>, path:string) => { + let parent:PropertyFEModel = _.find(properties, (property:PropertyFEModel):boolean=>{ + return property.name === path.substring(0, path.indexOf('#')); + }); + return parent; + } + + //undo disabling of parent and child props= + public undoDisableRelatedProperties = (property: PropertyFEModel, childPath?: string): void => { + property.isDisabled = false; + if (!childPath) { + property.isSelected = false; + property.flattenedChildren && property.flattenedChildren.map(child => child.isDisabled = false); + } else { //QND - unselect everything and then re-do the disabling of declared props. TODO: put a flag on propertyFEModel instead to indicate who's causing them to be disabled instead + property.flattenedChildren.filter(child => child.isDisabled && !child.isDeclared).map(child => child.isDisabled = false); + property.flattenedChildren.filter(child => child.isDeclared || child.isSelected).forEach((childProp) => { //handle brothers who are selected - redo their disabled relatives as well + this.disableRelatedProperties(property, childProp.propertiesName); + }); + } + } + + //disable parents and children of prop + public disableRelatedProperties = (property:PropertyFEModel, childPath?: string): void => { + if (!childPath) { //selecting the parent property + property.isSelected = true; + property.flattenedChildren && property.flattenedChildren.map(child => { child.isSelected = false; child.isDisabled = true; }); + } else { + property.isSelected = false; + property.isDisabled = true; + property.flattenedChildren.filter((childProp: DerivedFEProperty) => { + return (childProp.propertiesName.indexOf(childPath + "#") > -1 //is child of prop to disable + || childPath.indexOf(childProp.propertiesName + "#") > -1); //is parent of prop to disable + }).map((child: DerivedFEProperty) => { child.isSelected = false; child.isDisabled = true; }); + } + } + + public getCheckedProperties = (properties:Array<PropertyFEModel>): Array<PropertyBEModel> => { + let selectedProps: Array<PropertyBEModel> = []; + properties.forEach(prop => { + if (prop.isSelected && !prop.isDeclared && !prop.isDisabled) { + selectedProps.push(new PropertyBEModel(prop)); + } else if(prop.flattenedChildren) { + prop.flattenedChildren.forEach((child) => { + if (child.isSelected && !child.isDeclared && !child.isDisabled) { + let childProp = new PropertyBEModel(prop, child); //create it from the parent + selectedProps.push(childProp); + } + }) + } + }); + return selectedProps; + } + + /** + * Build hirarchy structure for the tree when user selects on table row. + * First create Array<SimpleFlatProperty> and insert also the parent (PropertyFEModel) to this array. + * The Array is flat and contains SimpleFlatProperty that has parentName and uniqueId. + * Now we build hirarchy from this Array (that includes childrens) and return it for the tree + * + * @argument property: PropertyFEModel - property contains flattenedChildren array of DerivedFEProperty + * @returns Array<SimpleFlatProperty> - containing childrens Array<SimpleFlatProperty>, augmantin childrens to SimpleFlatProperty. + */ + public getSimplePropertiesTree(property: PropertyFEModel, instanceName:string):Array<SimpleFlatProperty> { + // Build Array of SimpleFlatProperty before unflatten function + let flattenProperties:Array<SimpleFlatProperty> = []; + flattenProperties.push(new SimpleFlatProperty(property.uniqueId, property.name, property.name, '', instanceName)); // Push the root property + _.each(property.flattenedChildren, (child:DerivedFEProperty):void => { + flattenProperties.push(new SimpleFlatProperty(child.uniqueId, child.propertiesName, child.name, child.parentName, instanceName)); + }); + + let tree = this.unflatten(flattenProperties, '', []); + return tree[0].childrens; // Return the childrens without the root. + } + + /** + * Unflatten Array<SimpleFlatProperty> and build hirarchy. + * The result will be Array<SimpleFlatProperty> that augmantin with childrens for each SimpleFlatProperty. + */ + private unflatten( array:Array<SimpleFlatProperty>, parent:any, tree?:any ):any { + tree = typeof tree!=='undefined' ? tree : []; + parent = typeof parent!=='undefined' && parent!=='' ? parent : { path: '' }; + + var childrens = _.filter( array, (child:SimpleFlatProperty):boolean => { + return child.parentName == parent.path; + }); + + if( !_.isEmpty( childrens ) ){ + if( parent.path == '' ){ + tree = childrens; + } else { + parent['childrens'] = childrens; + } + _.each( childrens, ( child ):void => { + this.unflatten( array, child ); + }); + } + return tree; + } + + // TODO: To remove + // public convertPropertiesToFEAndInitialize(properties: Array<PropertyBEModel>): Array<PropertyFEModel> { + // let props:Array<PropertyFEModel> = []; + // _.forEach(properties, (property: PropertyFEModel, index: number) => { + // props[index] = new PropertyFEModel(property); + // this.initValueObjectRef(props[index]); + // if (props[index].isDataType || + // ((props[index].type === PROPERTY_TYPES.MAP || props[index].type === PROPERTY_TYPES.LIST) && props[index].schema.property.isDataType)) { + // props[index].valueObjectRef = props[index].valueObjectRef || {}; + // let defaultValueObject:any = props[index].defaultValue ? JSON.parse(props[index].defaultValue): null; + // this.createPropertiesTreeForProp(props[index], true, defaultValueObject); + // } + // }); + // return props; + // } + + + /** + * Converts a property's map values to properties and adds them to property.childrenProperties + * @param property - property to add children to + * @param onlyFirstLevel - recursively retrieve properties for each dataType? + */ + //TODO: To remove + // public createPropertyNodesForMapOfDataTypes(property: PropertyFEModel, onlyFirstLevel:boolean):void { + // property.childrenProperties = []; + // angular.forEach(property.valueObjectRef,function(itemInMap:any, keyInMap:string){ + // let newProperty: PropertyFEModel = new PropertyFEModel(keyInMap, + // property.schema.property.type, + // UUID.UUID(), + // property, + // property.valueObjectRef[keyInMap]); + // !onlyFirstLevel && this.createPropertiesTreeForProp(newProperty); + // property.childrenProperties.push(newProperty); + // },this); + // } + + + /** + * Converts a property's list values to properties and adds them to property.childrenProperties + * @param property - property to add children to + * @param onlyFirstLevel - recursively retrieve properties for each dataType? + */ + //TODO: To remove + // public createPropertyNodesForListOfDataTypes(property: PropertyFEModel, onlyFirstLevel:boolean):void { + // property.childrenProperties = []; + // property.valueObjectRef.forEach((itemInList:any, index:number):void =>{ + // let newProperty: PropertyFEModel = new PropertyFEModel(this.contentAfterLastDotPipe.transform(property.schema.property.type), + // property.schema.property.type, + // UUID.UUID(), + // property, + // property.valueObjectRef[index]); + // !onlyFirstLevel && this.createPropertiesTreeForProp(newProperty); + // property.childrenProperties.push(newProperty); + // }); + // } + + // private checkIfPropertyDerivedFromSimpleAndUpdateProp(property:PropertyFEModel | SchemaProperty): boolean{ + // property.derivedFromSimpleTypeName = this.dataTypeService.getTypeForDataTypeDerivedFromSimple(property.type); + // if(property.derivedFromSimpleTypeName){ + // property.isSimpleType = true; + // property.isDataType = false; + // } + // return property.isSimpleType; + // } + + // TODO: Remove + public createPropertiesTreeForProp(property: PropertyFEModel, onlyFirstLevel?: boolean, defaultValueObj?: any): void { + } + + public getPropertyFEModelFromDerivedPropertyUniqueId(properties: Array<PropertyFEModel>, uniqueId: string): PropertyFEModel { + // _.each(properties, (property):void => { + // property.flattenedChildren + + // }); + + // let rootProperty = _.find(properties, (property):boolean => { return property.uniqueId === uniqueId; }); + // if + return null; + } + + /** + * Utilizes the dataTypeService to retrieve children properties from dataTypes recursively. + * @param property + * @param onlyFirstLevel + */ + // TODO: To remove + // public createPropertiesTreeForProp(property: PropertyFEModel, onlyFirstLevel?:boolean, defaultValueObj?:any ):void{ + // if (property.isDataType && !this.checkIfPropertyDerivedFromSimpleAndUpdateProp(property)){ + // let dataType: DataTypeModel = this.dataTypeService.getDataTypeByTypeName(property.type); + // property.childrenProperties = []; + // let childrenProperties = this.dataTypeService.getDataTypePropertiesRecursively(dataType); + // childrenProperties.forEach((childProperty: PropertyBEModel):void => { + // let childDataTypePropertyObj: PropertyFEModel = new PropertyFEModel(childProperty.name, childProperty.type,UUID.UUID(),property, {}, childProperty.schema); + // //init empty object in valueObjectRef[childProperty.name] because this property's children should has ref to this obj. + // if(!property.valueObjectRef[childDataTypePropertyObj.name]){ + // if ((childDataTypePropertyObj.isDataType && !this.checkIfPropertyDerivedFromSimpleAndUpdateProp(childDataTypePropertyObj)) + // || childDataTypePropertyObj.type === PROPERTY_TYPES.MAP){ + // property.valueObjectRef[childDataTypePropertyObj.name] = {}; + // }else if(childDataTypePropertyObj.type === PROPERTY_TYPES.LIST){ + // property.valueObjectRef[childDataTypePropertyObj.name] = []; + // } + // } + // childDataTypePropertyObj.valueObjectRef = property.valueObjectRef[childDataTypePropertyObj.name]; + // property.valueObjectRef[childDataTypePropertyObj.name] = property.valueObjectRef[childProperty.name] || childDataTypePropertyObj.defaultValue; + // if( !childDataTypePropertyObj.isDataType && defaultValueObj ){//init property default value + // childDataTypePropertyObj.defaultValue = JSON.stringify(defaultValueObj[childDataTypePropertyObj.name]); + // } + // property.childrenProperties.push(childDataTypePropertyObj); + // !onlyFirstLevel && this.createPropertiesTreeForProp(childDataTypePropertyObj, false, (defaultValueObj ? defaultValueObj[childDataTypePropertyObj.name] : null)); + // }); + // } else if (property.type == PROPERTY_TYPES.MAP && property.schema.property.isDataType && !this.checkIfPropertyDerivedFromSimpleAndUpdateProp(property.schema.property)){ + // if( property.valueObjectRef && !_.isEmpty(property.valueObjectRef)){ + // this.createPropertyNodesForMapOfDataTypes(property, onlyFirstLevel); + // } + // } else if (property.type == PROPERTY_TYPES.LIST && property.schema.property.isDataType && !this.checkIfPropertyDerivedFromSimpleAndUpdateProp(property.schema.property)){ + // if( property.valueObjectRef && property.valueObjectRef.length){ + // this.createPropertyNodesForListOfDataTypes(property, onlyFirstLevel); + // } + // } + // } +} + +export class SimpleFlatProperty { + uniqueId:string; + path:string; + name:string; + parentName:string; + instanceName:string; + + constructor(uniqueId?:string, path?:string, name?: string, parentName?:string, instanceName?:string) { + this.uniqueId = uniqueId; + this.path = path; + this.name = name; + this.parentName = parentName; + this.instanceName = instanceName; + } +} diff --git a/catalog-ui/src/app/ng2/services/responses/component-generic-response.ts b/catalog-ui/src/app/ng2/services/responses/component-generic-response.ts new file mode 100644 index 0000000000..7dcd95d712 --- /dev/null +++ b/catalog-ui/src/app/ng2/services/responses/component-generic-response.ts @@ -0,0 +1,77 @@ +/** + * Created by ob0695 on 4/18/2017. + */ + +import { ArtifactGroupModel, PropertyModel, PropertiesGroup, AttributeModel, AttributesGroup, ComponentInstance, + InputModel, Module, ComponentMetadata, RelationshipModel, RequirementsGroup, CapabilitiesGroup,InputFEModel} from "app/models"; +import {CommonUtils} from "app/utils"; +import {Serializable} from "../utils/serializable"; +import {PropertyBEModel} from "../../../models/properties-inputs/property-be-model"; + +export class ComponentGenericResponse implements Serializable<ComponentGenericResponse> { + + public metadata: ComponentMetadata; + public deploymentArtifacts:ArtifactGroupModel; + public artifacts:ArtifactGroupModel; + public toscaArtifacts:ArtifactGroupModel; + public componentInstancesProperties:PropertiesGroup; + public componentInstancesAttributes:AttributesGroup; + public componentInstancesRelations:Array<RelationshipModel>; + public componentInstances:Array<ComponentInstance>; + public inputs:Array<PropertyBEModel>; + public capabilities:CapabilitiesGroup; + public requirements:RequirementsGroup; + public properties:Array<PropertyModel>; + public attributes:Array<AttributeModel>; + public groups:Array<Module>; + public interfaces:any; + public additionalInformation:any; + public derivedList:Array<any>; + + deserialize (response): ComponentGenericResponse { + + if(response.componentInstancesProperties) { + this.componentInstancesProperties = new PropertiesGroup(response.componentInstancesProperties); + } + if(response.componentInstancesAttributes) { + this.componentInstancesAttributes = new AttributesGroup(response.componentInstancesAttributes); + } + if(response.componentInstances) { + this.componentInstances = CommonUtils.initComponentInstances(response.componentInstances); + } + if(response.componentInstancesRelations) { + this.componentInstancesRelations = CommonUtils.initComponentInstanceRelations(response.componentInstancesRelations); + } + if(response.deploymentArtifacts) { + this.deploymentArtifacts = new ArtifactGroupModel(response.deploymentArtifacts); + } + if(response.inputs) { + this.inputs = CommonUtils.initInputs(response.inputs); + } + if(response.attributes) { + this.attributes = CommonUtils.initAttributes(response.attributes); + } + if(response.artifacts) { + this.artifacts = new ArtifactGroupModel(response.artifacts); + } + if(response.properties) { + this.properties = CommonUtils.initProperties(response.properties); + } + if(response.capabilities) { + this.capabilities = new CapabilitiesGroup(response.capabilities); + } + if(response.requirements) { + this.requirements = new RequirementsGroup(response.requirements); + } + if(response.toscaArtifacts) { + this.toscaArtifacts = new ArtifactGroupModel(response.toscaArtifacts); + } + if(response.metadata) { + this.metadata = new ComponentMetadata().deserialize(response.metadata); + } + if(response.groups) { + this.groups = CommonUtils.initModules(response.groups); + } + return this; + } +} diff --git a/catalog-ui/src/app/ng2/services/responses/properties.response.ts b/catalog-ui/src/app/ng2/services/responses/properties.response.ts new file mode 100644 index 0000000000..a3d82500eb --- /dev/null +++ b/catalog-ui/src/app/ng2/services/responses/properties.response.ts @@ -0,0 +1,7 @@ +export class PropertiesResponse { + properties: Array<Property>; +} + +class Property { + name: string +} diff --git a/catalog-ui/src/app/ng2/services/utils/serializable.ts b/catalog-ui/src/app/ng2/services/utils/serializable.ts new file mode 100644 index 0000000000..f8be120613 --- /dev/null +++ b/catalog-ui/src/app/ng2/services/utils/serializable.ts @@ -0,0 +1,6 @@ +/** + * Created by ob0695 on 4/26/2017. + */ +export interface Serializable<T> { + deserialize(input: Object): T; +} diff --git a/catalog-ui/src/app/ng2/shared/checkbox/checkbox.component.html b/catalog-ui/src/app/ng2/shared/checkbox/checkbox.component.html new file mode 100644 index 0000000000..efe830d6e0 --- /dev/null +++ b/catalog-ui/src/app/ng2/shared/checkbox/checkbox.component.html @@ -0,0 +1,8 @@ +<div class="checkbox-container {{checkboxStyle}}"> + <div class="checkbox-animation" [@checkEffect]="checked"></div> + <label class="checkbox-label" > + <input type="checkbox" class="checkbox-hidden" [ngModel]="checked" (ngModelChange)="toggleState($event)" [disabled]="disabled" /> + <div class="checkbox-icon"></div> + <span *ngIf="label" class="checkbox-label-content">{{label}}</span> + </label> +</div>
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/shared/checkbox/checkbox.component.less b/catalog-ui/src/app/ng2/shared/checkbox/checkbox.component.less new file mode 100644 index 0000000000..7ed8a22194 --- /dev/null +++ b/catalog-ui/src/app/ng2/shared/checkbox/checkbox.component.less @@ -0,0 +1,64 @@ + @import '../../../../assets/styles/tlv-sprite'; + +.checkbox-container { + display:inline-block; + position:relative; + text-align: left; + height: 20px; + + + .checkbox-icon { + display: inline-block; + } + + .checkbox-label { + font-weight: normal; + font-size: inherit; + } + + .checkbox-label-content { + margin-left:2px; + } + + .checkbox-icon::before { + .tlv-sprite; + background-position: -10px -60px; + width: 14px; + height: 14px; + content: ''; + display: inline-block; + margin-right: 0px; + margin-top: -2px; + vertical-align: middle; + } + + input[type=checkbox].checkbox-hidden { + width:0; + height:0; + display:none; + &:checked ~ .checkbox-icon::before{ + background-position: -10px -120px; + } + &[disabled] ~ .checkbox-icon::before { + /* TODO: add disabled styles here */ + background-image: none; + background-color: #EFEFEF; + border-radius: 2px; + border: solid #CCC 1px; + } + } + + .checkbox-animation { + background-color: #009fdb; + position: absolute; + left: 2px; + top: 5px; + width:10px; + height:10px; + border-radius: 50%; + z-index: 1; + pointer-events: none; + opacity:0; + } + +} diff --git a/catalog-ui/src/app/ng2/shared/checkbox/checkbox.component.ts b/catalog-ui/src/app/ng2/shared/checkbox/checkbox.component.ts new file mode 100644 index 0000000000..5a9954c336 --- /dev/null +++ b/catalog-ui/src/app/ng2/shared/checkbox/checkbox.component.ts @@ -0,0 +1,30 @@ +import { Component, Input, Output, EventEmitter, ViewEncapsulation } from '@angular/core'; +import { trigger, state, style, transition, animate, keyframes } from '@angular/core'; + +@Component({ + selector: 'checkbox', + templateUrl: './checkbox.component.html', + styleUrls: ['./checkbox.component.less'], + encapsulation: ViewEncapsulation.None, + animations: [ + trigger('checkEffect', [ + state('true', style({ position: 'absolute', left: '2px', top: '5px', width: '10px', height: '10px', display: 'none', opacity: '.5' })), + state('false', style({ left: '-18px', top: '-15px', height: '50px', width: '50px', opacity: '0' })), + transition('1 => 0', animate('150ms ease-out')), + transition('0 => 1', animate('150ms ease-in')) + ]) + ] +}) +export class CheckboxComponent { + + @Input() checkboxStyle: string; + @Input() label: string; + @Input() checked: boolean; + @Input() disabled: boolean; + @Output() checkedChange: EventEmitter<any> = new EventEmitter<any>(); + + toggleState(newValue:boolean) { + this.checkedChange.emit(newValue); + } +} + diff --git a/catalog-ui/src/app/ng2/shared/checkbox/checkbox.module.ts b/catalog-ui/src/app/ng2/shared/checkbox/checkbox.module.ts new file mode 100644 index 0000000000..116aa7f025 --- /dev/null +++ b/catalog-ui/src/app/ng2/shared/checkbox/checkbox.module.ts @@ -0,0 +1,28 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { BrowserModule } from '@angular/platform-browser'; +import { CheckboxComponent } from './checkbox.component'; + + +@NgModule({ + imports: [CommonModule, BrowserModule, FormsModule], + declarations: [CheckboxComponent], + bootstrap: [], + exports: [CheckboxComponent] +}) +export class CheckboxModule { } + +/** README: **/ + +/** USAGE Example: + *In page.module.ts: import CheckboxModule + *In HTML: + *<checkbox checkboxStyle="class-goes-here" [label]="'Text goes here'" [disabled]="variable-goes-here" [(checked)]="default-or-variable-goes-here" (checkedChange)="change-event-goes-here()"></checkbox> + */ + +/**STYLING: (ViewEncapsulation is set to None to allow styles to be overridden or customized) + * + * To create or override styles: + * Use /deep/ or >>> prefix to override styles via other components stylesheets + */
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/shared/navbar/navbar-routes.config.ts b/catalog-ui/src/app/ng2/shared/navbar/navbar-routes.config.ts new file mode 100644 index 0000000000..d8a21e66c8 --- /dev/null +++ b/catalog-ui/src/app/ng2/shared/navbar/navbar-routes.config.ts @@ -0,0 +1,7 @@ +import { MenuType, RouteInfo } from './navbar.metadata'; + +export const ROUTES: RouteInfo[] = [ + { path: 'page1', title: 'Logo', menuType: MenuType.BRAND }, + { path: 'page1', title: 'Page 1', menuType: MenuType.LEFT }, + { path: 'page2', title: 'Page 2', menuType: MenuType.LEFT } +]; diff --git a/catalog-ui/src/app/ng2/shared/navbar/navbar.component.html b/catalog-ui/src/app/ng2/shared/navbar/navbar.component.html new file mode 100644 index 0000000000..d783be4c27 --- /dev/null +++ b/catalog-ui/src/app/ng2/shared/navbar/navbar.component.html @@ -0,0 +1,23 @@ +<nav class="navbar navbar-dark"> + <div class="clearfix"> + <button (click)="isCollapsed = !isCollapsed" + class="navbar-toggler pull-xs-right hidden-sm-up" type="button" + aria-controls="bd-main-nav" + aria-label="Toggle navigation"> + {{menuIcon}} + </button> + <a (click)="isCollapsed = true" class="navbar-brand hidden-sm-up" [routerLink]="[brandMenu.path]"> + {{brandMenu.title}} + </a> + </div> + <div class="navbar-toggleable-xs navbar-collapse" id="bd-main-nav" [attr.aria-expanded]="!isCollapsed" [ngClass]="{collapse: isCollapsed}"> + <ul class="nav navbar-nav"> + <li (click)="isCollapsed = true" class="nav-item" routerLinkActive="active"> + <a class="navbar-brand hidden-xs-down" [routerLink]="[brandMenu.path]">{{brandMenu.title}}</a> + </li> + <li (click)="isCollapsed = true" *ngFor="let menuItem of menuItems" class="nav-item" routerLinkActive="active" [ngClass]="getMenuItemClasses(menuItem)"> + <a class="nav-item nav-link" [routerLink]="[menuItem.path]" routerLinkActive="active">{{menuItem.title}}</a> + </li> + </ul> + </div> +</nav> diff --git a/catalog-ui/src/app/ng2/shared/navbar/navbar.component.less b/catalog-ui/src/app/ng2/shared/navbar/navbar.component.less new file mode 100644 index 0000000000..3e5165b798 --- /dev/null +++ b/catalog-ui/src/app/ng2/shared/navbar/navbar.component.less @@ -0,0 +1,11 @@ +.active { + color: #ffffff; +} +.navbar-toggler { + border: solid 1px #cccccc; + color: #ff0000; +} +.navbar { + background-color: #0000ff; + border-radius: 0; +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/shared/navbar/navbar.component.ts b/catalog-ui/src/app/ng2/shared/navbar/navbar.component.ts new file mode 100644 index 0000000000..b174f9d18d --- /dev/null +++ b/catalog-ui/src/app/ng2/shared/navbar/navbar.component.ts @@ -0,0 +1,32 @@ +import {Component, OnInit, ViewEncapsulation} from '@angular/core'; +import { ROUTES } from './navbar-routes.config'; +import { MenuType, RouteInfo } from './navbar.metadata'; + +@Component({ + selector: 'app-navbar', + templateUrl: './navbar.component.html', + styleUrls: [ './navbar.component.less' ], + encapsulation: ViewEncapsulation.None +}) +export class NavbarComponent implements OnInit { + public menuItems: Array<RouteInfo>; + public brandMenu: RouteInfo; + isCollapsed = true; + + constructor() {} + + ngOnInit() { + this.menuItems = ROUTES.filter(menuItem => menuItem.menuType !== MenuType.BRAND); + this.brandMenu = ROUTES.filter(menuItem => menuItem.menuType === MenuType.BRAND)[0]; + } + + public get menuIcon(): string { + return this.isCollapsed ? '☰' : '✖'; + } + + public getMenuItemClasses(menuItem: any) { + return { + 'pull-xs-right': this.isCollapsed && menuItem.menuType === MenuType.RIGHT + }; + } +} diff --git a/catalog-ui/src/app/ng2/shared/navbar/navbar.metadata.ts b/catalog-ui/src/app/ng2/shared/navbar/navbar.metadata.ts new file mode 100644 index 0000000000..245d0e6cfe --- /dev/null +++ b/catalog-ui/src/app/ng2/shared/navbar/navbar.metadata.ts @@ -0,0 +1,11 @@ +export enum MenuType { + BRAND, + LEFT, + RIGHT +} + +export interface RouteInfo { + path: string; + title: string; + menuType: MenuType; +} diff --git a/catalog-ui/src/app/ng2/shared/navbar/navbar.module.ts b/catalog-ui/src/app/ng2/shared/navbar/navbar.module.ts new file mode 100644 index 0000000000..18120a61fb --- /dev/null +++ b/catalog-ui/src/app/ng2/shared/navbar/navbar.module.ts @@ -0,0 +1,16 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { RouterModule } from '@angular/router'; +import { NavbarComponent } from "./navbar.component"; + +@NgModule({ + imports: [ + RouterModule, + CommonModule + ], + declarations: [ NavbarComponent ], + exports: [ + NavbarComponent + ] +}) +export class NavbarModule {} diff --git a/catalog-ui/src/app/ng2/shared/shared.module.ts b/catalog-ui/src/app/ng2/shared/shared.module.ts new file mode 100644 index 0000000000..3e59e04441 --- /dev/null +++ b/catalog-ui/src/app/ng2/shared/shared.module.ts @@ -0,0 +1,20 @@ +import { NgModule } from '@angular/core'; + +import { CommonModule } from '@angular/common'; +import { RouterModule } from '@angular/router'; +import { NavbarModule } from "./navbar/navbar.module"; + +@NgModule({ + declarations: [ + + ], + imports: [ + CommonModule, + RouterModule, + NavbarModule + ], + exports: [ + ] +}) + +export class SharedModule {} diff --git a/catalog-ui/src/app/ng2/shared/tabs/tab/tab.component.ts b/catalog-ui/src/app/ng2/shared/tabs/tab/tab.component.ts new file mode 100644 index 0000000000..06dcfa0b16 --- /dev/null +++ b/catalog-ui/src/app/ng2/shared/tabs/tab/tab.component.ts @@ -0,0 +1,18 @@ +import { Component, Input } from '@angular/core'; +import { ViewEncapsulation } from '@angular/core'; + +@Component({ + selector: 'tab', + template: ` + <div *ngIf="active" class="tab-content"> + <ng-content></ng-content> + </div> + `, + encapsulation: ViewEncapsulation.None +}) +export class Tab { + @Input('tabTitle') title: string; + @Input() active:boolean = false; + @Input() indication?: number; + +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/shared/tabs/tabs.component.html b/catalog-ui/src/app/ng2/shared/tabs/tabs.component.html new file mode 100644 index 0000000000..3e263a3862 --- /dev/null +++ b/catalog-ui/src/app/ng2/shared/tabs/tabs.component.html @@ -0,0 +1,9 @@ +<div class="tabs {{tabStyle}}"> + <div class="tab" *ngFor="let tab of tabs" (click)="selectTab(tab)" [class.active]="tab.active"> + {{tab.title}} + <div class="tab-indication" *ngIf="tab.indication" [@indicatorAnimation]="tab.indication">{{tab.indication}}</div> + </div> +</div> +<div class="tab-content-container"> + <ng-content></ng-content> +</div>
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/shared/tabs/tabs.component.less b/catalog-ui/src/app/ng2/shared/tabs/tabs.component.less new file mode 100644 index 0000000000..aa59428a64 --- /dev/null +++ b/catalog-ui/src/app/ng2/shared/tabs/tabs.component.less @@ -0,0 +1,85 @@ +@import '../../../../assets/styles/variables'; + +.tabs { + display:flex; + flex: 0 0 auto; + flex-direction:row; +} + +.tab { + flex: 1 0 auto; + cursor: pointer; + padding: .5em; +} + +.tab-content-container { + flex: 1; + height: 100%; + overflow: hidden; + width:100%; +} + +.tab-content { + height:100%; +} + +/*Tab styles*/ +.tabs{ + &.round-tabs .tab{ + background-color: #f8f8f8; + color: #959595; + border: solid 1px #d2d2d2; + border-bottom:none; + border-left:none; + position:relative; + + &:first-child { + border-top-left-radius: 8px; + border-left:solid 1px #d2d2d2; + } + &:last-child { + border-top-right-radius: 8px; + } + + &.active { + background-color:#009fdb; + color:#e9e9e9; + border-color:#009fdb; + } + + .tab-indication { + position: absolute; + top: -10px; + background-color: #009fdb; + right: 10px; + padding: 2px 0; + border-radius: 15px; + border: solid 1px #d2d2d2; + color:white; + width: 25px; + height: 25px; + text-align: center; + + } + } + + &.simple-tabs .tab { + font-size: 12px; + color: @main_color_n; + + &:after { + display:block; + content: ''; + border-bottom: 2px solid @main_color_a; + transform: scaleX(0); + transition: transform 200ms ease-in-out; + } + + &.active { + color: @main_color_a; + &:after { + transform: scaleX(1); + } + } + } +} diff --git a/catalog-ui/src/app/ng2/shared/tabs/tabs.component.ts b/catalog-ui/src/app/ng2/shared/tabs/tabs.component.ts new file mode 100644 index 0000000000..dc5616c6cb --- /dev/null +++ b/catalog-ui/src/app/ng2/shared/tabs/tabs.component.ts @@ -0,0 +1,58 @@ +import { Component, ContentChildren, QueryList, AfterContentInit, Input, Output, EventEmitter } from '@angular/core'; +import { Tab } from './tab/tab.component'; +import { ViewEncapsulation } from '@angular/core'; +import { trigger, state, style, transition, animate, keyframes } from '@angular/core'; + +@Component({ + selector: 'tabs', + templateUrl: './tabs.component.html', + styleUrls: ['./tabs.component.less'], + encapsulation: ViewEncapsulation.None, + animations: [ + trigger('indicatorAnimation', [ + transition(':enter', [style({ transform: 'translateY(-50%)', opacity: 0 }), animate('250ms', style({ transform: 'translateY(0)', opacity: 1 })) ]), + transition(':leave', [style({ opacity: 1 }), animate('500ms', style({ opacity: 0 })) ]) + ]) + ] +}) +export class Tabs implements AfterContentInit { + + @Input() tabStyle: string; + @Input() hideIndicationOnTabChange?: boolean = false; + @ContentChildren(Tab) tabs: QueryList<Tab>; + @Output() tabChanged: EventEmitter<Tab> = new EventEmitter<Tab>(); + + + ngAfterContentInit() { + //after contentChildren are set, determine active tab. If no active tab set, activate the first one + let activeTabs = this.tabs.filter((tab) => tab.active); + + if (activeTabs.length === 0) { + this.selectTab(this.tabs.first); + } + } + + selectTab(tab: Tab) { + //activate the tab the user clicked. + this.tabs.toArray().forEach(tab => { + tab.active = false; + if (this.hideIndicationOnTabChange && tab.indication) { + tab.indication = null; + } + }); + tab.active = true; + this.tabChanged.emit(tab); + } + + triggerTabChange(tabTitle) { + this.tabs.toArray().forEach(tab => { + tab.active = (tab.title == tabTitle) ? true : false; + }); + } + + setTabIndication(tabTitle:string, indication?:number) { + let selectedTab: Tab = this.tabs.toArray().find(tab => tab.title == tabTitle); + selectedTab.indication = indication || null; + } + +} diff --git a/catalog-ui/src/app/ng2/shared/tabs/tabs.module.ts b/catalog-ui/src/app/ng2/shared/tabs/tabs.module.ts new file mode 100644 index 0000000000..36c7335fde --- /dev/null +++ b/catalog-ui/src/app/ng2/shared/tabs/tabs.module.ts @@ -0,0 +1,35 @@ +import { Component, NgModule } from '@angular/core' +import { BrowserModule } from '@angular/platform-browser' + +import { Tabs } from './tabs.component'; +import { Tab } from './tab/tab.component'; + + +@NgModule({ + imports: [BrowserModule], + declarations: [Tabs, Tab], + bootstrap: [], + exports: [Tabs, Tab] +}) +export class TabModule { } + +/** README: **/ + +/** USAGE Example: + *In page.module.ts: import TabModule + *In HTML: + *<tabs tabStyle="class-goes-here" (tabChanged)="tabChangedEvent($event) [hideIndicationOnTabChange]="optional-boolean"> + * <tab [tabTitle]="'Tab 1'">Content of tab 1</tab> + * <tab tabTitle="Tab 2" >Content of tab 2</tab> + * ... + *</tabs> + */ + +/**STYLING: (ViewEncapsulation is set to None to allow styles to be overridden or customized) + * Existing options: + * tabStyle="round-tabs" will provide generic rounded tab look + * + * To create or override styles: + * Parent div has class ".tabs". Each tab has class ".tab". Active tab has class ".active". + * Use /deep/ or >>> prefix to override styles via other components stylesheets + */
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/utils/ng1-upgraded-provider.ts b/catalog-ui/src/app/ng2/utils/ng1-upgraded-provider.ts new file mode 100644 index 0000000000..c8c1d9b721 --- /dev/null +++ b/catalog-ui/src/app/ng2/utils/ng1-upgraded-provider.ts @@ -0,0 +1,51 @@ +/** + * Created by rc2122 on 4/6/2017. + */ +import {DataTypesService} from "../../services/data-types-service"; +import ICacheObject = angular.ICacheObject; +import {SharingService} from "../../services/sharing-service"; +import {CookieService} from "../../services/cookie-service"; + +/** Services we need to upgrade from angular1 to angular2 - in the future we need to rewrite them all to angular2 **/ + +export function dataTypesServiceFactory(cacheObj: ICacheObject) { + return cacheObj.get('Sdc.Services.DataTypesService'); +} + +export function sharingServiceFactory(cacheObj: ICacheObject) { + return cacheObj.get('Sdc.Services.SharingService'); +} + +export function cookieServiceFactory(cacheObj: ICacheObject) { + return cacheObj.get('Sdc.Services.CookieService'); +} + +export function stateParamsServiceFactory(cacheObj: ICacheObject) { + return cacheObj.get('$stateParams'); +} + +export const DataTypesServiceProvider = { + provide: DataTypesService, + useFactory: dataTypesServiceFactory, + deps: ['$injector'] +}; + + +export const SharingServiceProvider = { + provide: SharingService, + useFactory: sharingServiceFactory, + deps: ['$injector'] +}; + + +export const CookieServiceProvider = { + provide: CookieService, + useFactory: cookieServiceFactory, + deps: ['$injector'] +}; + +export const StateParamsServiceFactory = { + provide: '$stateParams', + useFactory: stateParamsServiceFactory, + deps: ['$injector'] +}; diff --git a/catalog-ui/src/app/services.ts b/catalog-ui/src/app/services.ts new file mode 100644 index 0000000000..5b4358a90c --- /dev/null +++ b/catalog-ui/src/app/services.ts @@ -0,0 +1,32 @@ +/** + * Created by ob0695 on 2/23/2017. + */ +export * from './services/activity-log-service'; +export * from './services/available-icons-service'; +export * from './services/cache-service'; +export * from './services/configuration-ui-service'; +export * from './services/category-resource-service'; +export * from './services/components/component-service'; +export * from './services/components/product-service'; +export * from './services/components/resource-service'; +export * from './services/components/service-service'; +export * from './services/components/resource-service'; +export * from './services/components/utils/composition-left-palette-service'; +export * from './services/components/resource-service'; +export * from './services/components/resource-service'; +export * from './services/category-resource-service'; +export * from './services/cookie-service'; +export * from './services/data-types-service'; +export * from './services/ecomp-service'; +export * from './services/entity-service'; +export * from './services/event-listener-service'; +export * from './services/header-interceptor'; +export * from './services/loader-service'; +export * from './services/onboarding-service'; +export * from './services/progress-service'; +export * from './services/sdc-version-service'; +export * from './services/sharing-service'; +export * from './services/url-tobase64-service'; +export * from './services/user-resource-service'; +export * from './services/angular-js-bridge-service'; + diff --git a/catalog-ui/src/app/services/activity-log-service.ts b/catalog-ui/src/app/services/activity-log-service.ts new file mode 100644 index 0000000000..565dc9a39c --- /dev/null +++ b/catalog-ui/src/app/services/activity-log-service.ts @@ -0,0 +1,28 @@ +'use strict'; +import {Activity} from "../models/activity"; +import {IAppConfigurtaion, IApi} from "../models/app-config"; + +// 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<Activity>>; +} + +export class ActivityLogService implements IActivityLogService { + + + static '$inject' = ['$http', '$q', 'sdcConfig']; + private api:IApi; + + constructor(private $http:ng.IHttpService, private $q:ng.IQService, sdcConfig:IAppConfigurtaion) { + this.api = sdcConfig.api; + } + + getActivityLogService = (type:string, id:string):ng.IPromise<Array<Activity>> => { + let defer = this.$q.defer<any>(); + this.$http.get(this.api.root + this.api.GET_activity_log.replace(':type', type).replace(':id', id)) + .then((activityLog:any) => { + defer.resolve(activityLog.data); + }); + return defer.promise; + } +} diff --git a/catalog-ui/src/app/services/angular-js-bridge-service.ts b/catalog-ui/src/app/services/angular-js-bridge-service.ts new file mode 100644 index 0000000000..1eaea878dd --- /dev/null +++ b/catalog-ui/src/app/services/angular-js-bridge-service.ts @@ -0,0 +1,23 @@ +'use strict'; +import {IAppConfigurtaion} from "../models/app-config"; + +export class AngularJSBridge { + private static _$filter:ng.IFilterService; + private static _sdcConfig:IAppConfigurtaion; + + public static getFilter(filterName:string) { + return AngularJSBridge._$filter(filterName); + } + + public static getAngularConfig() { + return AngularJSBridge._sdcConfig; + } + + + constructor($filter:ng.IFilterService, sdcConfig:IAppConfigurtaion) { + AngularJSBridge._$filter = $filter; + AngularJSBridge._sdcConfig = sdcConfig; + } +} + +AngularJSBridge.$inject = ['$filter', 'sdcConfig'] diff --git a/catalog-ui/src/app/services/available-icons-service.ts b/catalog-ui/src/app/services/available-icons-service.ts new file mode 100644 index 0000000000..ad17b069d6 --- /dev/null +++ b/catalog-ui/src/app/services/available-icons-service.ts @@ -0,0 +1,87 @@ +/** + * Created by obarda on 2/23/2016. + */ +'use strict'; +import {ComponentType} from "../utils/constants"; + +interface IAvailableIconsService { + getIcons(componentType:ComponentType):Array<string>; +} + +export class AvailableIconsService implements IAvailableIconsService { + constructor() { + } + + public getIcons = (componentType:string):Array<string> => { + + let icons:Array<string>; + + switch (componentType) { + case ComponentType.SERVICE: + icons = [ + 'call_controll', + 'mobility', + 'network_l_1-3', + 'network_l_4' + ]; + break; + + case 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 ComponentType.PRODUCT: + icons = [ + 'vfw', + 'network', + 'security', + 'cloud', + 'setting', + 'orphan', + 'wanx', + 'vrouter', + 'ucpe', + 'mobility' + + ]; + break; + + } + return icons; + } +} + + + diff --git a/catalog-ui/src/app/services/cache-service.ts b/catalog-ui/src/app/services/cache-service.ts new file mode 100644 index 0000000000..9ee08c8478 --- /dev/null +++ b/catalog-ui/src/app/services/cache-service.ts @@ -0,0 +1,34 @@ +'use strict'; +import {Dictionary} from "app/utils"; + +interface ICacheService { + get(key:string):any; + set(key:string, value:any):void; +} + +export class CacheService implements ICacheService { + + private storage:Dictionary<string, any>; + + constructor() { + this.storage = new 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/src/app/services/category-resource-service.ts b/catalog-ui/src/app/services/category-resource-service.ts new file mode 100644 index 0000000000..a2e3c61e0a --- /dev/null +++ b/catalog-ui/src/app/services/category-resource-service.ts @@ -0,0 +1,61 @@ +'use strict'; +// import 'angular-resource'; +import {IAppConfigurtaion, IMainCategory, IUserProperties} from "../models"; +import {Categories} from "../models/categories"; + +// Define an interface of the object you want to use, providing it's properties +export interface ICategoryResource extends IUserProperties,ng.resource.IResource<ICategoryResource> { + name:string; + uniqueId:string; + subcategories:Array<ICategoryResource>; + + getAllCategories(params?:Object, success?:Function, error?:Function):Categories; + $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):Categories; + 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:IAppConfigurtaion):ICategoryResourceClass => { + + // Define your custom actions here as IActionDescriptor + let getAllCategoriesAction:ng.resource.IActionDescriptor = { + method: 'GET', + isArray: false, + 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/src/app/services/components/component-service.ts b/catalog-ui/src/app/services/components/component-service.ts new file mode 100644 index 0000000000..1a6b9a8a14 --- /dev/null +++ b/catalog-ui/src/app/services/components/component-service.ts @@ -0,0 +1,755 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ +'use strict'; +import {ArtifactModel, IFileDownload, InstancesInputsPropertiesMap, InputModel, IValidate, RelationshipModel, PropertyModel, Component, ComponentInstance, + AttributeModel, IAppConfigurtaion, Resource, Module, DisplayModule, ArtifactGroupModel, InputsAndProperties} from "app/models"; +import {ComponentInstanceFactory, CommonUtils} from "app/utils"; +import {SharingService} from "../sharing-service"; + +export interface IComponentService { + + getComponent(id:string); + updateComponent(component:Component):ng.IPromise<Component>; + changeLifecycleState(component:Component, state:string, userRemarks:any):ng.IPromise<Component> ; + validateName(newName:string, subtype?:string):ng.IPromise<IValidate>; + createComponent(component:Component):ng.IPromise<Component>; + addOrUpdateArtifact(componentId:string, artifact:ArtifactModel):ng.IPromise<ArtifactModel>; + deleteArtifact(componentId:string, artifact:string, artifactLabel):ng.IPromise<ArtifactModel>; + addProperty(componentId:string, property:PropertyModel):ng.IPromise<PropertyModel>; + updateProperty(componentId:string, property:PropertyModel):ng.IPromise<PropertyModel>; + addAttribute(componentId:string, attribute:AttributeModel):ng.IPromise<AttributeModel>; + updateAttribute(componentId:string, attribute:AttributeModel):ng.IPromise<AttributeModel>; + deleteProperty(componentId:string, propertyId:string):ng.IPromise<PropertyModel>; + deleteAttribute(componentId:string, attributeId:string):ng.IPromise<AttributeModel>; + changeResourceInstanceVersion(componentId:string, componentInstanceId:string, componentUid:string):ng.IPromise<ComponentInstance>; + updateInstanceArtifact(componentId:string, instanceId:string, artifact:ArtifactModel):ng.IPromise<ArtifactModel>; + addInstanceArtifact(componentId:string, instanceId:string, artifact:ArtifactModel):ng.IPromise<ArtifactModel>; + deleteInstanceArtifact(componentId:string, instanceId:string, artifact:string, artifactLabel):ng.IPromise<ArtifactModel>; + createComponentInstance(componentId:string, componentInstance:ComponentInstance):ng.IPromise<ComponentInstance>; + updateComponentInstance(componentId:string, componentInstance:ComponentInstance):ng.IPromise<ComponentInstance>; + updateMultipleComponentInstances(componentId:string, instances:Array<ComponentInstance>):ng.IPromise< Array<ComponentInstance>>; + downloadArtifact(componentId:string, artifactId:string):ng.IPromise<IFileDownload>; + uploadInstanceEnvFile(componentId:string, instanceId:string, artifact:ArtifactModel):ng.IPromise<ArtifactModel>; + downloadInstanceArtifact(componentId:string, instanceId:string, artifactId:string):ng.IPromise<IFileDownload>; + deleteComponentInstance(componentId:string, componentInstanceId:string):ng.IPromise<ComponentInstance>; + createRelation(componentId:string, link:RelationshipModel):ng.IPromise<RelationshipModel>; + deleteRelation(componentId:string, link:RelationshipModel):ng.IPromise<RelationshipModel>; + getRequirementsCapabilities(componentId:string):ng.IPromise<any>; + updateInstanceProperty(componentId:string, property:PropertyModel):ng.IPromise<PropertyModel>; + updateInstanceAttribute(componentId:string, attribute:AttributeModel):ng.IPromise<AttributeModel>; + getComponentInstancesFilteredByInputsAndProperties(componentId:string, searchText:string):ng.IPromise<Array<ComponentInstance>> + getComponentInstanceInputs(componentId:string, instanceId:string, originComponentUid):ng.IPromise<Array<InputModel>>; + getComponentInputs(componentId:string):ng.IPromise<Array<InputModel>>; + getComponentInstanceInputProperties(componentId:string, instanceId:string, inputId:string):ng.IPromise<Array<PropertyModel>>; + getComponentInstanceProperties(componentId:string, instanceId:string):ng.IPromise<Array<PropertyModel>>; + getModuleForDisplay(componentId:string, moduleId:string):ng.IPromise<DisplayModule>; + getComponentInstanceModule(componentId:string, componentInstanceId:string, moduleId:string):ng.IPromise<DisplayModule>; + updateGroupMetadata(componentId:string, group:Module):ng.IPromise<Module>; + getComponentInputInputsAndProperties(serviceId:string, input:string):ng.IPromise<InputsAndProperties>; + createInputsFromInstancesInputs(serviceId:string, instancesInputsMap:InstancesInputsPropertiesMap):ng.IPromise<Array<InputModel>>; + createInputsFromInstancesInputsProperties(resourceId:string, instanceInputsPropertiesMap:InstancesInputsPropertiesMap):ng.IPromise<Array<PropertyModel>>; + deleteComponentInput(serviceId:string, inputId:string):ng.IPromise<InputModel>; + getArtifactByGroupType(componentId:string, artifactGroupType:string):ng.IPromise<ArtifactGroupModel>; + getComponentInstanceArtifactsByGroupType(componentId:string, componentInstanceId:string, artifactGroupType:string):ng.IPromise<ArtifactGroupModel>; +} + +export class ComponentService implements IComponentService { + + static '$inject' = [ + 'Restangular', + 'sdcConfig', + 'Sdc.Services.SharingService', + '$q', + '$base64' + ]; + + constructor(protected restangular:restangular.IElement, + protected sdcConfig:IAppConfigurtaion, + protected sharingService:SharingService, + protected $q:ng.IQService, + protected $base64:any + ) { + + 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:Component):Component => { + return component; + }; + + public getComponent = (id:string):ng.IPromise<Component> => { + let deferred = this.$q.defer(); + this.restangular.one(id).get().then((response:Component) => { + let component:Component = this.createComponentObject(response); + //console.log("Component Loaded successfully : ", component); + deferred.resolve(component); + }, (err)=> { + console.log("Failed to load component with ID: " + id); + deferred.reject(err); + }); + return deferred.promise; + }; + + public updateComponent = (component:Component):ng.IPromise<Component> => { + // If this is resource + if (component instanceof Resource) { + let resource:Resource = <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:Component):ng.IPromise<Component> => { + let deferred = this.$q.defer(); + this.restangular.one(component.uniqueId).one("metadata").customPUT(JSON.stringify(component)).then((response:Component) => { + let component:Component = this.createComponentObject(response); + deferred.resolve(component); + }, (err)=> { + deferred.reject(err); + }); + return deferred.promise; + }; + + private updateResource = (component:Component):ng.IPromise<Component> => { + let deferred = this.$q.defer(); + this.restangular.one(component.uniqueId).customPUT(JSON.stringify(component)).then((response:Component) => { + let component:Component = this.createComponentObject(response); + deferred.resolve(component); + }, (err)=> { + deferred.reject(err); + }); + return deferred.promise; + }; + + private updateResourceMetadata = (component:Component):ng.IPromise<Component> => { + let deferred = this.$q.defer(); + this.restangular.one(component.uniqueId).one('metadata').customPUT(JSON.stringify(component)).then((response:Component) => { + let component: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:Resource):ng.IPromise<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:Component) => { + let componentResult:Component = this.createComponentObject(response); + deferred.resolve(componentResult); + }, (err)=> { + deferred.reject(err); + }); + + return deferred.promise; + }; + + public createComponent = (component:Component):ng.IPromise<Component> => { + let deferred = this.$q.defer(); + let headerObj = this.getHeaderMd5(component); + this.restangular.customPOST(JSON.stringify(component), '', {}, headerObj).then((response:Component) => { + let component:Component = this.createComponentObject(response); + deferred.resolve(component); + }, (err)=> { + deferred.reject(err); + }); + return deferred.promise; + }; + + public validateName = (newName:string, subtype?:string):ng.IPromise<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:Component, state:string, userRemarks:any):ng.IPromise<Component> => { + let deferred = this.$q.defer(); + this.restangular.one(component.uniqueId).one(state).customPOST(userRemarks).then((response:Component) => { + this.sharingService.addUuidValue(response.uniqueId, response.uuid); + let component:Component = this.createComponentObject(response); + deferred.resolve(component); + }, (err)=> { + deferred.reject(err); + }); + return deferred.promise; + }; + + // ------------------------------------------------ Artifacts API --------------------------------------------------// + public addOrUpdateArtifact = (componentId:string, artifact:ArtifactModel):ng.IPromise<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<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<ArtifactModel> => { + let deferred = this.$q.defer(); + this.restangular.one(componentId).one("artifacts").one(artifactId).remove({'operation': artifactLabel}).then((response:ArtifactModel) => { + deferred.resolve(response); + }, (err)=> { + deferred.reject(err); + }); + return deferred.promise; + }; + + public getArtifactByGroupType = (componentId:string, artifactGroupType:string):ng.IPromise<ArtifactGroupModel> => { + let deferred = this.$q.defer(); + this.restangular.one(componentId).one("artifactsByType").one(artifactGroupType).get().then((response:any) => { + var artifacts:ArtifactGroupModel = new ArtifactGroupModel(response.plain()); + deferred.resolve(artifacts); + }, (err)=> { + deferred.reject(err); + }); + return deferred.promise; + }; + + public getComponentInstanceArtifactsByGroupType = (componentId:string, componentInstanceId:string, artifactGroupType:string):ng.IPromise<ArtifactGroupModel> => { + let deferred = this.$q.defer(); + this.restangular.one(componentId).one("resourceInstances").one(componentInstanceId).one("artifactsByType").one(artifactGroupType).get().then((response:any) => { + var artifacts:ArtifactGroupModel = new ArtifactGroupModel(response.plain()); + deferred.resolve(artifacts); + }, (err)=> { + deferred.reject(err); + }); + return deferred.promise; + }; + + + // ------------------------------------------------ Properties API --------------------------------------------------// + public addProperty = (componentId:string, property:PropertyModel):ng.IPromise<PropertyModel> => { + let deferred = this.$q.defer(); + this.restangular.one(componentId).one("properties").customPOST(property.convertToServerObject()).then((response:any) => { + let property:PropertyModel = new PropertyModel(response[Object.keys(response)[0]]); + deferred.resolve(property); + }, (err)=> { + deferred.reject(err); + }); + return deferred.promise; + }; + + public updateProperty = (componentId:string, property:PropertyModel):ng.IPromise<PropertyModel> => { + let deferred = this.$q.defer(); + this.restangular.one(componentId).one("properties").one(property.uniqueId).customPUT(property.convertToServerObject()).then((response:any) => { + let property:PropertyModel = new PropertyModel(response[Object.keys(response)[0]]); + deferred.resolve(property); + }, (err)=> { + deferred.reject(err); + }); + return deferred.promise; + }; + + public deleteProperty = (componentId:string, propertyId:string):ng.IPromise<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:AttributeModel):ng.IPromise<AttributeModel> => { + let deferred = this.$q.defer(); + this.restangular.one(componentId).one("attributes").customPOST(attribute.convertToServerObject()).then((response:any) => { + let attribute:AttributeModel = new AttributeModel(response); + deferred.resolve(attribute); + }, (err)=> { + deferred.reject(err); + }); + return deferred.promise; + }; + + public updateAttribute = (componentId:string, attribute:AttributeModel):ng.IPromise<AttributeModel> => { + let deferred = this.$q.defer(); + this.restangular.one(componentId).one("attributes").one(attribute.uniqueId).customPUT(attribute.convertToServerObject()).then((response:any) => { + let attribute:AttributeModel = new AttributeModel(response); + deferred.resolve(attribute); + }, (err)=> { + deferred.reject(err); + }); + return deferred.promise; + }; + + public deleteAttribute = (componentId:string, attributeId:string):ng.IPromise<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:ComponentInstance):ng.IPromise<ComponentInstance> => { + let deferred = this.$q.defer(); + this.restangular.one(componentId).one("resourceInstance").customPOST(JSON.stringify(componentInstance)).then((response:any) => { + let componentInstance:ComponentInstance = ComponentInstanceFactory.createComponentInstance(response); + console.log("Component Instance created", componentInstance); + deferred.resolve(componentInstance); + }, (err)=> { + console.log("Failed to create componentInstance. With Name: " + componentInstance.name); + deferred.reject(err); + }); + return deferred.promise; + }; + + public updateComponentInstance = (componentId:string, componentInstance:ComponentInstance):ng.IPromise<ComponentInstance> => { + let deferred = this.$q.defer(); + this.restangular.one(componentId).one("resourceInstance").one(componentInstance.uniqueId).customPOST(JSON.stringify(componentInstance)).then((response:any) => { + let componentInstance:ComponentInstance = ComponentInstanceFactory.createComponentInstance(response); + console.log("Component Instance was updated", componentInstance); + deferred.resolve(componentInstance); + }, (err)=> { + console.log("Failed to update componentInstance. With ID: " + componentInstance.uniqueId + "Name: " + componentInstance.name); + deferred.reject(err); + }); + return deferred.promise; + }; + + public updateMultipleComponentInstances = (componentId:string, instances:Array<ComponentInstance>):ng.IPromise<Array<ComponentInstance>> => { + let deferred = this.$q.defer(); + this.restangular.one(componentId).one("resourceInstance/multipleComponentInstance").customPOST(JSON.stringify(instances)).then((response:any) => { + console.log("Multiple Component Instances was updated", response); + let updateInstances:Array<ComponentInstance> = new Array<ComponentInstance>(); + _.forEach(response, (componentInstance:ComponentInstance) => { + let updatedComponentInstance:ComponentInstance = ComponentInstanceFactory.createComponentInstance(componentInstance); + updateInstances.push(updatedComponentInstance); + }); + deferred.resolve(updateInstances); + }, (err)=> { + console.log("Failed to update Multiple componentInstance."); + deferred.reject(err); + }); + return deferred.promise; + }; + + public deleteComponentInstance = (componentId:string, componentInstanceId:string):ng.IPromise<ComponentInstance> => { + let deferred = this.$q.defer(); + this.restangular.one(componentId).one("resourceInstance").one(componentInstanceId).remove().then(() => { + console.log("Component Instance was deleted"); + deferred.resolve(); + }, (err)=> { + console.log("Failed to delete componentInstance. With ID: " + componentInstanceId); + deferred.reject(err); + }); + return deferred.promise; + }; + + public changeResourceInstanceVersion = (componentId:string, componentInstanceId:string, componentUid:string):ng.IPromise<ComponentInstance> => { + let deferred = this.$q.defer(); + this.restangular.one(componentId).one("resourceInstance").one(componentInstanceId).one("changeVersion").customPOST({'componentUid': componentUid}).then((response:any) => { + let componentInstance:ComponentInstance = ComponentInstanceFactory.createComponentInstance(response); + deferred.resolve(componentInstance); + }, (err)=> { + deferred.reject(err); + }); + return deferred.promise; + }; + + public downloadInstanceArtifact = (componentId:string, instanceId:string, artifactId:string):ng.IPromise<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:ArtifactModel):ng.IPromise<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 ArtifactModel(response); + deferred.resolve(newArtifact); + }, (err)=> { + deferred.reject(err); + }); + return deferred.promise; + }; + + public addInstanceArtifact = (componentId:string, instanceId:string, artifact:ArtifactModel):ng.IPromise<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:ArtifactModel = new 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<ArtifactModel> => { + let deferred = this.$q.defer(); + this.restangular.one(componentId).one("resourceInstance").one(instanceId).one("artifacts").one(artifactId).remove({'operation': artifactLabel}).then((response:ArtifactModel) => { + deferred.resolve(response); + }, (err)=> { + deferred.reject(err); + }); + return deferred.promise; + }; + + public uploadInstanceEnvFile = (componentId:string, instanceId:string, artifact:ArtifactModel):ng.IPromise<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 ArtifactModel(response); + deferred.resolve(newArtifact); + }, (err)=> { + deferred.reject(err); + }); + return deferred.promise; + }; + + public updateInstanceProperty = (componentId:string, property:PropertyModel):ng.IPromise<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 PropertyModel(response); + newProperty.readonly = true; + newProperty.resourceInstanceUniqueId = instanceId; + deferred.resolve(newProperty); + }, (err)=> { + deferred.reject(err); + }); + return deferred.promise; + }; + + public updateInstanceAttribute = (componentId:string, attribute:AttributeModel):ng.IPromise<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 AttributeModel(response); + newAttribute.readonly = true; + newAttribute.resourceInstanceUniqueId = instanceId; + deferred.resolve(newAttribute); + }, (err)=> { + deferred.reject(err); + }); + return deferred.promise; + }; + + public createRelation = (componentId:string, link:RelationshipModel):ng.IPromise<RelationshipModel> => { + let deferred = this.$q.defer(); + this.restangular.one(componentId).one("resourceInstance").one("associate").customPOST(JSON.stringify(link)).then((response:any) => { + let relation:RelationshipModel = new RelationshipModel(response.plain()); + console.log("Link created successfully ", relation); + deferred.resolve(relation); + }, (err)=> { + console.log("Failed to create Link From: " + link.fromNode + "To: " + link.toNode); + deferred.reject(err); + }); + return deferred.promise; + }; + + public deleteRelation = (componentId:string, link:RelationshipModel):ng.IPromise<RelationshipModel> => { + let deferred = this.$q.defer(); + this.restangular.one(componentId).one("resourceInstance").one("dissociate").customPUT(JSON.stringify(link)).then((response:any) => { + let relation:RelationshipModel = new RelationshipModel(response); + console.log("Link deleted successfully ", relation); + deferred.resolve(relation); + }, (err)=> { + console.log("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) => { + console.log("Component requirement capabilities recived: ", response); + deferred.resolve(response); + }, (err)=> { + console.log("Failed to get requirements & capabilities"); + deferred.reject(err); + }); + return deferred.promise; + }; + + public getModuleForDisplay = (componentId:string, moduleId:string):ng.IPromise<DisplayModule> => { + let deferred = this.$q.defer(); + this.restangular.one(componentId).one("groups").one(moduleId).get().then((response:any) => { + console.log("module loaded successfully: ", response); + let module:DisplayModule = new DisplayModule(response); + deferred.resolve(module); + }, (err)=> { + console.log("Failed to get module with id: ", moduleId); + deferred.reject(err); + }); + return deferred.promise; + }; + + public getComponentInstanceModule = (componentId:string, componentInstanceId:string, moduleId:string):ng.IPromise<Module> => { + let deferred = this.$q.defer(); + this.restangular.one(componentId).one("resourceInstance").one(componentInstanceId).one("groupInstance").one(moduleId).get().then((response:any) => { + console.log("module loaded successfully: ", response); + let module:DisplayModule = new DisplayModule(response); + deferred.resolve(module); + }, (err)=> { + console.log("Failed to get module with id: ", moduleId); + deferred.reject(err); + }); + return deferred.promise; + }; + + public getComponentInstancesFilteredByInputsAndProperties = (componentId:string, searchText?:string):ng.IPromise<Array<ComponentInstance>> => { + let deferred = this.$q.defer(); + this.restangular.one(componentId).one("componentInstances").get({'searchText': searchText}).then((response:any) => { + console.log("component instances return successfully: ", response); + let componentInstances:Array<ComponentInstance> = CommonUtils.initComponentInstances(response); + deferred.resolve(componentInstances); + }, (err) => { + console.log("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<InputModel>> => { + + let deferred = this.$q.defer(); + this.restangular.one(componentId).one("componentInstances").one(instanceId).one(originComponentUid).one("inputs").get().then((response:any) => { + console.log("component instance input return successfully: ", response); + let inputsArray:Array<InputModel> = new Array<InputModel>(); + _.forEach(response, (inputObj:InputModel) => { + inputsArray.push(new InputModel(inputObj)); + }); + deferred.resolve(inputsArray); + }, (err) => { + console.log("Failed to get component instance input with id: " + instanceId); + deferred.reject(err); + }); + + return deferred.promise; + }; + + public getComponentInputs = (componentId:string):ng.IPromise<Array<InputModel>> => { + + let deferred = this.$q.defer(); + this.restangular.one(componentId).one("inputs").get().then((response:any) => { + console.log("component inputs return successfully: ", response); + let inputsArray:Array<InputModel> = new Array<InputModel>(); + _.forEach(response, (inputObj:InputModel) => { + inputsArray.push(new InputModel(inputObj)); + }); + deferred.resolve(inputsArray); + }, (err) => { + console.log("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<PropertyModel>> => { + + let deferred = this.$q.defer(); + this.restangular.one(componentId).one("componentInstances").one(instanceId).one(inputId).one("properties").get().then((response:any) => { + console.log("component instance input properties return successfully: ", response); + let propertiesArray:Array<PropertyModel> = new Array<PropertyModel>(); + _.forEach(response, (propertyObj:PropertyModel) => { + propertiesArray.push(new PropertyModel(propertyObj)); + }); + deferred.resolve(propertiesArray); + }, (err) => { + console.log("Failed to get component instance input properties with instanceId: " + instanceId + "and input id: " + inputId); + deferred.reject(err); + }); + + return deferred.promise; + }; + + + public getComponentInstanceProperties = (componentId:string, instanceId:string):ng.IPromise<Array<PropertyModel>> => { + + let deferred = this.$q.defer(); + this.restangular.one(componentId).one("componentInstances").one(instanceId).one("properties").get().then((response:any) => { + console.log("component instance properties return successfully: ", response); + let propertiesArray:Array<PropertyModel> = new Array<PropertyModel>(); + _.forEach(response, (propertyObj:PropertyModel) => { + propertiesArray.push(new PropertyModel(propertyObj)); + }); + deferred.resolve(propertiesArray); + }, (err) => { + console.log("Failed to get component instance properties with instanceId: " + instanceId); + deferred.reject(err); + }); + + return deferred.promise; + }; + + public updateGroupMetadata = (componentId:string, group:Module):ng.IPromise<Module> => { + + let deferred = this.$q.defer(); + this.restangular.one(componentId).one("groups").one(group.uniqueId).one("metadata").customPUT(JSON.stringify(group)).then((response:Module) => { + console.log("group metadata updated successfully: ", response); + let updatedGroup:Module = new Module(response); + + deferred.resolve(updatedGroup); + }, (err) => { + console.log("Failed to update group metadata for component: " + componentId + " for group with id: " + group.uniqueId); + deferred.reject(err); + }); + + return deferred.promise; + }; + + public getComponentInputInputsAndProperties = (serviceId:string, inputId:string):ng.IPromise<InputsAndProperties> => { + let defer = this.$q.defer<any>(); + this.restangular.one(serviceId).one("inputs").one(inputId).get().then((response:InputsAndProperties) => { + + let inputsArray:Array<InputModel> = new Array<InputModel>(); + _.forEach(response.inputs, (inputObj:InputModel) => { + inputsArray.push(new InputModel(inputObj)); + }); + + let propertiesArray:Array<PropertyModel> = new Array<PropertyModel>(); + _.forEach(response.properties, (property:PropertyModel) => { + propertiesArray.push(new PropertyModel(property)); + }); + + defer.resolve(new InputsAndProperties(inputsArray, propertiesArray)); + }, (err)=> { + console.log("failed to get inputs of input : ", err); + defer.reject(err); + }); + return defer.promise; + }; + + createInputsFromInstancesInputsProperties = (resourceId:string, instancePropertyMap:InstancesInputsPropertiesMap):ng.IPromise<Array<PropertyModel>> => { + let defer = this.$q.defer<any>(); + this.restangular.one(resourceId).one("create/properties").customPOST(instancePropertyMap).then((response:any) => { + let inputsArray:Array<PropertyModel> = new Array<PropertyModel>(); + _.forEach(response, (inputObj:PropertyModel) => { + inputsArray.push(new PropertyModel(inputObj)); + }); + defer.resolve(inputsArray); + }, (err)=> { + console.log("failed to create service inputs from VF instances inputs : ", err); + defer.reject(err); + }); + return defer.promise; + }; + + createInputsFromInstancesInputs = (serviceId:string, instancesMap:InstancesInputsPropertiesMap):ng.IPromise<Array<InputModel>> => { + let defer = this.$q.defer<any>(); + this.restangular.one(serviceId).one("create/inputs").customPOST(instancesMap).then((response:any) => { + let inputsArray:Array<InputModel> = new Array<InputModel>(); + _.forEach(response, (inputObj:InputModel) => { + inputsArray.push(new InputModel(inputObj)); + }); + defer.resolve(inputsArray); + }, (err)=> { + console.log("failed to create service inputs from VF instances inputs : ", err); + defer.reject(err); + }); + return defer.promise; + }; + + deleteComponentInput = (serviceId:string, inputId:string):ng.IPromise<InputModel> => { + let defer = this.$q.defer(); + this.restangular.one(serviceId).one("delete").one(inputId).one("input").remove().then((response:any) => { + let inputToDelete = new 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/src/app/services/components/product-service.ts b/catalog-ui/src/app/services/components/product-service.ts new file mode 100644 index 0000000000..c1a14478d2 --- /dev/null +++ b/catalog-ui/src/app/services/components/product-service.ts @@ -0,0 +1,35 @@ +/** + * Created by obarda on 2/8/2016. + */ +'use strict'; +import {IComponentService, ComponentService} from "./component-service"; +import {SharingService} from "../sharing-service"; +import {Product, Component, IAppConfigurtaion} from "../../models"; + +export interface IProductService extends IComponentService { + +} + +export class ProductService extends ComponentService implements IProductService { + + static '$inject' = [ + 'Restangular', + 'sdcConfig', + 'Sdc.Services.SharingService', + '$q', + '$base64' + ]; + + constructor(protected restangular:restangular.IElement, + protected sdcConfig:IAppConfigurtaion, + protected sharingService:SharingService, + protected $q:ng.IQService, + protected $base64:any) { + super(restangular, sdcConfig, sharingService, $q, $base64); + this.restangular = restangular.one("products"); + } + + createComponentObject = (component:Component):Component => { + return new Product(this, this.$q, <Product>component); + }; +} diff --git a/catalog-ui/src/app/services/components/resource-service.ts b/catalog-ui/src/app/services/components/resource-service.ts new file mode 100644 index 0000000000..470f1e2061 --- /dev/null +++ b/catalog-ui/src/app/services/components/resource-service.ts @@ -0,0 +1,52 @@ +/** + * Created by obarda on 2/4/2016. + */ +'use strict'; +import {IComponentService, ComponentService} from "./component-service"; +import {PropertyModel, IAppConfigurtaion, Resource, Component} from "../../models"; +import {SharingService} from "../sharing-service"; + +export interface IResourceService extends IComponentService { + updateResourceGroupProperties(uniqueId:string, groupId:string, properties:Array<PropertyModel>):ng.IPromise<Array<PropertyModel>> +} + +export class ResourceService extends ComponentService implements IResourceService { + + static '$inject' = [ + 'Restangular', + 'sdcConfig', + 'Sdc.Services.SharingService', + '$q', + '$base64' + ]; + + constructor(protected restangular:restangular.IElement, + protected sdcConfig:IAppConfigurtaion, + protected sharingService:SharingService, + protected $q:ng.IQService, + protected $base64:any + ) { + super(restangular, sdcConfig, sharingService, $q, $base64); + + this.restangular = restangular.one("resources"); + } + + createComponentObject = (component:Component):Component => { + return new Resource(this, this.$q, <Resource>component); + }; + + + updateResourceGroupProperties = (uniqueId:string, groupId:string, properties:Array<PropertyModel>):ng.IPromise<Array<PropertyModel>> => { + let defer = this.$q.defer<Array<PropertyModel>>(); + this.restangular.one(uniqueId).one("groups").one(groupId).one('properties').customPUT(JSON.stringify(properties)).then((updatesProperties:any) => { + let propertiesArray:Array<PropertyModel> = new Array<PropertyModel>(); + _.forEach(updatesProperties, (propertyObj:PropertyModel) => { + propertiesArray.push(new PropertyModel(propertyObj)); + }); + defer.resolve(propertiesArray); + }, (err)=> { + defer.reject(err); + }); + return defer.promise; + }; +} diff --git a/catalog-ui/src/app/services/components/service-service.ts b/catalog-ui/src/app/services/components/service-service.ts new file mode 100644 index 0000000000..f0fe90bf7f --- /dev/null +++ b/catalog-ui/src/app/services/components/service-service.ts @@ -0,0 +1,86 @@ +/** + * Created by obarda on 2/4/2016. + */ +'use strict'; +import {IComponentService, ComponentService} from "./component-service"; +import {Distribution, DistributionComponent, Service, PropertyModel, Component, IAppConfigurtaion} from "app/models"; +import {SharingService} from "../sharing-service"; + +export interface IServiceService extends IComponentService { + getDistributionsList(uuid:string):ng.IPromise<Array<Distribution>>; + getDistributionComponents(distributionId:string):ng.IPromise<Array<DistributionComponent>>; + markAsDeployed(serviceId:string, distributionId:string):ng.IPromise<any>; + updateGroupInstanceProperties(serviceId:string, resourceInstanceId:string, groupInstanceId:string, groupInstanceProperties:Array<PropertyModel>):ng.IPromise<Array<PropertyModel>>; +} + +export class ServiceService extends ComponentService implements IServiceService { + + static '$inject' = [ + 'Restangular', + 'sdcConfig', + 'Sdc.Services.SharingService', + '$q', + '$base64' + ]; + + public distribution:string = "distribution"; + + constructor(protected restangular:restangular.IElement, + protected sdcConfig:IAppConfigurtaion, + protected sharingService:SharingService, + protected $q:ng.IQService, + protected $base64:any) { + super(restangular, sdcConfig, sharingService, $q, $base64); + + this.restangular = restangular.one("services"); + } + + getDistributionsList = (uuid:string):ng.IPromise<Array<Distribution>> => { + let defer = this.$q.defer<Array<Distribution>>(); + this.restangular.one(uuid).one("distribution").get().then((distributions:any) => { + defer.resolve(<Array<Distribution>> distributions.distributionStatusOfServiceList); + }, (err)=> { + defer.reject(err); + }); + return defer.promise; + }; + + getDistributionComponents = (distributionId:string):ng.IPromise<Array<DistributionComponent>> => { + let defer = this.$q.defer<Array<DistributionComponent>>(); + this.restangular.one("distribution").one(distributionId).get().then((distributions:any) => { + defer.resolve(<Array<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:Component):Component => { + return new Service(this, this.$q, <Service>component); + }; + + updateGroupInstanceProperties = (serviceId:string, resourceInstanceId:string, groupInstanceId:string, groupInstanceProperties:Array<PropertyModel>):ng.IPromise<Array<PropertyModel>> => { + let defer = this.$q.defer<Array<PropertyModel>>(); + this.restangular.one(serviceId).one("resourceInstance").one(resourceInstanceId).one('groupInstance').one(groupInstanceId).customPUT(JSON.stringify(groupInstanceProperties)).then((updatedProperties:any) => { + let propertiesArray:Array<PropertyModel> = new Array<PropertyModel>(); + _.forEach(updatedProperties, (propertyObj:PropertyModel) => { + propertiesArray.push(new PropertyModel(propertyObj)); + }); + defer.resolve(propertiesArray); + }, (err)=> { + defer.reject(err); + }); + return defer.promise; + }; +} diff --git a/catalog-ui/src/app/services/components/utils/composition-left-palette-service.ts b/catalog-ui/src/app/services/components/utils/composition-left-palette-service.ts new file mode 100644 index 0000000000..0abdbcfcb3 --- /dev/null +++ b/catalog-ui/src/app/services/components/utils/composition-left-palette-service.ts @@ -0,0 +1,120 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR 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. + */ +'use strict'; +import {LeftPaletteComponent} from "../../../models/components/displayComponent"; +import {Component} from "../../../models/components/component"; +import {EventListenerService} from "../../event-listener-service"; +import {ComponentFactory} from "../../../utils/component-factory"; +import {IAppConfigurtaion} from "../../../models/app-config"; +import {ResourceType, ComponentType, EVENTS} from "../../../utils/constants"; +import {ComponentMetadata} from "../../../models/component-metadata"; + +export class LeftPaletteDataObject { + displayLeftPanelComponents:Array<LeftPaletteComponent>; + onFinishLoadingEvent:string; + + constructor(onFinishEventListener:string) { + + this.displayLeftPanelComponents = new Array<LeftPaletteComponent>(); + this.onFinishLoadingEvent = onFinishEventListener; + } +} + +export class LeftPaletteLoaderService { + + static '$inject' = [ + 'Restangular', + 'sdcConfig', + '$q', + 'ComponentFactory', + 'EventListenerService' + + ]; + + constructor(protected restangular:restangular.IElement, + protected sdcConfig:IAppConfigurtaion, + protected $q:ng.IQService, + protected ComponentFactory:ComponentFactory, + protected EventListenerService: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 = (componentType: string):void => { + this.serviceLeftPaletteData = new LeftPaletteDataObject(EVENTS.SERVICE_LEFT_PALETTE_UPDATE_EVENT); + this.resourceLeftPaletteData = new LeftPaletteDataObject(EVENTS.RESOURCE_LEFT_PALETTE_UPDATE_EVENT); + this.updateComponentLeftPalette(componentType); + } + + + private getTypeUrl = (componentType:string):string => { + return ComponentType.PRODUCT === componentType ? "services" : "resources"; + }; + + private onFinishLoading = (componentType:string, leftPaletteData:LeftPaletteDataObject):void => { + this.EventListenerService.notifyObservers(leftPaletteData.onFinishLoadingEvent); + }; + + private updateLeftPalette = (componentType, componentInternalType:string, leftPaletteData:LeftPaletteDataObject):void => { + + this.restangular.one(this.getTypeUrl(componentType)).one('/latestversion/notabstract/metadata').get({'internalComponentType': componentInternalType}).then((leftPaletteComponentMetadata:Array<ComponentMetadata>) => { + _.forEach(leftPaletteComponentMetadata, (componentMetadata:ComponentMetadata) => { + leftPaletteData.displayLeftPanelComponents.push(new LeftPaletteComponent(componentMetadata)); + }); + this.onFinishLoading(componentType, leftPaletteData); + }); + }; + + public getLeftPanelComponentsForDisplay = (componentType:string):Array<LeftPaletteComponent> => { + switch (componentType) { + case ComponentType.SERVICE: + return this.serviceLeftPaletteData.displayLeftPanelComponents; + case ComponentType.PRODUCT: + return this.productLeftPaletteData.displayLeftPanelComponents; + default: + return this.resourceLeftPaletteData.displayLeftPanelComponents; + } + }; + + public updateComponentLeftPalette = (componentType):void => { + switch (componentType) { + case ResourceType.VL: + this.updateLeftPalette(ComponentType.RESOURCE, ResourceType.VL, this.vlData); + break; + case ComponentType.SERVICE: + this.updateLeftPalette(ComponentType.SERVICE, ComponentType.SERVICE, this.serviceLeftPaletteData); + break; + case ComponentType.PRODUCT: + this.updateLeftPalette(ComponentType.PRODUCT, ComponentType.SERVICE, this.productLeftPaletteData); + break; + default: + this.updateLeftPalette(ComponentType.RESOURCE, ResourceType.VF, this.resourceLeftPaletteData); + } + }; +} diff --git a/catalog-ui/src/app/services/configuration-ui-service.ts b/catalog-ui/src/app/services/configuration-ui-service.ts new file mode 100644 index 0000000000..92da0a50ed --- /dev/null +++ b/catalog-ui/src/app/services/configuration-ui-service.ts @@ -0,0 +1,25 @@ +'use strict' +import {IAppConfigurtaion, IApi} from "../models/app-config"; + +interface IConfigurationUiService { + getConfigurationUi():ng.IPromise<any>; +} + +export class ConfigurationUiService implements IConfigurationUiService { + + static '$inject' = ['$http', '$q', 'sdcConfig']; + private api:IApi; + + constructor(private $http:ng.IHttpService, private $q:ng.IQService, sdcConfig:IAppConfigurtaion) { + this.api = sdcConfig.api; + } + + public getConfigurationUi = ():ng.IPromise<any> => { + let defer = this.$q.defer<any>(); + this.$http.get(this.api.root + this.api.GET_configuration_ui) + .then((result:any) => { + defer.resolve(result.data); + }); + return defer.promise; + } +} diff --git a/catalog-ui/src/app/services/cookie-service.ts b/catalog-ui/src/app/services/cookie-service.ts new file mode 100644 index 0000000000..8f88835c18 --- /dev/null +++ b/catalog-ui/src/app/services/cookie-service.ts @@ -0,0 +1,72 @@ +'use strict'; +import {IAppConfigurtaion, ICookie} from "../models/app-config"; + +interface ICookieService { + getUserId():string; + getFirstName():string; + getLastName():string; + getEmail():string; + getUserIdSuffix():string; +} + +export class CookieService implements ICookieService { + + static '$inject' = ['sdcConfig', '$document']; + private cookie:ICookie; + private cookiePrefix:string; + + + constructor(sdcConfig: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/src/app/services/data-types-service.ts b/catalog-ui/src/app/services/data-types-service.ts new file mode 100644 index 0000000000..1c6ac07fdc --- /dev/null +++ b/catalog-ui/src/app/services/data-types-service.ts @@ -0,0 +1,125 @@ +'use strict'; +import { DataTypePropertyModel } from "../models/data-type-properties"; +import {ComponentInstance, InputModel, DataTypesMap, PropertyModel, InputPropertyBase, IAppConfigurtaion, SchemaProperty} from "../models"; +import {PROPERTY_DATA} from "../utils/constants"; + +export interface IDataTypesService { + + dataTypes:DataTypesMap; //Data type map + selectedPropertiesName:string; + selectedInput:PropertyModel; + alreadySelectedProperties:Array<InputPropertyBase>; + selectedInstance:ComponentInstance; + selectedComponentInputs:Array<InputModel>; + //declare methods + initDataTypes():void; + getAllDataTypes():DataTypesMap; + getFirsLevelOfDataTypeProperties(dataTypeName:string):Array<DataTypePropertyModel>; + isDataTypeForSchemaType(property:SchemaProperty):boolean; + isDataTypeForPropertyType(property:PropertyModel):boolean; + isDataTypeForDataTypePropertyType(property:DataTypePropertyModel):boolean; +} + +export class DataTypesService implements IDataTypesService { + + static '$inject' = [ + 'sdcConfig', + '$q', + '$http' + ]; + + constructor(private sdcConfig:IAppConfigurtaion, + private $q:ng.IQService, + private $http:ng.IHttpService) { + + } + + dataTypes:DataTypesMap; //Data type map + selectedPropertiesName:string; + selectedInput:PropertyModel; + alreadySelectedProperties:Array<InputPropertyBase>; + selectedInstance:ComponentInstance; + selectedComponentInputs:Array<InputModel>; + + public initDataTypes = ():void => { + this.$http({ + url: this.sdcConfig.api.root + this.sdcConfig.api.component_api_root + "dataTypes", + method: "get" + }).then((response:any) => { + this.dataTypes = response.data; + delete this.dataTypes['tosca.datatypes.Root']; + }); + }; + + public getAllDataTypes = ():DataTypesMap => { + return this.dataTypes; + }; + + //if the dt derived from simple- return the first parent type, else- return null + private getTypeForDataTypeDerivedFromSimple = (dataTypeName:string):string => { + /////////temporary hack for tosca primitives/////////////////////// + if (!this.dataTypes[dataTypeName]) { + return 'string'; + } + /////////////////////////////////////////////////////////////////// + if (this.dataTypes[dataTypeName].derivedFromName == "tosca.datatypes.Root" || this.dataTypes[dataTypeName].properties) { + return null; + } + if (PROPERTY_DATA.SIMPLE_TYPES.indexOf(this.dataTypes[dataTypeName].derivedFromName) > -1) { + return this.dataTypes[dataTypeName].derivedFromName + } + return this.getTypeForDataTypeDerivedFromSimple(this.dataTypes[dataTypeName].derivedFromName); + }; + + + //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):Array<DataTypePropertyModel> => { + let properties = this.dataTypes[dataTypeName].properties || []; + if (this.dataTypes[dataTypeName].derivedFromName != "tosca.datatypes.Root") { + properties = this.getFirsLevelOfDataTypeProperties(this.dataTypes[dataTypeName].derivedFromName).concat(properties); + } + return properties; + }; + + //return false when type= data type (=not simple type) that not derived from simple type + public isDataTypeForSchemaType = (property:SchemaProperty):boolean=> { + property.simpleType = ""; + if (property.type && PROPERTY_DATA.TYPES.indexOf(property.type) > -1) { + return false; + } + let simpleType = this.getTypeForDataTypeDerivedFromSimple(property.type); + if (simpleType) { + property.simpleType = simpleType; + return false; + } + return true; + }; + + public isDataTypeForPropertyType = (property:PropertyModel):boolean=> { + property.simpleType = ""; + if (property.type && PROPERTY_DATA.TYPES.indexOf(property.type) > -1) { + return false; + } + let simpleType = this.getTypeForDataTypeDerivedFromSimple(property.type); + if (simpleType) { + property.simpleType = simpleType; + return false; + } + return true; + }; + + + public isDataTypeForDataTypePropertyType = (property:DataTypePropertyModel):boolean=> { + property.simpleType = ""; + if (property.type && PROPERTY_DATA.TYPES.indexOf(property.type) > -1) { + return false; + } + let simpleType = this.getTypeForDataTypeDerivedFromSimple(property.type); + if (simpleType) { + property.simpleType = simpleType; + return false; + } + return true; + }; +} diff --git a/catalog-ui/src/app/services/ecomp-service.ts b/catalog-ui/src/app/services/ecomp-service.ts new file mode 100644 index 0000000000..2703a50fc3 --- /dev/null +++ b/catalog-ui/src/app/services/ecomp-service.ts @@ -0,0 +1,30 @@ +'use strict'; +import {IAppConfigurtaion, IApi} from "../models/app-config"; + +interface IEcompHeaderService { + getMenuItems(userId):ng.IPromise<Array<any>>; +} + +export class EcompHeaderService implements IEcompHeaderService { + static '$inject' = ['$http', '$q', 'sdcConfig']; + private api:IApi; + + constructor(private $http:ng.IHttpService, + private $q:ng.IQService, + private sdcConfig: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)) + .then((response:any) => { + defer.resolve(response.data); + }, (response) => { + defer.reject(response.data); + }); + + return defer.promise; + }; +} diff --git a/catalog-ui/src/app/services/entity-service.ts b/catalog-ui/src/app/services/entity-service.ts new file mode 100644 index 0000000000..d480bf9104 --- /dev/null +++ b/catalog-ui/src/app/services/entity-service.ts @@ -0,0 +1,94 @@ +'use strict'; +import {Product, Service, IApi, IAppConfigurtaion, Resource, Component} from "../models"; +import {SharingService} from "./sharing-service"; +import {ComponentFactory} from "../utils/component-factory"; +import {CacheService} from "./cache-service"; + +interface IEntityService { + getAllComponents():ng.IPromise<Array<Component>>; +} + +interface IComponentsArray { + services:Array<Service>; + resources:Array<Resource>; + products:Array<Product>; +} + +export class EntityService implements IEntityService { + static '$inject' = ['$http', '$q', 'sdcConfig', 'Sdc.Services.SharingService', 'ComponentFactory', 'Sdc.Services.CacheService']; + private api:IApi; + + constructor(private $http:ng.IHttpService, + private $q:ng.IQService, + private sdcConfig:IAppConfigurtaion, + private sharingService:SharingService, + private ComponentFactory:ComponentFactory, + private cacheService:CacheService) { + this.api = sdcConfig.api; + } + + getCatalog = ():ng.IPromise<Array<Component>> => { + let defer = this.$q.defer<Array<Component>>(); + this.$http.get(this.api.root + this.api.GET_catalog) + .then((response:any) => { + let followedResponse: IComponentsArray = response.data; + let componentsList:Array<Component> = new Array(); + + followedResponse.services.forEach((serviceResponse:Service) => { + let component:Service = this.ComponentFactory.createService(serviceResponse); // new Service(serviceResponse); + componentsList.push(component); + this.sharingService.addUuidValue(component.uniqueId, component.uuid); + }); + + followedResponse.resources.forEach((resourceResponse:Resource) => { + let component:Resource = this.ComponentFactory.createResource(resourceResponse); + componentsList.push(component); + this.sharingService.addUuidValue(component.uniqueId, component.uuid); + }); + + followedResponse.products.forEach((productResponse:Product) => { + + let component:Product = this.ComponentFactory.createProduct(productResponse); + componentsList.push(component); + this.sharingService.addUuidValue(component.uniqueId, component.uuid); + }); + + this.cacheService.set('breadcrumbsComponents', componentsList); + defer.resolve(componentsList); + },(responce) => { + defer.reject(responce); + }); + return defer.promise; + }; + + getAllComponents = ():ng.IPromise<Array<Component>> => { + let defer = this.$q.defer<Array<Component>>(); + this.$http.get(this.api.root + this.api.GET_element) + .then((response:any) => { + let componentResponse:IComponentsArray = response.data; + let componentsList:Array<Component> = []; + + componentResponse.services && componentResponse.services.forEach((serviceResponse:Service) => { + let component:Service = this.ComponentFactory.createService(serviceResponse); + componentsList.push(component); + this.sharingService.addUuidValue(component.uniqueId, component.uuid); + }); + + componentResponse.resources && componentResponse.resources.forEach((resourceResponse:Resource) => { + let component:Resource = this.ComponentFactory.createResource(resourceResponse); + componentsList.push(component); + this.sharingService.addUuidValue(component.uniqueId, component.uuid); + }); + + componentResponse.products && componentResponse.products.forEach((productsResponse:Product) => { + let component: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/src/app/services/event-listener-service.ts b/catalog-ui/src/app/services/event-listener-service.ts new file mode 100644 index 0000000000..51aa857e51 --- /dev/null +++ b/catalog-ui/src/app/services/event-listener-service.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 7/4/2016. + */ +'use strict'; +import {Dictionary} from "../utils/dictionary/dictionary"; + +interface IEventListenerService { + +} + +interface ICallbackData { + callback:Function; + args:any[]; +} + +export class EventListenerService implements IEventListenerService { + + public observerCallbacks:Dictionary<string, ICallbackData[]> = new 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, callbackFunc?:Function) => { + if (this.observerCallbacks.containsKey(eventName)) { + + let callbacks: ICallbackData[] = this.observerCallbacks.getValue(eventName); + if(callbacks.length === 1) { + this.observerCallbacks.remove(eventName); + } else { + let filterCallbacks = _.filter(callbacks, (callBackObj) => { + return callBackObj.callback != callbackFunc; + }); + this.observerCallbacks.setValue(eventName, filterCallbacks); + } + + } + }; + + public notifyObservers = function (eventName:string, ...args) { + _.forEach(this.observerCallbacks.getValue(eventName), (callbackData:ICallbackData) => { + callbackData.callback(...args); + }); + }; +} diff --git a/catalog-ui/src/app/services/header-interceptor.ts b/catalog-ui/src/app/services/header-interceptor.ts new file mode 100644 index 0000000000..a1e79934d8 --- /dev/null +++ b/catalog-ui/src/app/services/header-interceptor.ts @@ -0,0 +1,69 @@ +'use strict'; +import {IAppConfigurtaion} from "../models/app-config"; +import {Dictionary} from "../utils/dictionary/dictionary"; +import {SharingService} from "./sharing-service"; + +//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 = [ + '$injector', + '$q', + 'uuid4', + 'Sdc.Services.SharingService', + 'sdcConfig', + '$location' + ]; + + public static Factory($injector:ng.auto.IInjectorService, + $q:ng.IQService, + uuid4:any, + sharingService:SharingService, + sdcConfig:IAppConfigurtaion, + $location:ng.ILocationService) { + return new HeaderInterceptor($injector, $q, uuid4, sharingService, sdcConfig, $location); + } + + constructor(private $injector:ng.auto.IInjectorService, + private $q:ng.IQService, + private uuid4:any, + private sharingService:SharingService, + private sdcConfig:IAppConfigurtaion, + private $location:ng.ILocationService) { + console.debug('header-interceptor: 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:Dictionary<string, string> = this.sharingService.getUuidMap(); + if (map && requestSuccess.url.indexOf(this.sdcConfig.api.root) === 0) { + console.log("header-interceptor: 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/src/app/services/http-error-interceptor.ts b/catalog-ui/src/app/services/http-error-interceptor.ts new file mode 100644 index 0000000000..b61091c37c --- /dev/null +++ b/catalog-ui/src/app/services/http-error-interceptor.ts @@ -0,0 +1,99 @@ +'use strict'; +import {IServerMessageModalModel} from "../view-models/modals/message-modal/message-server-modal/server-message-modal-view-model"; +import {SEVERITY} from "../utils/constants"; +import 'app/utils/prototypes.ts'; + +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: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: 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: SEVERITY.ERROR + }; + } + + let modalsHandler = this.$injector.get('ModalsHandler'); + modalsHandler.openServerMessageModal(data); + + return this.$q.reject(rejection); + } +} diff --git a/catalog-ui/src/app/services/loader-service.ts b/catalog-ui/src/app/services/loader-service.ts new file mode 100644 index 0000000000..4bf8a6afe0 --- /dev/null +++ b/catalog-ui/src/app/services/loader-service.ts @@ -0,0 +1,24 @@ +/** + * Created by obarda on 3/13/2016. + */ +'use strict'; +import {EventListenerService} from "./event-listener-service"; +import {EVENTS} from "../utils/constants"; + +export class LoaderService { + + + constructor(private eventListenerService:EventListenerService) { + + } + + public showLoader(...args) { + this.eventListenerService.notifyObservers(EVENTS.SHOW_LOADER_EVENT, ...args); + } + + public hideLoader(...args) { + this.eventListenerService.notifyObservers(EVENTS.HIDE_LOADER_EVENT, ...args); + } +} + +LoaderService.$inject = ['EventListenerService']; diff --git a/catalog-ui/src/app/services/onboarding-service.ts b/catalog-ui/src/app/services/onboarding-service.ts new file mode 100644 index 0000000000..8b93b18ca9 --- /dev/null +++ b/catalog-ui/src/app/services/onboarding-service.ts @@ -0,0 +1,82 @@ +'use strict'; +import {Component, IComponent} from "../models/components/component"; +import {ICsarComponent} from "../models/csar-component"; +import {IAppConfigurtaion, IApi} from "../models/app-config"; +import {IFileDownload} from "../models/file-download"; +import {Resource} from "../models/components/resource"; +import {ComponentFactory} from "../utils/component-factory"; + +interface IOnboardingService { + getOnboardingComponents():ng.IPromise<Array<IComponent>>; + getComponentFromCsarUuid(csarUuid:string):ng.IPromise<Component>; + downloadOnboardingCsar(packageId:string):ng.IPromise<IFileDownload>; +} + +export class OnboardingService implements IOnboardingService { + + static '$inject' = ['$http', '$q', 'sdcConfig', 'ComponentFactory']; + private api:IApi; + + constructor(private $http:ng.IHttpService, + private $q:ng.IQService, + private sdcConfig:IAppConfigurtaion, + private ComponentFactory:ComponentFactory) { + this.api = sdcConfig.api; + } + + getOnboardingComponents = ():ng.IPromise<Array<IComponent>> => { + let defer = this.$q.defer<Array<IComponent>>(); + this.$http.get(this.api.GET_onboarding) + .then((response:any) => { + let onboardingComponents:Array<ICsarComponent> = response.data.results; + let componentsList:Array<IComponent> = new Array(); + + onboardingComponents.forEach((obc:ICsarComponent) => { + let component:Component = this.ComponentFactory.createFromCsarComponent(obc); + componentsList.push(component); + }); + + defer.resolve(componentsList); + },(response) => { + defer.reject(response); + }); + + return defer.promise; + }; + + downloadOnboardingCsar = (packageId:string):ng.IPromise<IFileDownload> => { + let defer = this.$q.defer(); + this.$http({ + url: this.api.GET_onboarding + "/" + packageId, + method: "get", + responseType: "blob" + }) + .then((response:any) => { + defer.resolve(response.data); + }, (err) => { + defer.reject(err); + }); + + return defer.promise; + }; + + getComponentFromCsarUuid = (csarUuid:string):ng.IPromise<Component> => { + let defer = this.$q.defer<Component>(); + this.$http.get(this.api.root + this.api.GET_component_from_csar_uuid.replace(':csar_uuid', csarUuid)) + .then((response:any) => { + let component: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.data.status !== 400) { + component = new Resource(null, this.$q, <Resource>response.data); + } + defer.resolve(component); + },(response) => { + defer.reject(response.data); + }); + + return defer.promise; + }; + +} diff --git a/catalog-ui/src/app/services/progress-service.ts b/catalog-ui/src/app/services/progress-service.ts new file mode 100644 index 0000000000..59ae16d734 --- /dev/null +++ b/catalog-ui/src/app/services/progress-service.ts @@ -0,0 +1,85 @@ +/** + * Created by obarda on 7/7/2016. + */ + +'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 => { + let 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 + let 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 => { + let 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/src/app/services/sdc-version-service.ts b/catalog-ui/src/app/services/sdc-version-service.ts new file mode 100644 index 0000000000..47001e9c9c --- /dev/null +++ b/catalog-ui/src/app/services/sdc-version-service.ts @@ -0,0 +1,26 @@ +'use strict'; +import {Distribution} from "../models/distribution"; +import {IAppConfigurtaion, IApi} from "../models/app-config"; + +export interface ISdcVersionService { + getVersion():ng.IPromise<any>; +} +export class SdcVersionService implements ISdcVersionService { + + static '$inject' = ['$http', '$q', 'sdcConfig']; + private api:IApi; + + constructor(private $http:ng.IHttpService, private $q:ng.IQService, sdcConfig:IAppConfigurtaion) { + this.api = sdcConfig.api; + } + + public getVersion():ng.IPromise<any> { + let defer = this.$q.defer<Array<Distribution>>(); + this.$http.get(this.api.root + this.api.GET_SDC_Version) + .then((version:any) => { + defer.resolve(version.data); + }); + return defer.promise; + } +} + diff --git a/catalog-ui/src/app/services/sharing-service.ts b/catalog-ui/src/app/services/sharing-service.ts new file mode 100644 index 0000000000..706f01f16b --- /dev/null +++ b/catalog-ui/src/app/services/sharing-service.ts @@ -0,0 +1,20 @@ +'use strict'; +import {Dictionary} from "app/utils"; + +export class SharingService { + + private uuidMap:Dictionary<string, string> = new 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 = ():Dictionary<string, string> => { + return this.uuidMap; + }; + +} diff --git a/catalog-ui/src/app/services/url-tobase64-service.ts b/catalog-ui/src/app/services/url-tobase64-service.ts new file mode 100644 index 0000000000..4e8dc18c7f --- /dev/null +++ b/catalog-ui/src/app/services/url-tobase64-service.ts @@ -0,0 +1,30 @@ +'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/src/app/services/user-resource-service.ts b/catalog-ui/src/app/services/user-resource-service.ts new file mode 100644 index 0000000000..b881e3a98a --- /dev/null +++ b/catalog-ui/src/app/services/user-resource-service.ts @@ -0,0 +1,103 @@ +'use strict'; +import {IUserProperties} from "../models/user"; +import {ICookie, IAppConfigurtaion} from "../models/app-config"; +import {CookieService} from "./cookie-service"; + +// Define an interface of the object you want to use, providing it's properties +export interface IUserResource extends 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:IAppConfigurtaion, + cookieService: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: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/src/app/utils.ts b/catalog-ui/src/app/utils.ts new file mode 100644 index 0000000000..734c276c56 --- /dev/null +++ b/catalog-ui/src/app/utils.ts @@ -0,0 +1,15 @@ +/** + * Created by ob0695 on 2/23/2017. + */ +export * from './utils/dictionary/dictionary'; +export * from './utils/artifacts-utils'; +export * from'./utils/file-utils' +export * from './utils/validation-utils' +export * from './utils/component-factory'; +export * from './utils/component-instance-factory'; +export * from './utils/change-lifecycle-state-handler'; +export * from './utils/modals-handler'; +export * from './utils/menu-handler'; +export * from './utils/constants'; +export * from './utils/common-utils'; +export * from './utils/functions'; diff --git a/catalog-ui/src/app/utils/artifacts-utils.ts b/catalog-ui/src/app/utils/artifacts-utils.ts new file mode 100644 index 0000000000..b52fe6f03e --- /dev/null +++ b/catalog-ui/src/app/utils/artifacts-utils.ts @@ -0,0 +1,104 @@ +import {ArtifactModel} from "../models/artifacts"; +import {IArtifactResourceFormViewModelScope} from "../view-models/forms/artifact-form/artifact-form-view-model"; +import {Component} from "../models/components/component"; +import {ArtifactGroupType, ArtifactType} from "./constants"; +export class ArtifactsUtils { + + static '$inject' = [ + '$filter' + ]; + + constructor(private $filter:ng.IFilterService) { + + } + + public getArtifactTypeByState(currentState:string):string { + switch (currentState) { + case "workspace.composition.lifecycle": + return "interface"; + case "workspace.composition.api": + return "api"; + case "workspace.deployment_artifacts": + case "workspace.composition.deployment": + return "deployment"; + case "workspace.composition.artifacts": + return "informational"; + default: + return "informational"; + } + } + + public getTitle(artifactType:string, selectedComponent:Component):string { + switch (artifactType) { + case "interface": + return "Lifecycle Management"; + case "api": + return "API Artifacts"; + case "deployment": + return "Deployment Artifacts"; + case "informational": + return "Informational Artifacts"; + default: + if (!selectedComponent) { + return ""; + } else { + return this.$filter("resourceName")(selectedComponent.name) + ' Artifacts'; + } + } + } + + public setArtifactType = (artifact:ArtifactModel, artifactType:string):void => { + switch (artifactType) { + case "api": + artifact.artifactGroupType = ArtifactGroupType.SERVICE_API; + break; + case "deployment": + artifact.artifactGroupType = ArtifactGroupType.DEPLOYMENT; + break; + default: + artifact.artifactGroupType = ArtifactGroupType.INFORMATION; + break; + } + }; + + public isLicenseType = (artifactType:string):boolean => { + let isLicense:boolean = false; + + if (ArtifactType.VENDOR_LICENSE === artifactType || ArtifactType.VF_LICENSE === artifactType) { + isLicense = true; + } + + return isLicense; + }; + + public removeArtifact = (artifact:ArtifactModel, artifactsArr:Array<ArtifactModel>):void => { + + if (!artifact.mandatory && (ArtifactGroupType.INFORMATION == artifact.artifactGroupType || + 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:IArtifactResourceFormViewModelScope) { + let newArtifact = new 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/src/app/utils/change-lifecycle-state-handler.ts b/catalog-ui/src/app/utils/change-lifecycle-state-handler.ts new file mode 100644 index 0000000000..dc59e3bb98 --- /dev/null +++ b/catalog-ui/src/app/utils/change-lifecycle-state-handler.ts @@ -0,0 +1,163 @@ +import {ComponentFactory} from "./component-factory"; +import {Component, Service,IAppMenu, IAppConfigurtaion} from "../models"; +import {IEmailModalModel, IEmailModalModel_Email, IEmailModalModel_Data} from "../view-models/modals/email-modal/email-modal-view-model"; +import {AsdcComment} from "../models/comments"; +import {ModalsHandler} from "./modals-handler"; +import {ServiceServiceNg2} from "../ng2/services/component-services/service.service"; + +/** + * Created by obarda on 2/11/2016. + */ + +export class ChangeLifecycleStateHandler { + + static '$inject' = [ + 'sdcConfig', + 'sdcMenu', + 'ComponentFactory', + '$filter', + 'ModalsHandler', + 'ServiceServiceNg2' + ]; + + constructor(private sdcConfig:IAppConfigurtaion, + private sdcMenu:IAppMenu, + private ComponentFactory:ComponentFactory, + private $filter:ng.IFilterService, + private ModalsHandler:ModalsHandler, + private ServiceServiceNg2:ServiceServiceNg2) { + + } + + private actualChangeLifecycleState = (component:Component, data:any, scope:any, onSuccessCallback?:Function, onErrorCallback?:Function):void => { + + let self = this; + + let getContacts = (component: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:Component):void => { + //scope.isLoading = false; + console.info(component.componentType.toLowerCase + ' change state ', newComponent); + if (onSuccessCallback) { + onSuccessCallback(self.ComponentFactory.createComponent(newComponent), data.url); + } + }; + + let onError = (error):void => { + scope.isLoading = false; + console.info('Failed to changeLifecycleState to ', data.url); + if (onErrorCallback) { + onErrorCallback(error); + } + }; + + let comment:AsdcComment = new 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:IEmailModalModel = <IEmailModalModel>{}; + emailModel.email = <IEmailModalModel_Email>{}; + emailModel.data = <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); + } + + } + + public changeLifecycleState = (component:Component, data:any, scope:any, onSuccessCallback?:Function, onErrorCallback?:Function):void => { + + if (data.conformanceLevelModal) { + this.validateConformanceLevel(component, data, scope, onSuccessCallback, onErrorCallback); + } else { + this.actualChangeLifecycleState(component, data, scope, onSuccessCallback, onErrorCallback); + } + } + + private validateConformanceLevel = (component:Component, data:any, scope:any, onSuccessCallback?:Function, onErrorCallback?:Function):void => { + // Validate conformance level if defined in menu.json + //------------------------------------------------- + this.ServiceServiceNg2.validateConformanceLevel(<Service>component).subscribe((res:boolean) => { + if (res === true) { + //conformance level is ok - continue + this.actualChangeLifecycleState(component, data, scope, onSuccessCallback, onErrorCallback); + + } else { + //show warning modal + this.ModalsHandler.openConformanceLevelModal() + .then(() => { + //continue distribute + this.actualChangeLifecycleState(component, data, scope, onSuccessCallback, onErrorCallback); + + }).catch(() => { + //reject distribution + this.actualChangeLifecycleState(component, data.conformanceLevelModal, scope, onSuccessCallback, onErrorCallback); + }); + } + }); + } +} diff --git a/catalog-ui/src/app/utils/common-utils.ts b/catalog-ui/src/app/utils/common-utils.ts new file mode 100644 index 0000000000..d8019d2f96 --- /dev/null +++ b/catalog-ui/src/app/utils/common-utils.ts @@ -0,0 +1,96 @@ +import {Module, AttributeModel, ResourceInstance, PropertyModel, InputFEModel} from "../models"; +import {ComponentInstanceFactory} from "./component-instance-factory"; +import {PropertyBEModel, RelationshipModel} from "app/models"; + +export class CommonUtils { + + static initProperties(propertiesObj:Array<PropertyModel>, uniqueId?:string):Array<PropertyModel> { + + let properties = new Array<PropertyModel>(); + if (propertiesObj) { + _.forEach(propertiesObj, (property:PropertyModel):void => { + if (uniqueId) { + property.readonly = property.parentUniqueId != uniqueId; + } + properties.push(new PropertyModel(property)); + }); + } + return properties; + }; + + static initAttributes(attributesObj:Array<AttributeModel>, uniqueId?:string):Array<AttributeModel> { + + let attributes = new Array<AttributeModel>(); + if (attributesObj) { + _.forEach(attributesObj, (attribute:AttributeModel):void => { + if (uniqueId) { + attribute.readonly = attribute.parentUniqueId != uniqueId; + } + attributes.push(new AttributeModel(attribute)); + }); + } + return attributes; + }; + + static initComponentInstances(componentInstanceObj:Array<ResourceInstance>):Array<ResourceInstance> { + + let componentInstances = new Array<ResourceInstance>(); + if (componentInstanceObj) { + _.forEach(componentInstanceObj, (instance:ResourceInstance):void => { + componentInstances.push(ComponentInstanceFactory.createComponentInstance(instance)); + }); + } + return componentInstances; + }; + + static initModules(moduleArrayObj:Array<Module>):Array<Module> { + + let modules = new Array<Module>(); + + if (moduleArrayObj) { + _.forEach(moduleArrayObj, (module:Module):void => { + if (module.type === "org.openecomp.groups.VfModule") { + modules.push(new Module(module)); + } + }); + } + return modules; + }; + + static initInputs(inputsObj:Array<PropertyBEModel>):Array<PropertyBEModel> { + + let inputs = new Array<PropertyBEModel>(); + + if(inputsObj) { + _.forEach(inputsObj, (input:PropertyBEModel):void => { + inputs.push(new PropertyBEModel(input)); + }) + } + + return inputs; + } + + static initBeProperties(propertiesObj: Array<PropertyBEModel>): Array<PropertyBEModel> { + + let properties = new Array<PropertyBEModel>(); + + if (propertiesObj) { + _.forEach(propertiesObj, (property: PropertyBEModel): void => { + properties.push(new PropertyBEModel(property)); + }) + } + + return properties; + } + + static initComponentInstanceRelations = (componentInstanceRelationsObj:Array<RelationshipModel>):Array<RelationshipModel> => { + if (componentInstanceRelationsObj) { + let componentInstancesRelations: Array<RelationshipModel> = []; + _.forEach(componentInstanceRelationsObj, (instanceRelation:RelationshipModel):void => { + componentInstancesRelations.push(new RelationshipModel(instanceRelation)); + }); + return componentInstancesRelations; + } + }; +} + diff --git a/catalog-ui/src/app/utils/component-factory.ts b/catalog-ui/src/app/utils/component-factory.ts new file mode 100644 index 0000000000..18edbfb02c --- /dev/null +++ b/catalog-ui/src/app/utils/component-factory.ts @@ -0,0 +1,161 @@ +'use strict'; +import {DEFAULT_ICON, ResourceType, ComponentType} from "./constants"; +import {ServiceService, CacheService, ResourceService, ProductService} from "app/services"; +import {IMainCategory, ISubCategory, ICsarComponent, Component, Resource, Service, Product} from "app/models"; +import {ComponentMetadata} from "../models/component-metadata"; +import {ComponentServiceNg2} from "../ng2/services/component-services/component.service"; +import {ComponentGenericResponse} from "../ng2/services/responses/component-generic-response"; + + +export class ComponentFactory { + + static '$inject' = [ + 'Sdc.Services.Components.ResourceService', + 'Sdc.Services.Components.ServiceService', + 'Sdc.Services.Components.ProductService', + 'Sdc.Services.CacheService', + '$q', + 'ComponentServiceNg2' + ]; + + constructor(private ResourceService:ResourceService, + private ServiceService:ServiceService, + private ProductService:ProductService, + private cacheService:CacheService, + private $q:ng.IQService, + private ComponentServiceNg2: ComponentServiceNg2) { + } + + public createComponent = (component:Component):Component => { + let newComponent:Component; + switch (component.componentType) { + + case 'SERVICE': + newComponent = new Service(this.ServiceService, this.$q, <Service> component); + break; + + case 'RESOURCE': + newComponent = new Resource(this.ResourceService, this.$q, <Resource> component); + break; + + case 'PRODUCT': + newComponent = new Product(this.ProductService, this.$q, <Product> component); + break; + } + return newComponent; + }; + + public createProduct = (product:Product):Product => { + let newProduct:Product = new Product(this.ProductService, this.$q, <Product> product); + return newProduct; + }; + + public createService = (service:Service):Service => { + let newService:Service = new Service(this.ServiceService, this.$q, <Service> service); + return newService; + }; + + public createResource = (resource:Resource):Resource => { + let newResource:Resource = new Resource(this.ResourceService, this.$q, <Resource> resource); + return newResource; + }; + + public createFromCsarComponent = (csar:ICsarComponent):Component => { + let newResource:Resource = <Resource>this.createEmptyComponent(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:IMainCategory)=> { + if (main.subcategories) { + allSubs = allSubs.concat(main.subcategories); + } + }); + + let selectedCategory:IMainCategory = _.find(availableCategories, function (main:IMainCategory) { + return main.uniqueId === csar.category; + }); + + let selectedSubCategory:ISubCategory = _.find(allSubs, (sub:ISubCategory)=> { + return sub.uniqueId === csar.subCategory; + }); + + // Build the categories and sub categories array (same format as component category) + let categories:Array<IMainCategory> = new Array(); + let subcategories:Array<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):Component => { + let newComponent:Component; + + switch (componentType) { + + case ComponentType.SERVICE: + newComponent = new Service(this.ServiceService, this.$q); + break; + + case ComponentType.RESOURCE: + case ResourceType.VF: + case ResourceType.VL: + case ResourceType.VFC: + case ResourceType.CP: + newComponent = new Resource(this.ResourceService, this.$q); + break; + + case ComponentType.PRODUCT: + newComponent = new Product(this.ProductService, this.$q); + break; + } + newComponent.componentType = componentType; + newComponent.tags = []; + newComponent.icon = DEFAULT_ICON; + return newComponent; + }; + + public getComponentFromServer = (componentType:string, componentId:string):ng.IPromise<Component> => { + let newComponent:Component = this.createEmptyComponent(componentType); + newComponent.setUniqueId(componentId); + return newComponent.getComponent(); + }; + + public createComponentOnServer = (componentObject:Component):ng.IPromise<Component> => { + let component:Component = this.createComponent(componentObject); + return component.createComponentOnServer(); + + }; + + public getComponentWithMetadataFromServer = (componentType:string, componentId:string):ng.IPromise<Component> => { + let deferred = this.$q.defer(); + let component = this.createEmptyComponent(componentType); + component.setUniqueId(componentId); + this.ComponentServiceNg2.getComponentMetadata(component).subscribe((response:ComponentGenericResponse) => { + component.setComponentMetadata(response.metadata); + deferred.resolve(component); + }); + return deferred.promise; + } +} diff --git a/catalog-ui/src/app/utils/component-instance-factory.ts b/catalog-ui/src/app/utils/component-instance-factory.ts new file mode 100644 index 0000000000..df92f20c90 --- /dev/null +++ b/catalog-ui/src/app/utils/component-instance-factory.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 3/7/2016. + */ +'use strict'; +import {ComponentInstance, ServiceInstance, ProductInstance, ResourceInstance, Component} from "../models"; +import {LeftPaletteComponent} from "../models/components/displayComponent"; + +export class ComponentInstanceFactory { + + static createComponentInstance(componentInstance:ComponentInstance):ComponentInstance { + let newComponentInstance:ComponentInstance; + switch (componentInstance.originType) { + case 'SERVICE': + newComponentInstance = new ServiceInstance(componentInstance); + break; + + case 'PRODUCT': + newComponentInstance = new ProductInstance(componentInstance); + break; + + default : + newComponentInstance = new ResourceInstance(componentInstance); + break; + } + return newComponentInstance; + }; + + public createEmptyComponentInstance = (componentInstanceType?:string):ComponentInstance => { + let newComponentInstance:ComponentInstance; + switch (componentInstanceType) { + case 'SERVICE': + newComponentInstance = new ServiceInstance(); + break; + + case 'PRODUCT': + newComponentInstance = new ProductInstance(); + break; + + default : + newComponentInstance = new ResourceInstance(); + break; + } + return newComponentInstance; + }; + + public createComponentInstanceFromComponent = (component:LeftPaletteComponent):ComponentInstance => { + let newComponentInstance: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/src/app/utils/constants.ts b/catalog-ui/src/app/utils/constants.ts new file mode 100644 index 0000000000..d55079d662 --- /dev/null +++ b/catalog-ui/src/app/utils/constants.ts @@ -0,0 +1,277 @@ +/** + * Created by obarda on 2/18/2016. + */ + +export let DEFAULT_ICON = 'defaulticon'; +export let CP_END_POINT = 'CpEndPoint'; +export let CHANGE_COMPONENT_CSAR_VERSION_FLAG = 'changeComponentCsarVersion'; + +export class ComponentType { + static SERVICE = 'SERVICE'; + static RESOURCE = 'RESOURCE'; + static PRODUCT = 'PRODUCT'; +} + +export class ServerTypeUrl { + static RESOURCES = 'resources/'; + static SERVICES = 'services/'; + static PRODUCTS = 'product/'; +} + +export class ResourceType { + static VF = 'VF'; + static VL = 'VL'; + static CP = 'CP'; + static VFC = 'VFC'; + static VFCMT = 'VFCMT'; +} + +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 HEAT_VOL = "HEAT_VOL"; + static HEAT_NET = "HEAT_NET"; + 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 ROOT_DATA_TYPE = "tosca.datatypes.Root"; + 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 = '/assets/styles/images/resource-icons/'; + public static SERVICE_ICONS = '/assets/styles/images/service-icons/'; + public static SELECTED_UCPE_INSTANCE = '/assets/styles/images/resource-icons/selectedUcpeInstance.png'; + public static SELECTED_CP_INSTANCE = '/assets/styles/images/resource-icons/selectedCPInstance.png'; + public static SELECTED_VL_INSTANCE = '/assets/styles/images/resource-icons/selectedVLInstance.png'; + public static CANVAS_PLUS_ICON = '/assets/styles/images/resource-icons/canvasPlusIcon.png'; + public static MODULE_ICON = '/assets/styles/images/resource-icons/module.png'; + public static OPEN_MODULE_ICON = '/assets/styles/images/resource-icons/openModule.png'; + public static OPEN_MODULE_HOVER_ICON = '/assets/styles/images/resource-icons/openModuleHover.png'; + public static CLOSE_MODULE_ICON = '/assets/styles/images/resource-icons/closeModule.png'; + public static CLOSE_MODULE_HOVER_ICON = '/assets/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; + // public static COMPOSITION_HEADER_OFFSET = 50; + // public static COMPOSITION_NODE_MENU_WIDTH = 230; + // public static COMPOSITION_NODE_MENU_HEIGHT = 200; + // public static COMPOSITION_RIGHT_PANEL_OFFSET = 300; +} + + +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_PROPERTIES_ASSIGNMENT = 'workspace.properties_assignment'; + public static WORKSPACE_REQUIREMENTS_AND_CAPABILITIES = 'workspace.reqAndCap'; + public static WORKSPACE_NG2 = 'workspace.ng2'; +} + +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 UNIQUE_GROUP_PROPERTIES_NAME { + public static MIN_VF_MODULE_INSTANCES = 'min_vf_module_instances'; + public static MAX_VF_MODULE_INSTANCES = 'max_vf_module_instances'; + public static INITIAL_COUNT = 'initial_count'; + public static IS_BASE = 'isBase'; + public static VF_MODULE_TYPE = 'vf_module_type'; + public static VF_MODULE_LABEL = 'vf_module_label'; + public static VF_MODULE_DESCRIPTION = 'vf_module_description'; + public static VOLUME_GROUP = 'volume_group'; +} + + +export class GRAPH_EVENTS { + static ON_COMPOSITION_GRAPH_DATA_LOADED = 'onCompositionGraphDataLoaded'; + static ON_DEPLOYMENT_GRAPH_DATA_LOADED = 'onDeploymentGraphDataLoaded'; + 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'; + static ON_CREATE_COMPONENT_INSTANCE = 'onCreateComponentInstance'; +} + + +export class COMPONENT_FIELDS { + static COMPONENT_INSTANCES_PROPERTIES = "componentInstancesProperties"; + static COMPONENT_INSTANCES_ATTRIBUTES = "componentInstancesAttributes"; + static COMPONENT_ATTRIBUTES = "attributes"; + static COMPONENT_INSTANCES = "componentInstances"; + static COMPONENT_INSTANCES_RELATION = "componentInstancesRelations"; + static COMPONENT_INPUTS = "inputs"; + static COMPONENT_METADATA = "metadata"; + static COMPONENT_DEPLOYMENT_ARTIFACTS = "deploymentArtifacts"; + static COMPONENT_INFORMATIONAL_ARTIFACTS = "artifacts"; + static COMPONENT_PROPERTIES = "properties"; + static COMPONENT_CAPABILITIES = "capabilities"; + static COMPONENT_REQUIREMENTS = "requirements"; + static COMPONENT_TOSCA_ARTIFACTS = "toscaArtifacts"; + static COMPONENT_GROUPS = "groups"; + +} +export class API_QUERY_PARAMS { + static INCLUDE = "include"; +} diff --git a/catalog-ui/src/app/utils/dictionary/dictionary.ts b/catalog-ui/src/app/utils/dictionary/dictionary.ts new file mode 100644 index 0000000000..fd2a028c34 --- /dev/null +++ b/catalog-ui/src/app/utils/dictionary/dictionary.ts @@ -0,0 +1,235 @@ +/** + + This code was copy from collections.ts lib + https://github.com/basarat/typescript-collections + **/ +'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/src/app/utils/file-utils.ts b/catalog-ui/src/app/utils/file-utils.ts new file mode 100644 index 0000000000..d8c18229c3 --- /dev/null +++ b/catalog-ui/src/app/utils/file-utils.ts @@ -0,0 +1,51 @@ +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); + + var clickEvent = new MouseEvent("click", { + "view": window, + "bubbles": true, + "cancelable": true + }); + downloadLink.dispatchEvent(clickEvent); + + } +} diff --git a/catalog-ui/src/app/utils/functions.ts b/catalog-ui/src/app/utils/functions.ts new file mode 100644 index 0000000000..24f8008393 --- /dev/null +++ b/catalog-ui/src/app/utils/functions.ts @@ -0,0 +1,35 @@ +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/src/app/utils/menu-handler.ts b/catalog-ui/src/app/utils/menu-handler.ts new file mode 100644 index 0000000000..1dc5a203e2 --- /dev/null +++ b/catalog-ui/src/app/utils/menu-handler.ts @@ -0,0 +1,125 @@ +'use strict'; +import {WorkspaceMode, ComponentState} from "./constants"; +import {IAppConfigurtaion, IAppMenu, Component} from "../models"; +import {ComponentFactory} from "./component-factory"; +import {ModalsHandler} from "./modals-handler"; + +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 + alertModal:string; + conformanceLevelModal: boolean; // Call validateConformanceLevel API and shows conformanceLevelModal if necessary, then continue with action or invokes another action + 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', + '$filter', + 'ModalsHandler', + '$state', + '$q' + ]; + + constructor(private sdcConfig:IAppConfigurtaion, + private sdcMenu:IAppMenu, + private ComponentFactory:ComponentFactory, + private $filter:ng.IFilterService, + private ModalsHandler:ModalsHandler, + private $state:ng.ui.IStateService, + private $q:ng.IQService) { + + } + + + generateBreadcrumbsModelFromComponents = (components:Array<Component>, selected: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:Component) => { + return item.uuid === selected.uuid; + }); + + // If not found search by invariantUUID + if (undefined == selectedItem) { + selectedItem = _.find(components, (item:Component) => { + //invariantUUID && Certified State matches between major versions + return item.invariantUUID === selected.invariantUUID && item.lifecycleState === ComponentState.CERTIFIED; + }); + } + + // If not found search by name (name is unique). + if (undefined == selectedItem) { + selectedItem = _.find(components, (item:Component) => { + return item.name === selected.name; + }); + } + + result.selectedIndex = components.indexOf(selectedItem); + components[result.selectedIndex] = selected; + let clickItemCallback = (component:Component):ng.IPromise<boolean> => { + this.$state.go('workspace.general', { + id: component.uniqueId, + type: component.componentType.toLowerCase(), + mode: WorkspaceMode.VIEW + }); + return this.$q.when(true); + }; + + components.forEach((component: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/src/app/utils/modals-handler.ts b/catalog-ui/src/app/utils/modals-handler.ts new file mode 100644 index 0000000000..fe864cb658 --- /dev/null +++ b/catalog-ui/src/app/utils/modals-handler.ts @@ -0,0 +1,389 @@ +import {PropertyModel, Component, ArtifactModel, Distribution, InputModel, DisplayModule, InputPropertyBase} from "../models"; +import {IEmailModalModel} from "../view-models/modals/email-modal/email-modal-view-model"; +import {IClientMessageModalModel} from "../view-models/modals/message-modal/message-client-modal/client-message-modal-view-model"; +import {IServerMessageModalModel} from "../view-models/modals/message-modal/message-server-modal/server-message-modal-view-model"; +import {IConfirmationModalModel} from "../view-models/modals/confirmation-modal/confirmation-modal-view-model"; +import {ModalType} from "./constants"; +import {AttributeModel} from "../models/attributes"; + +export interface IModalsHandler { + + + openDistributionStatusModal (distribution:Distribution, status:string, component:Component):ng.IPromise<any>; + openConfirmationModal (title:string, message:string, showComment:boolean, size?:string):ng.IPromise<any>; + openAlertModal (title:string, message:string, size?:string):ng.IPromise<any>; + openEmailModal(emailModel:IEmailModalModel):ng.IPromise<any>; + openServerMessageModal(data:IServerMessageModalModel):ng.IPromise<any>; + openClientMessageModal(data:IClientMessageModalModel):ng.IPromise<ng.ui.bootstrap.IModalServiceInstance>; + openArtifactModal(artifact:ArtifactModel, component:Component):ng.IPromise<any>; + openEditPropertyModal(property:PropertyModel, component:Component, filteredProperties:Array<PropertyModel>, isPropertyOwnValue:boolean):ng.IPromise<any>; +} + +export class ModalsHandler implements IModalsHandler { + + static '$inject' = [ + '$uibModal', + '$q' + ]; + + constructor(private $uibModal:ng.ui.bootstrap.IModalService, + private $q:ng.IQService) { + } + + + + + openDistributionStatusModal = (distribution:Distribution, status:string, component:Component):ng.IPromise<any> => { + let deferred = this.$q.defer(); + let modalOptions:ng.ui.bootstrap.IModalSettings = { + templateUrl: '../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, + 'component': component + }; + } + } + }; + let modalInstance:ng.ui.bootstrap.IModalServiceInstance = this.$uibModal.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, ModalType.ALERT, size); + }; + + openConfirmationModal = (title:string, message:string, showComment:boolean, size?:string):ng.IPromise<any> => { + return this.openConfirmationModalBase(title, message, showComment, ModalType.STANDARD, size); + }; + + private openConfirmationModalBase = (title:string, message:string, showComment:boolean, type:ModalType, size?:string):ng.IPromise<any> => { + let deferred = this.$q.defer(); + let modalOptions:ng.ui.bootstrap.IModalSettings = { + templateUrl: '../view-models/modals/confirmation-modal/confirmation-modal-view.html', + controller: 'Sdc.ViewModels.ConfirmationModalViewModel', + size: size ? size : 'sdc-sm', + backdrop: 'static', + resolve: { + confirmationModalModel: ():IConfirmationModalModel => { + let model:IConfirmationModalModel = { + title: title, + message: message, + showComment: showComment, + type: type + }; + return model; + } + } + }; + + let modalInstance:ng.ui.bootstrap.IModalServiceInstance = this.$uibModal.open(modalOptions); + deferred.resolve(modalInstance.result); + return deferred.promise; + }; + + openEmailModal = (emailModel:IEmailModalModel):ng.IPromise<any> => { + + let deferred = this.$q.defer(); + let modalOptions:ng.ui.bootstrap.IModalSettings = { + templateUrl: '../view-models/modals/email-modal/email-modal-view.html', + controller: 'Sdc.ViewModels.EmailModalViewModel', + size: 'sdc-sm', + backdrop: 'static', + resolve: { + emailModalModel: ():IEmailModalModel => { + return emailModel; + } + } + }; + let modalInstance:ng.ui.bootstrap.IModalServiceInstance = this.$uibModal.open(modalOptions); + deferred.resolve(modalInstance.result); + return deferred.promise; + + }; + + openServerMessageModal = (data:IServerMessageModalModel):ng.IPromise<any> => { + let deferred = this.$q.defer(); + let modalOptions:ng.ui.bootstrap.IModalSettings = { + templateUrl: '../view-models/modals/message-modal/message-server-modal/server-message-modal-view.html', + controller: 'Sdc.ViewModels.ServerMessageModalViewModel', + size: 'sdc-sm', + backdrop: 'static', + resolve: { + serverMessageModalModel: ():IServerMessageModalModel => { + return data; + } + } + }; + + let modalInstance:ng.ui.bootstrap.IModalServiceInstance = this.$uibModal.open(modalOptions); + deferred.resolve(modalInstance.result); + return deferred.promise; + }; + + openClientMessageModal = (data:IClientMessageModalModel):ng.IPromise<any> => { + let deferred = this.$q.defer(); + let modalOptions:ng.ui.bootstrap.IModalSettings = { + templateUrl: '../view-models/modals/message-modal/message-client-modal/client-message-modal-view.html', + controller: 'Sdc.ViewModels.ClientMessageModalViewModel', + size: 'sdc-sm', + backdrop: 'static', + resolve: { + clientMessageModalModel: ():IClientMessageModalModel => { + return data; + } + } + }; + let modalInstance:ng.ui.bootstrap.IModalServiceInstance = this.$uibModal.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 = { + templateUrl: '../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.$uibModal.open(modalOptions); + deferred.resolve(modalInstance.result); + return deferred.promise; + }; + + openEditEnvParametersModal = (artifactResource:ArtifactModel, component?:Component):ng.IPromise<any> => { + let deferred = this.$q.defer(); + let modalOptions:ng.ui.bootstrap.IModalSettings = { + templateUrl: '../view-models/forms/env-parameters-form/env-parameters-form.html', + controller: 'Sdc.ViewModels.EnvParametersFormViewModel', + size: 'sdc-xl', + backdrop: 'static', + resolve: { + artifact: ():ArtifactModel => { + return artifactResource; + }, + component: ():Component => { + return component; + } + } + }; + let modalInstance:ng.ui.bootstrap.IModalServiceInstance = this.$uibModal.open(modalOptions); + deferred.resolve(modalInstance.result); + return deferred.promise; + }; + + openEditInputValueModal = (input:InputModel):ng.IPromise<any> => { + let deferred = this.$q.defer(); + let modalOptions:ng.ui.bootstrap.IModalSettings = { + templateUrl: '../view-models/forms/input-form/input-form-view.html', + controller: 'Sdc.ViewModels.InputFormViewModel', + size: 'sdc-md', + backdrop: 'static', + resolve: { + input: ():InputModel => { + return input; + } + } + }; + let modalInstance:ng.ui.bootstrap.IModalServiceInstance = this.$uibModal.open(modalOptions); + deferred.resolve(modalInstance.result); + return deferred.promise; + }; + + openArtifactModal = (artifact:ArtifactModel, component:Component):ng.IPromise<any> => { + let deferred = this.$q.defer(); + + let modalOptions:ng.ui.bootstrap.IModalSettings = { + templateUrl: '../view-models/forms/artifact-form/artifact-form-view.html', + controller: 'Sdc.ViewModels.ArtifactResourceFormViewModel', + size: 'sdc-md', + backdrop: 'static', + keyboard: false, + resolve: { + artifact: ():ArtifactModel => { + return artifact; + }, + component: ():Component => { + return component; + } + } + }; + + let modalInstance:ng.ui.bootstrap.IModalServiceInstance = this.$uibModal.open(modalOptions); + deferred.resolve(modalInstance.result); + return deferred.promise; + }; + + + /** + * + * This function openes up the edit property modal + * + * @param property - the property to edit + * @param component - the component who is the owner of the property + * @param filteredProperties - the filtered properties list to scroll between in the edit modal + * @param isPropertyValueOwner - boolean telling if the component is eligible of editing the property + * @returns {IPromise<T>} - Promise telling if the modal has opened or not + */ + openEditPropertyModal = (property:PropertyModel, component:Component, filteredProperties:Array<PropertyModel>, isPropertyValueOwner:boolean):ng.IPromise<any> => { + let deferred = this.$q.defer(); + + let modalOptions:ng.ui.bootstrap.IModalSettings = { + templateUrl: '../view-models/forms/property-forms/component-property-form/property-form-view.html', + controller: 'Sdc.ViewModels.PropertyFormViewModel', + size: 'sdc-l', + backdrop: 'static', + keyboard: false, + resolve: { + property: ():PropertyModel => { + return property; + }, + component: ():Component => { + return <Component> component; + }, + filteredProperties: ():Array<PropertyModel> => { + return filteredProperties; + }, + isPropertyValueOwner: ():boolean => { + return isPropertyValueOwner; + } + } + }; + + let modalInstance:ng.ui.bootstrap.IModalServiceInstance = this.$uibModal.open(modalOptions); + deferred.resolve(modalInstance.result); + return deferred.promise; + }; + + + openEditModulePropertyModal = (property:PropertyModel, component:Component, selectedModule:DisplayModule):ng.IPromise<any> => { + let deferred = this.$q.defer(); + + let modalOptions:ng.ui.bootstrap.IModalSettings = { + templateUrl: '../view-models/forms/property-forms/base-property-form/property-form-base-view.html', + controller: 'Sdc.ViewModels.ModulePropertyView', + size: 'sdc-l', + backdrop: 'static', + keyboard: false, + resolve: { + originalProperty: ():PropertyModel => { + return property; + }, + component: ():Component => { + return <Component> component; + }, + selectedModule: ():DisplayModule => { + return selectedModule; + } + } + }; + + let modalInstance:ng.ui.bootstrap.IModalServiceInstance = this.$uibModal.open(modalOptions); + deferred.resolve(modalInstance.result); + return deferred.promise; + }; + + openSelectDataTypeModal = (property:PropertyModel, component:Component, filteredProperties:Array<PropertyModel>, propertiesMap:Array<InputPropertyBase>):ng.IPromise<any> => { + let deferred = this.$q.defer(); + + let modalOptions:ng.ui.bootstrap.IModalSettings = { + templateUrl: '../view-models/forms/property-forms/base-property-form/property-form-base-view.html', + controller: 'Sdc.ViewModels.SelectDataTypeViewModel', + size: 'sdc-l', + backdrop: 'static', + keyboard: false, + resolve: { + originalProperty: ():PropertyModel => { + return property; + }, + component: ():Component => { + return <Component> component; + }, + filteredProperties: ():Array<PropertyModel> => { + return filteredProperties; + }, + propertiesMap: ():Array<InputPropertyBase>=> { + return propertiesMap; + } + } + }; + + let modalInstance:ng.ui.bootstrap.IModalServiceInstance = this.$uibModal.open(modalOptions); + deferred.resolve(modalInstance.result); + return deferred.promise; + }; + + public openEditAttributeModal = (attribute:AttributeModel, component: Component):void => { + + let modalOptions:ng.ui.bootstrap.IModalSettings = { + templateUrl: '../view-models/forms/attribute-form/attribute-form-view.html', + controller: 'Sdc.ViewModels.AttributeFormViewModel', + size: 'sdc-md', + backdrop: 'static', + keyboard: false, + resolve: { + attribute: ():AttributeModel => { + return attribute; + }, + component: ():Component => { + return component; + } + } + }; + this.$uibModal.open(modalOptions); + }; + + public openUpdateComponentInstanceNameModal = (currentComponent: Component):ng.IPromise<any> => { + let deferred = this.$q.defer(); + + let modalOptions:ng.ui.bootstrap.IModalSettings = { + templateUrl: '../view-models/forms/resource-instance-name-form/resource-instance-name-view.html', + controller: 'Sdc.ViewModels.ResourceInstanceNameViewModel', + size: 'sdc-sm', + backdrop: 'static', + resolve: { + component: ():Component => { + return currentComponent; + } + } + }; + + let modalInstance:ng.ui.bootstrap.IModalServiceInstance = this.$uibModal.open(modalOptions); + deferred.resolve(modalInstance.result); + return deferred.promise; + }; + + public openConformanceLevelModal = ():ng.IPromise<any> => { + let deferred = this.$q.defer(); + let modalOptions:ng.ui.bootstrap.IModalSettings = { + templateUrl: '../view-models/workspace/conformance-level-modal/conformance-level-modal-view.html', + controller: 'Sdc.ViewModels.ConformanceLevelModalViewModel', + size: 'sdc-sm', + backdrop: 'static', + resolve: { + + } + }; + + let modalInstance:ng.ui.bootstrap.IModalServiceInstance = this.$uibModal.open(modalOptions); + deferred.resolve(modalInstance.result); + return deferred.promise; + }; + +} diff --git a/catalog-ui/src/app/utils/prototypes.ts b/catalog-ui/src/app/utils/prototypes.ts new file mode 100644 index 0000000000..2aa718a06c --- /dev/null +++ b/catalog-ui/src/app/utils/prototypes.ts @@ -0,0 +1,64 @@ +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; + }; +} diff --git a/catalog-ui/src/app/utils/validation-utils.ts b/catalog-ui/src/app/utils/validation-utils.ts new file mode 100644 index 0000000000..9246d3350d --- /dev/null +++ b/catalog-ui/src/app/utils/validation-utils.ts @@ -0,0 +1,153 @@ +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/src/app/view-models/admin-dashboard/add-category-modal/add-category-modal-view-model.ts b/catalog-ui/src/app/view-models/admin-dashboard/add-category-modal/add-category-modal-view-model.ts new file mode 100644 index 0000000000..c421e632da --- /dev/null +++ b/catalog-ui/src/app/view-models/admin-dashboard/add-category-modal/add-category-modal-view-model.ts @@ -0,0 +1,72 @@ +'use strict'; +import {ICategoryResourceClass, ICategoryResource} from "../../../services/category-resource-service"; + +interface IAddCategoryModalViewModelScope extends ng.IScope { + category:ICategoryResource; + modelType:string; + footerButtons:Array<any>; + forms:any; + + save():void; + close():void; +} + +export class AddCategoryModalViewModel { + + static '$inject' = [ + '$scope', + 'Sdc.Services.CategoryResourceService', + '$uibModalInstance', + 'parentCategory', + 'type' + ]; + + constructor(private $scope:IAddCategoryModalViewModelScope, + private categoryResourceService:ICategoryResourceClass, + private $uibModalInstance:ng.ui.bootstrap.IModalServiceInstance, + private parentCategory: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.$uibModalInstance.dismiss(); + }; + + this.$scope.save = ():void => { + + let onOk = (newCategory:ICategoryResource):void => { + this.$uibModalInstance.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/src/app/view-models/admin-dashboard/add-category-modal/add-category-modal-view.html b/catalog-ui/src/app/view-models/admin-dashboard/add-category-modal/add-category-modal-view.html new file mode 100644 index 0000000000..a9df3e6009 --- /dev/null +++ b/catalog-ui/src/app/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="forms.editForm.categoryName.$dirty && forms.editForm.categoryName.$invalid"> + <span ng-show="forms.editForm.categoryName.$error.required" translate="CREATE_CATEGORY_MODAL_REQUIRED" translate-values="{'modelType': '{{modelType}}' }"></span> + <span ng-show="forms.editForm.categoryName.$error.minlength" translate="CREATE_CATEGORY_MODAL_MINLENGTH" translate-values="{'minlength': '4', 'modelType': '{{modelType}}' }"></span> + <span ng-show="forms.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/src/app/view-models/admin-dashboard/add-category-modal/add-category-modal-view.less b/catalog-ui/src/app/view-models/admin-dashboard/add-category-modal/add-category-modal-view.less new file mode 100644 index 0000000000..39d84aab23 --- /dev/null +++ b/catalog-ui/src/app/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/src/app/view-models/admin-dashboard/admin-dashboard-view-model.ts b/catalog-ui/src/app/view-models/admin-dashboard/admin-dashboard-view-model.ts new file mode 100644 index 0000000000..c8503bce42 --- /dev/null +++ b/catalog-ui/src/app/view-models/admin-dashboard/admin-dashboard-view-model.ts @@ -0,0 +1,61 @@ +'use strict'; +import {CacheService} from "app/services"; +import {IAppConfigurtaion} from "app/models"; + +interface IAdminDashboardViewModelScope extends ng.IScope { + version:string; + sdcConfig:IAppConfigurtaion; + isLoading:boolean; + currentTab:string; + templateUrl:string; + monitorUrl:string; + moveToTab(tab:string):void; + isSelected(tab:string):boolean; +} + + +export class AdminDashboardViewModel { + static '$inject' = [ + '$scope', + '$templateCache', + 'Sdc.Services.CacheService', + 'sdcConfig' + ]; + + constructor(private $scope:IAdminDashboardViewModelScope, + private $templateCache:ng.ITemplateCacheService, + private cacheService:CacheService, + private sdcConfig: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="user-management-view.html"; + this.$templateCache.put("user-management-view.html", require('app/view-models/admin-dashboard/user-management/user-management-view.html')); + } + else if (tab === 'CATEGORY_MANAGEMENT') { + this.$scope.templateUrl="category-management-view.html"; + this.$templateCache.put("category-management-view.html", require('app/view-models/admin-dashboard/category-management/category-management-view.html')); + } + this.$scope.currentTab = tab; + }; + + this.$scope.moveToTab('USER_MANAGEMENT'); + + + } +} diff --git a/catalog-ui/src/app/view-models/admin-dashboard/admin-dashboard-view.html b/catalog-ui/src/app/view-models/admin-dashboard/admin-dashboard-view.html new file mode 100644 index 0000000000..150f7c2554 --- /dev/null +++ b/catalog-ui/src/app/view-models/admin-dashboard/admin-dashboard-view.html @@ -0,0 +1,26 @@ +<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> + + <ecomp-footer></ecomp-footer> +</div> diff --git a/catalog-ui/src/app/view-models/admin-dashboard/admin-dashboard.less b/catalog-ui/src/app/view-models/admin-dashboard/admin-dashboard.less new file mode 100644 index 0000000000..874a02c431 --- /dev/null +++ b/catalog-ui/src/app/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/src/app/view-models/admin-dashboard/category-management/category-management-view-model.ts b/catalog-ui/src/app/view-models/admin-dashboard/category-management/category-management-view-model.ts new file mode 100644 index 0000000000..ba390c4bee --- /dev/null +++ b/catalog-ui/src/app/view-models/admin-dashboard/category-management/category-management-view-model.ts @@ -0,0 +1,176 @@ +'use strict'; +import {ModalsHandler, ValidationUtils} from "app/utils"; +import {CacheService, ICategoryResource} from "app/services"; +import {IAppConfigurtaion} from "app/models"; +import {ComponentType} from "../../../utils/constants"; + +interface ICategoryManagementViewModelScope extends ng.IScope { + SERVICE:string; + RESOURCE:string; + categoriesToShow:Array<ICategoryResource>; + serviceCategories:Array<ICategoryResource>; + resourceCategories:Array<ICategoryResource>; + selectedCategory:ICategoryResource; + selectedSubCategory:ICategoryResource; + modalInstance:ng.ui.bootstrap.IModalServiceInstance; + isLoading:boolean; + type:string; + namePattern:RegExp; + + selectCategory(category:ICategoryResource):void; + selectSubCategory(subcategory:ICategoryResource):void; + selectType(type:string):void; + deleteCategory(category:ICategoryResource, subCategory:ICategoryResource):void; + createCategoryModal(parentCategory:ICategoryResource):void; +} + +export class CategoryManagementViewModel { + static '$inject' = [ + '$scope', + 'sdcConfig', + 'Sdc.Services.CacheService', + '$uibModal', + '$filter', + 'ValidationUtils', + 'ModalsHandler' + ]; + + constructor(private $scope:ICategoryManagementViewModelScope, + private sdcConfig:IAppConfigurtaion, + private cacheService:CacheService, + private $uibModal:ng.ui.bootstrap.IModalService, + private $filter:ng.IFilterService, + private ValidationUtils:ValidationUtils, + private ModalsHandler:ModalsHandler) { + + this.initScope(); + this.$scope.selectType(ComponentType.SERVICE.toLocaleLowerCase()); + + } + + private initScope = ():void => { + let scope:ICategoryManagementViewModelScope = this.$scope; + scope.SERVICE = ComponentType.SERVICE.toLocaleLowerCase(); + scope.RESOURCE = ComponentType.RESOURCE.toLocaleLowerCase(); + + scope.namePattern = this.ValidationUtils.getValidationPattern('category'); + + scope.selectCategory = (category:ICategoryResource) => { + if (scope.selectedCategory !== category) { + scope.selectedSubCategory = null; + } + scope.selectedCategory = category; + }; + scope.selectSubCategory = (subcategory: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:ICategoryResource):void => { + //can't create a sub category for service + if (parentCategory && scope.type === ComponentType.SERVICE.toLowerCase()) { + return; + } + + let type:string = scope.type; + + let onOk = (newCategory: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: 'src/app/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.$uibModal.open(modalOptions); + scope.modalInstance.result.then(onOk, onCancel); + + }; + + scope.deleteCategory = (category:ICategoryResource, subCategory: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<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' : 'category'; + 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/src/app/view-models/admin-dashboard/category-management/category-management-view.html b/catalog-ui/src/app/view-models/admin-dashboard/category-management/category-management-view.html new file mode 100644 index 0000000000..95a002d3d7 --- /dev/null +++ b/catalog-ui/src/app/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/src/app/view-models/admin-dashboard/category-management/category-management.less b/catalog-ui/src/app/view-models/admin-dashboard/category-management/category-management.less new file mode 100644 index 0000000000..011122c9e8 --- /dev/null +++ b/catalog-ui/src/app/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/src/app/view-models/admin-dashboard/ecomp/ecomp-view.html b/catalog-ui/src/app/view-models/admin-dashboard/ecomp/ecomp-view.html new file mode 100644 index 0000000000..7c89b545c5 --- /dev/null +++ b/catalog-ui/src/app/view-models/admin-dashboard/ecomp/ecomp-view.html @@ -0,0 +1 @@ +<div></div> diff --git a/catalog-ui/src/app/view-models/admin-dashboard/user-management/user-management-view-model.ts b/catalog-ui/src/app/view-models/admin-dashboard/user-management/user-management-view-model.ts new file mode 100644 index 0000000000..82cc3a74da --- /dev/null +++ b/catalog-ui/src/app/view-models/admin-dashboard/user-management/user-management-view-model.ts @@ -0,0 +1,202 @@ +'use strict'; +import {ModalsHandler} from "app/utils"; +import {IUserResource, IUserResourceClass} from "app/services"; +import {User, IUserProperties, IUser, IAppConfigurtaion} from "app/models"; + +interface IUserManagementViewModelScope extends ng.IScope { + sdcConfig:IAppConfigurtaion; + usersList:Array<IUserProperties>; + isLoading:boolean; + isNewUser:boolean; + sortBy:string; + reverse:boolean; + tableHeadersList:any; + roles:Array<string>; + newUser:IUser; + currentUser: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', + 'UserIdValidationPattern', + '$filter', + 'ModalsHandler' + ]; + + constructor(private $scope:IUserManagementViewModelScope, + private sdcConfig:IAppConfigurtaion, + private userResourceService:IUserResourceClass, + private UserIdValidationPattern:RegExp, + private $filter:ng.IFilterService, + private ModalsHandler: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<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:IUserResource = <IUserResource>{}; + this.$scope.newUser = new 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: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 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(); + } + //if(this.$scope.editForm['contactId'].$viewValue === '' && this.$scope.editForm['role'].$viewValue){ + // this.$scope.editForm.$setPristine(); + //} + }; + } +} diff --git a/catalog-ui/src/app/view-models/admin-dashboard/user-management/user-management-view.html b/catalog-ui/src/app/view-models/admin-dashboard/user-management/user-management-view.html new file mode 100644 index 0000000000..d2983873cc --- /dev/null +++ b/catalog-ui/src/app/view-models/admin-dashboard/user-management/user-management-view.html @@ -0,0 +1,103 @@ +<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="Search user by name, userId, email or role" 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="row_{{$index}}"> + + <div sdc-smart-tooltip class="sdc-user-management-table-col-general sdc-user-management-flex-item" data-tests-id="firstName_{{$index}}">{{user.firstName || '---'}}</div> + <div sdc-smart-tooltip class="sdc-user-management-table-col-general sdc-user-management-flex-item" data-tests-id="lastName__{{$index}}">{{user.lastName || '---' }}</div> + <div class="sdc-user-management-flex-item" data-tests-id="userId_{{$index}}">{{user.userId || '---'}}</div> + <div sdc-smart-tooltip class="sdc-user-management-table-col-general sdc-user-management-flex-item" data-tests-id="email_{{$index}}">{{user.email || '---'}}</div> + <div class="sdc-user-management-table-col-general sdc-user-management-flex-item"> + <div class="sdc-user-management-table-role-select capitalize sdc-user-management-table-role-label" + data-ng-if="!user.isInEditMode" + data-tests-id="role_{{$index}}" + data-ng-bind="getTitle(user.role)"></div> + <select class="sdc-user-management-table-role-select capitalize" + data-tests-id="selectRole_{{$index}}" + 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="lastActive_{{$index}}">{{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_{{$index}}"> </button> + <button data-ng-show="user.isInEditMode" class="sdc-user-management-table-save-btn" ng-click="saveUserChanges(user)" data-tests-id="save_{{$index}}"> </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_{{$index}}"> </button> + </div> + + </div> + </perfect-scrollbar> + </div> + + </div> + </div> +</div> diff --git a/catalog-ui/src/app/view-models/admin-dashboard/user-management/user-management.less b/catalog-ui/src/app/view-models/admin-dashboard/user-management/user-management.less new file mode 100644 index 0000000000..934faab9e7 --- /dev/null +++ b/catalog-ui/src/app/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/src/app/view-models/catalog/catalog-view-model.ts b/catalog-ui/src/app/view-models/catalog/catalog-view-model.ts new file mode 100644 index 0000000000..0e7e4aaeae --- /dev/null +++ b/catalog-ui/src/app/view-models/catalog/catalog-view-model.ts @@ -0,0 +1,303 @@ +'use strict'; +import {Component, IMainCategory, IGroup, IConfigStatuses, IAppMenu, IAppConfigurtaion, IUserProperties, ISubCategory} from "app/models"; +import {EntityService, IUserResourceClass, CacheService} from "app/services"; +import {ComponentFactory, ResourceType, MenuHandler, ChangeLifecycleStateHandler} from "app/utils"; + + +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<IMainCategory>; + confStatus:IConfigStatuses; + sdcMenu:IAppMenu; + catalogFilterdItems:Array<Component>; + expandedSection:Array<string>; + actionStrategy:any; + user:IUserProperties; + catalogMenuItem:any; + version:string; + sortBy:string; + reverse:boolean; + vfcmtType:string; + + //this is for UI paging + numberOfItemToDisplay:number; + isAllItemDisplay:boolean; + + changeLifecycleState(entity:any, state:string):void; + sectionClick (section:string):void; + order(sortBy:string):void; + getNumOfElements(num:number):string; + goToComponent(component:Component):void; + raiseNumberOfElementToDisplay():void; +} + +export class CatalogViewModel { + static '$inject' = [ + '$scope', + '$filter', + 'Sdc.Services.EntityService', + 'sdcConfig', + 'sdcMenu', + '$state', + '$q', + 'Sdc.Services.UserResourceService', + 'Sdc.Services.CacheService', + 'ComponentFactory', + 'ChangeLifecycleStateHandler', + 'MenuHandler' + ]; + + constructor(private $scope:ICatalogViewModelScope, + private $filter:ng.IFilterService, + private EntityService:EntityService, + private sdcConfig:IAppConfigurtaion, + private sdcMenu:IAppMenu, + private $state:ng.ui.IStateService, + private $q:ng.IQService, + private userResourceService:IUserResourceClass, + private cacheService:CacheService, + private ComponentFactory:ComponentFactory, + private ChangeLifecycleStateHandler:ChangeLifecycleStateHandler, + private MenuHandler:MenuHandler) { + + + this.initScopeMembers(); + this.initCatalogData(); // Async task to get catalog from server. + this.initScopeMethods(); + } + + private initCatalogData = ():void => { + let onSuccess = (followedResponse:Array<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", "category", "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; + + this.$scope.vfcmtType = ResourceType.VFCMT; + }; + + 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: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: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: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: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:IMainCategory, subCategory: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: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:IMainCategory):boolean => { + if (!category.subcategories) { + return false; + } + let allIds = _.map(category.subcategories, (sub:ISubCategory)=> { + return sub.uniqueId; + }); + let total = _.intersection(this.$scope.checkboxesFilter.selectedCategoriesModel, allIds); + return total.length === category.subcategories.length ? true : false; + }; + + private areAllSubCategoryChildsSelected = (subCategory:ISubCategory):boolean => { + if (!subCategory.groupings) { + return false; + } + let allIds = _.map(subCategory.groupings, (group: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/src/app/view-models/catalog/catalog-view.html b/catalog-ui/src/app/view-models/catalog/catalog-view.html new file mode 100644 index 0000000000..03ca4cb81f --- /dev/null +++ b/catalog-ui/src/app/view-models/catalog/catalog-view.html @@ -0,0 +1,199 @@ +<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 | categoryTypeFilter:checkboxesFilter.selectedComponentTypes:checkboxesFilter.selectedResourceSubTypes | 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| filter:{resourceType:('!'+vfcmtType)} | 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> + + <div class='w-sdc-row-flex-items'> + + <!-- Tile new --> + <div data-ng-init="component.filterTerm = component.name + ' ' + component.description + ' ' + component.tags.toString() + ' ' + component.version" + class="sdc-tile-catalog sdc-tile-fix-width" + data-ng-repeat="component in catalogFilterdItems| filter:{resourceType:('!'+vfcmtType)} | entityFilter:checkboxesFilter | filter:search | orderBy:sortBy:reverse | limitTo:numberOfItemToDisplay" + > + + <div class="sdc-tile-header"> + <div class='sdc-tile-header-type' data-ng-class="{'purple': component.isResource(), 'blue': !component.isResource()}"> + <div data-ng-if="component.isResource()" data-tests-id="asset-type">{{component.getComponentSubType()}}</div> + <div data-ng-if="component.isService()">S</div> + </div> + </div> + <div class='sdc-tile-content' data-ng-click="gui.isLoading || goToComponent(component)"> + <div class='sdc-tile-content-icon'> + <div class="{{component.iconSprite}} {{component.icon}}" + data-ng-class="{'sprite-resource-icons': component.isResource(), 'sprite-services-icons': component.isService()}" + data-tests-id="{{component.name}}"></div> + </div> + <div class='sdc-tile-content-info'> + <div class="sdc-tile-content-info-item-name" data-tests-id="{{component.name | resourceName}}" sdc-smart-tooltip>{{component.name | resourceName}}</div> + <div class="sdc-tile-content-info-version-info"> + <div class="sdc-tile-content-info-version-info-text" data-tests-id="{{component.name}}Version">V {{component.version}}</div> + </div> + </div> + </div> + <div class='sdc-tile-footer'> + <div class='sdc-tile-footer-text'>{{component.getStatus(sdcMenu)}}</div> + </div> + + </div> + <!-- Tile new --> + + </div> + + </div> + </perfect-scrollbar> + + </div> + + <top-nav top-lvl-selected-index="1" search-bind="search.filterTerm" version="{{version}}"></top-nav> + + <ecomp-footer></ecomp-footer> + +</div> diff --git a/catalog-ui/src/app/view-models/catalog/catalog.less b/catalog-ui/src/app/view-models/catalog/catalog.less new file mode 100644 index 0000000000..9db9192167 --- /dev/null +++ b/catalog-ui/src/app/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('/assets/styles/images/sprites/sprite-global-old.png') no-repeat -53px -2889px; + width: 14px; + height: 14px; + + } + } + + &.CERTIFIED { + .i-sdc-categories-list-item-icon { + background: url('/assets/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('/assets/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('/assets/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('/assets/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/src/app/view-models/dashboard/dashboard-view-model.ts b/catalog-ui/src/app/view-models/dashboard/dashboard-view-model.ts new file mode 100644 index 0000000000..d0b74a9062 --- /dev/null +++ b/catalog-ui/src/app/view-models/dashboard/dashboard-view-model.ts @@ -0,0 +1,387 @@ +'use strict'; +import {IConfigRoles, IAppConfigurtaion, IAppMenu, IUserProperties, Component} from "app/models"; +import {EntityService, IUserResourceClass, SharingService, CacheService} from "app/services"; +import {ComponentType, ResourceType, MenuHandler, ModalsHandler, ChangeLifecycleStateHandler, SEVERITY, ComponentFactory} from "app/utils"; +import {IClientMessageModalModel} from "../modals/message-modal/message-client-modal/client-message-modal-view-model"; + +export interface IDashboardViewModelScope extends ng.IScope { + + isLoading:boolean; + components:Array<Component>; + folders:FoldersMenu; + roles:IConfigRoles; + user:IUserProperties; + sdcConfig:IAppConfigurtaion; + sdcMenu:IAppMenu; + sharingService:SharingService; + showTutorial:boolean; + isFirstTime:boolean; + version:string; + checkboxesFilter:CheckboxesFilter; + vfcmtType:string; + + + onImportVfc(file:any):void; + onImportVf(file:any):void; + openCreateModal(componentType:ComponentType, importedFile:any):void; + openWhatsNewModal(version:string):void; + openDesignerModal(isResource:boolean, uniqueId:string):void; + setSelectedFolder(folderItem:FoldersItemsMenu):void; + entitiesCount(folderItem:FoldersItemsMenu):number; + getCurrentFolderDistributed():Array<Component>; + changeLifecycleState(entity:any, data:any):void; + goToComponent(component: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', + '$state', + '$stateParams', + 'Sdc.Services.UserResourceService', + 'Sdc.Services.SharingService', + 'Sdc.Services.CacheService', + '$q', + 'ComponentFactory', + 'ChangeLifecycleStateHandler', + 'ModalsHandler', + 'MenuHandler' + ]; + + private components:Array<Component>; + + constructor(private $scope:IDashboardViewModelScope, + private $filter:ng.IFilterService, + private entityService:EntityService, + private $http:ng.IHttpService, + private sdcConfig:IAppConfigurtaion, + private sdcMenu:IAppMenu, + private $state:any, + private $stateParams:any, + private userResourceService:IUserResourceClass, + private sharingService:SharingService, + private cacheService:CacheService, + private $q:ng.IQService, + private ComponentFactory:ComponentFactory, + private ChangeLifecycleStateHandler:ChangeLifecycleStateHandler, + private ModalsHandler:ModalsHandler, + private MenuHandler: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; + this.$scope.vfcmtType = ResourceType.VFCMT; + + // 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 = []; + + 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: ComponentType.RESOURCE.toLowerCase(), + importedFile: file, + resourceType: ResourceType.VF + }); + } else { + let data: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: 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: ComponentType.RESOURCE.toLowerCase(), + importedFile: file, + resourceType: ResourceType.VFC + }); + } else { + let data: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: 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: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<Component>) => { + this.components = components; + this.$scope.components = components; + this.$scope.isLoading = false; + }); + }; + + private getEntitiesByStateDist = (state:string, dist:string):Array<Component> => { + let gObj:Array<Component>; + if (this.components && (state || dist)) { + gObj = this.components.filter(function (obj: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/src/app/view-models/dashboard/dashboard-view.html b/catalog-ui/src/app/view-models/dashboard/dashboard-view.html new file mode 100644 index 0000000000..806bb8138d --- /dev/null +++ b/catalog-ui/src/app/view-models/dashboard/dashboard-view.html @@ -0,0 +1,112 @@ + +<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"> + + <div class='w-sdc-row-flex-items'> + + <!-- 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" data-tests-id="createProductButton" 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> + + <!-- Tile new --> + <div class="sdc-tile-catalog sdc-tile-fix-width" data-ng-repeat="component in components | filter:{resourceType:('!'+vfcmtType)} | entityFilter:checkboxesFilter | filter:search"> + + <div class="sdc-tile-header"> + <div class='sdc-tile-header-type' data-ng-class="{'purple': component.isResource(), 'blue': !component.isResource()}"> + <div data-ng-if="component.isResource()" data-tests-id="asset-type">{{component.getComponentSubType()}}</div> + <div data-ng-if="component.isService()">S</div> + </div> + </div> + <div class='sdc-tile-content' data-tests-id="dashboard-Elements" data-ng-click="goToComponent(component)"> + <div class='sdc-tile-content-icon'> + <div class="{{component.iconSprite}} {{component.icon}}" + data-ng-class="{'sprite-resource-icons': component.isResource(), 'sprite-services-icons': component.isService()}" + data-tests-id="{{component.name}}"></div> + </div> + <div class='sdc-tile-content-info'> + <div class="sdc-tile-content-info-item-name" data-tests-id="{{component.name | resourceName}}" sdc-smart-tooltip>{{component.name | resourceName}}</div> + <div class="sdc-tile-content-info-version-info"> + <div class="sdc-tile-content-info-version-info-text" data-tests-id="{{component.name}}Version">V {{component.version}}</div> + </div> + </div> + </div> + <div class='sdc-tile-footer'> + <div class='sdc-tile-footer-text'>{{component.getStatus(sdcMenu)}}</div> + </div> + + </div> + <!-- Tile new --> + + </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> + + +<ecomp-footer></ecomp-footer> diff --git a/catalog-ui/src/app/view-models/dashboard/dashboard.less b/catalog-ui/src/app/view-models/dashboard/dashboard.less new file mode 100644 index 0000000000..7993390769 --- /dev/null +++ b/catalog-ui/src/app/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: 198px; + margin: 11px; + position: relative; + vertical-align: middle; + width: 202px; +} + +.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/src/app/view-models/dcae-app/dcae-app-view-model.ts b/catalog-ui/src/app/view-models/dcae-app/dcae-app-view-model.ts new file mode 100644 index 0000000000..9be7786f4d --- /dev/null +++ b/catalog-ui/src/app/view-models/dcae-app/dcae-app-view-model.ts @@ -0,0 +1,112 @@ +'use strict'; +import {MenuItemGroup, MenuItem} from "app/utils"; +import {BreadcrumbsPath, BreadcrumbsMenu} from "../onboard-vendor/onboard-vendor-view-model"; +import {CacheService} from "app/services"; +import {IUserProperties} from "app/models"; + +export class TestData { + breadcrumbs:BreadcrumbsPath; +} + +export interface IDcaeAppViewModelScope extends ng.IScope { + testData:TestData; + onTestEvent:Function; + topNavMenuModel:Array<MenuItemGroup>; + topNavRootMenu:MenuItemGroup; + user:IUserProperties; + version:string; +} + +export class DcaeAppViewModel { + static '$inject' = [ + '$scope', + '$q', + 'Sdc.Services.CacheService' + ]; + + private firstControlledTopNavMenu:MenuItemGroup; + + constructor(private $scope:IDcaeAppViewModelScope, + private $q:ng.IQService, + private cacheService:CacheService) { + + this.$scope.testData = { + breadcrumbs: { + selectedKeys: [] + } + }; + + this.$scope.version = this.cacheService.get('version'); + + this.$scope.onTestEvent = (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.testData = { + breadcrumbs: {selectedKeys: selectedKeys} + }; + + return this.$q.when(true); + }; + + handleBreadcrumbsUpdate(breadcrumbsMenus:Array<BreadcrumbsMenu>):void { + let selectedKeys = []; + let topNavMenus = breadcrumbsMenus.map((breadcrumbMenu, breadcrumbIndex) => { + let topNavMenu = new MenuItemGroup(); + topNavMenu.menuItems = breadcrumbMenu.menuItems.map(menuItem => + new 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/src/app/view-models/dcae-app/dcae-app-view.html b/catalog-ui/src/app/view-models/dcae-app/dcae-app-view.html new file mode 100644 index 0000000000..af0d06787a --- /dev/null +++ b/catalog-ui/src/app/view-models/dcae-app/dcae-app-view.html @@ -0,0 +1,17 @@ +<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"> + <!--<div ng-include="'/dcaeapp/index.html'"/>--> + <!--<div id="dcaeAppContainer"></div>--> + <!--<punch-out name="'dcaeApp'" data="vendorData" user="user" on-event="onTestEvent"></punch-out>--> + <div id="main" ui-view="main"></div> + </div> + + <top-nav top-lvl-selected-index="3" search-bind="search.filterTerm" menu-model="topNavMenuModel" version="{{version}}"></top-nav> + +</div> diff --git a/catalog-ui/src/app/view-models/dcae-app/dcae-app.less b/catalog-ui/src/app/view-models/dcae-app/dcae-app.less new file mode 100644 index 0000000000..4a16ca2b5b --- /dev/null +++ b/catalog-ui/src/app/view-models/dcae-app/dcae-app.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('/assets/styles/images/sprites/sprite-global-old.png') no-repeat -53px -2889px; + width: 14px; + height: 14px; + + } + } + + &.CERTIFIED { + .i-sdc-categories-list-item-icon { + background: url('/assets/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('/assets/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('/assets/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('/assets/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/src/app/view-models/forms/artifact-form/artifact-form-view-model.ts b/catalog-ui/src/app/view-models/forms/artifact-form/artifact-form-view-model.ts new file mode 100644 index 0000000000..3e912706e0 --- /dev/null +++ b/catalog-ui/src/app/view-models/forms/artifact-form/artifact-form-view-model.ts @@ -0,0 +1,358 @@ +'use strict'; +import {ArtifactModel, Resource, Component} from "app/models"; +import {ArtifactsUtils, FormState, ValidationUtils, ArtifactType} from "app/utils"; +import {CacheService} from "app/services"; + +export interface IEditArtifactModel { + artifactResource: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:ArtifactModel):void; + getFormTitle():string; + fileUploadRequired():string; + isArtifactOwner():boolean; +} + +export class ArtifactResourceFormViewModel { + + static '$inject' = [ + '$scope', + '$uibModalInstance', + 'artifact', + 'Sdc.Services.CacheService', + 'ValidationPattern', + 'UrlValidationPattern', + 'LabelValidationPattern', + 'IntegerValidationPattern', + 'CommentValidationPattern', + 'ValidationUtils', + '$base64', + '$state', + 'ArtifactsUtils', + '$uibModal', + 'component' + ]; + + private formState:FormState; + private entityId:string; + + constructor(private $scope:IArtifactResourceFormViewModelScope, + private $uibModalInstance:ng.ui.bootstrap.IModalServiceInstance, + private artifact:ArtifactModel, + private cacheService:CacheService, + private ValidationPattern:RegExp, + private UrlValidationPattern:RegExp, + private LabelValidationPattern:RegExp, + private IntegerValidationPattern:RegExp, + private CommentValidationPattern:RegExp, + private ValidationUtils:ValidationUtils, + private $base64:any, + private $state:any, + private artifactsUtils:ArtifactsUtils, + private $uibModal:ng.ui.bootstrap.IModalService, + private component:Component) { + + + this.entityId = this.component.uniqueId; + this.formState = angular.isDefined(artifact.artifactLabel) ? FormState.UPDATE : 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 filterDeploymentArtifactTypeByResourceType = (resourceType:string):any => { + let result = {}; + _.each(this.$scope.validExtensions, function (typeSettings:any, typeName:string) { + if (!typeSettings.validForResourceTypes || typeSettings.validForResourceTypes.indexOf(resourceType) > -1) { + result[typeName] = typeSettings; + } + }); + + return result; + }; + + private initArtifactTypes = ():void => { + + let artifactTypes:any = this.cacheService.get('UIConfiguration'); + + if ('deployment' === this.$scope.artifactType) { + + + if ('HEAT_ENV' == this.artifact.artifactType || this.component.selectedInstance) { + this.$scope.validExtensions = artifactTypes.artifacts.deployment.resourceInstanceDeploymentArtifacts; + } else if (this.component.isResource()) { + this.$scope.validExtensions = artifactTypes.artifacts.deployment.resourceDeploymentArtifacts; + this.$scope.validExtensions = this.filterDeploymentArtifactTypeByResourceType((<Resource>this.component).resourceType); + } else { + this.$scope.validExtensions = 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(ArtifactType.THIRD_PARTY_RESERVED_TYPES, item); + }); + } + + } + if (this.$scope.artifactType === 'informational') { + this.$scope.editArtifactResourceModel.artifactTypes = artifactTypes.artifacts.other.map((element:any)=> { + return element.name; + }); + _.remove(this.$scope.editArtifactResourceModel.artifactTypes, (item:string)=> { + return _.has(ArtifactType.THIRD_PARTY_RESERVED_TYPES, item) || + _.has(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 === FormState.CREATE); + this.$scope.artifactType = this.artifactsUtils.getArtifactTypeByState(this.$state.current.name); + this.$scope.modalInstanceArtifact = this.$uibModalInstance; + + 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' + && this.$scope.editArtifactResourceModel.artifactResource.isHEAT(); + + }; + 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; + } + + if (this.$scope.editArtifactResourceModel.artifactFile.filename) { + this.$scope.editArtifactResourceModel.artifactFile = {}; + this.$scope.forms.editForm.myArtifactFile.$setValidity('required', false); + } + }; + + 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:ArtifactModel):void => { + this.$scope.isLoading = false; + this.$scope.originalArtifactName = ""; + + if (this.$scope.isDeploymentHeat()) { + if (artifactResource.heatParameters) { + this.$scope.openEditEnvParametersModal(artifactResource); + } + } + + if (!doNotCloseModal) { + this.$uibModalInstance.close(); + } 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) { + if (this.component.selectedInstance) { + this.component.uploadInstanceEnvFile(this.$scope.editArtifactResourceModel.artifactResource).then(onSuccess, onFaild); + } else { + this.component.addOrUpdateArtifact(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.$uibModalInstance.close(); + }; + + 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:ArtifactModel):void => { + + let modalOptions:ng.ui.bootstrap.IModalSettings = { + templateUrl: '../env-parameters-form/env-parameters-form.html', + controller: 'Sdc.ViewModels.EnvParametersFormViewModel', + size: 'sdc-md', + backdrop: 'static', + resolve: { + artifact: ():ArtifactModel => { + return artifactResource; + }, + component: ():Component => { + return this.component; + } + } + }; + + let modalInstance:ng.ui.bootstrap.IModalServiceInstance = this.$uibModal.open(modalOptions); + modalInstance + .result + .then(():void => { + }); + }; + + this.$scope.forms = {}; + + this.initFooterButtons(); + + + this.$scope.$watch("forms.editForm.$invalid", (newVal, oldVal) => { + if(this.$scope.forms.editForm) { + 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/src/app/view-models/forms/artifact-form/artifact-form-view.html b/catalog-ui/src/app/view-models/forms/artifact-form/artifact-form-view.html new file mode 100644 index 0000000000..0984c6872d --- /dev/null +++ b/catalog-ui/src/app/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" data-tests-id="sdc-add-artifact"> + + <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="artifactLabel" + 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/src/app/view-models/forms/artifact-form/artifact-form.less b/catalog-ui/src/app/view-models/forms/artifact-form/artifact-form.less new file mode 100644 index 0000000000..1f77958c88 --- /dev/null +++ b/catalog-ui/src/app/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/src/app/view-models/forms/attribute-form/attribute-form-view.html b/catalog-ui/src/app/view-models/forms/attribute-form/attribute-form-view.html new file mode 100644 index 0000000000..90b8f67df4 --- /dev/null +++ b/catalog-ui/src/app/view-models/forms/attribute-form/attribute-form-view.html @@ -0,0 +1,152 @@ +<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="attributeValue.value" + type="text" + name="value" + data-custom-validation="" data-validation-func="validateUniqueKeys" + 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="attributeValue.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': '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/src/app/view-models/forms/attribute-form/attribute-from-view-model.ts b/catalog-ui/src/app/view-models/forms/attribute-form/attribute-from-view-model.ts new file mode 100644 index 0000000000..122cf10ed2 --- /dev/null +++ b/catalog-ui/src/app/view-models/forms/attribute-form/attribute-from-view-model.ts @@ -0,0 +1,241 @@ +'use strict'; +import {AttributeModel, Component} from "app/models"; +import {IMapRegex, ValidationUtils, FormState, PROPERTY_TYPES} from "app/utils"; + +export interface IEditAttributeModel { + attribute:AttributeModel; + types:Array<string>; + simpleTypes:Array<string>; +} + +export class attributeValue {//in order to solve DE226783, we update the value on another obj + value: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:IMapRegex; + mapRegex:IMapRegex; + propertyNameValidationPattern:RegExp; + commentValidationPattern:RegExp; + isLoading:boolean; + validationPattern:RegExp; + attributeValue:attributeValue; + + 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', + '$uibModalInstance', + 'attribute', + 'ValidationUtils', + 'CommentValidationPattern', + 'PropertyNameValidationPattern', + 'component' + ]; + + private formState:FormState; + + + constructor(private $scope:IAttributeFormViewModelScope, + private $uibModalInstance:ng.ui.bootstrap.IModalServiceInstance, + private attribute:AttributeModel, + private ValidationUtils:ValidationUtils, + private CommentValidationPattern:RegExp, + private PropertyNameValidationPattern:RegExp, + private component:Component) { + this.formState = angular.isDefined(attribute.name) ? FormState.UPDATE : FormState.CREATE; + this.initScope(); + } + + private initResource = ():void => { + this.$scope.editAttributeModel.attribute = new 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.$uibModalInstance; + this.$scope.listRegex = this.ValidationUtils.getPropertyListPatterns(); + this.$scope.mapRegex = this.ValidationUtils.getPropertyMapPatterns(); + + this.$scope.isNew = (this.formState === FormState.CREATE); + this.$scope.isLoading = false; + this.$scope.attributeValue = new attributeValue(); + + this.initEditAttributeModel(); + this.setValidationPattern(); + + //scope methods + this.$scope.save = ():void => { + if (!this.$scope.forms.editForm.$invalid) { + let attribute: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.$uibModalInstance.close(); + return; + } + this.$scope.isLoading = true; + let onAttributeFaild = (response):void => { + console.info('onFaild', response); + this.$scope.isLoading = false; + }; + + let onAttributeSuccess = (attributeFromBE:AttributeModel):void => { + console.info('onAttributeResourceSuccess : ', attributeFromBE); + this.$scope.isLoading = false; + this.$uibModalInstance.close(); + }; + + //in case we have uniqueId we call update method + if (this.$scope.isAttributeValueOwner()) { + attribute.value = this.$scope.attributeValue.value; + this.component.updateInstanceAttribute(attribute).then(onAttributeSuccess, onAttributeFaild); + } else { + attribute.defaultValue = this.$scope.attributeValue.value; + this.component.addOrUpdateAttribute(attribute).then(onAttributeSuccess, onAttributeFaild); + } + } + }; + + this.$scope.close = ():void => { + this.$uibModalInstance.close(); + }; + + this.$scope.validateName = ():void => { + let existsAttr:AttributeModel = _.find(this.component.attributes, (attribute: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 === PROPERTY_TYPES.STRING) { + result += "_STRING"; + } else if (this.$scope.editAttributeModel.attribute.schema.property.type === 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; + }); + + this.$scope.attributeValue.value = this.$scope.isAttributeValueOwner() ? this.$scope.editAttributeModel.attribute.value : this.$scope.editAttributeModel.attribute.defaultValue; + }; + + + 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/src/app/view-models/forms/env-parameters-form/env-parameters-form.html b/catalog-ui/src/app/view-models/forms/env-parameters-form/env-parameters-form.html new file mode 100644 index 0000000000..ae13844532 --- /dev/null +++ b/catalog-ui/src/app/view-models/forms/env-parameters-form/env-parameters-form.html @@ -0,0 +1,92 @@ +<sdc-modal modal="envParametersModal" type="classic" class="sdc-env-form-container" buttons="buttons" header="{{artifactResource.artifactDisplayName}}" show-close-button="true"> + <div class="w-sdc-env-form-container"> + <div class="w-sdc-env-search pull-left"> + <input type="text" class="w-sdc-env-search-input" placeholder="Search" data-ng-model="searchText" data-tests-id="search-env-param-name"/> + <div class="search-icon-container"> + <span class="w-sdc-search-icon env-search-icon magnification-white"></span> + </div> + </div> + <div class="table-container-flex"> + <div class="table"> + <div class="head flex-container"> + <div class="table-header head-row flex-item" ng-repeat="header in tableHeadersList track by $index"> + <info-tooltip class="header-info" data-ng-if="header.info" class="info-button" info-message-translate="{{header.info}}" direction="left"></info-tooltip> + {{header.title}} + </div> + </div> + <div class="body"> + <perfect-scrollbar suppress-scroll-x="true" scroll-y-margin-offset="0" include-padding="true" class="scrollbar-container"> + <ng-form name="forms.editForm" class="w-sdc-form"> + <div data-ng-repeat="parameter in heatParameters| filter:{filterTerm:searchText} track by $index " + class="flex-container data-row" + data-ng-init="parameter.filterTerm=parameter.name + ' ' + parameter.currentValue + ' ' + parameter.defaultValue + ' ' +parameter.description"> + <div class="table-col-general flex-item" data-tests-id="heatParameterName_{{parameter.name}}"> + {{parameter.name}} + <span class="sprite-new show-desc hand" + uib-popover-template="templatePopover" + popover-class="parameter-description-popover top" + popover-title="Parameter Description" + popover-placement="top-left" + popover-is-open="selectedParameter.name == parameter.name" + popover-trigger="'none'" + popover-append-to-body="true" + data-ng-click="openDescPopover(parameter)"></span> + </div> + + <div class="table-col-general flex-item text"> + <span data-tests-id="default-value-of-{{parameter.name}}" tooltips tooltip-content="{{parameter.defaultValue}}">{{parameter.defaultValue}}</span> + </div> + + <!--<div class="table-col-general flex-item">--> + <!--<input type="text" value="{{parameter.currentValue}}"/>--> + <!--</div>--> + + <div class="table-col-general flex-item left-column-container"> + + <div class="i-sdc-form-item" data-ng-class="{error:(forms.editForm[parameter.name].$dirty && forms.editForm[parameter.name].$invalid), required: (parameter.defaultValue)}"> + <span class="required-symbol">*</span> + <div class="input-parameter"> + <input class="i-sdc-form-input" data-ng-class="{error: (forms.editForm[parameter.name].$invalid)}" + data-ng-model-options="{ debounce: 200 }" + data-ng-model="parameter.currentValue" + value="{{parameter.currentValue}}" + type="text" + name="{{parameter.name}}" + data-ng-pattern="getValidationPattern(parameter.type, 'heat')" + data-ng-required="parameter.defaultValue" + data-ng-change="'json'==parameter.type && forms.editForm[parameter.name].$setValidity('pattern', validateJson(parameter.currentValue))" + data-ng-blur="(forms.editForm[parameter.name].$error.required && (parameter.currentValue=parameter.defaultValue))" + data-tests-id="value-field-of-{{parameter.name}}"/> + + <div class="action-button"> + <div class="sprite-new revert-param" data-ng-if="parameter.defaultValue" data-ng-click="parameter.currentValue = parameter.defaultValue" + data-tests-id="revert-{{parameter.name}}"> + </div> + <div class="sprite-new delete-param" + data-ng-if="!parameter.defaultValue" + data-ng-disabled="!parameter.currentValue" + data-ng-class="{disabled:!parameter.currentValue}" + data-ng-click="parameter.currentValue = ''" + data-tests-id="delete-{{parameter.name}}"> + </div> + </div> + </div> + <div class="input-error" data-ng-show="forms.editForm[parameter.name].$invalid"> + <span ng-show="forms.editForm[parameter.name].$error.required" translate="VALIDATION_ERROR_REQUIRED" translate-values="{'field': 'Value'}"></span> + <span ng-show="forms.editForm[parameter.name].$error.pattern && parameter.type==='string'" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span> + <span ng-show="forms.editForm[parameter.name].$error.pattern && !(parameter.type==='string')" translate="VALIDATION_ERROR_TYPE" translate-values="{'type': '{{parameter.type}}'}"></span> + </div> + </div> + + </div> + + </div> + </ng-form> + + </perfect-scrollbar> + </div> + </div> + </div> + </div> + </div> +</sdc-modal> diff --git a/catalog-ui/src/app/view-models/forms/env-parameters-form/env-parameters-form.less b/catalog-ui/src/app/view-models/forms/env-parameters-form/env-parameters-form.less new file mode 100644 index 0000000000..a25a2c5f62 --- /dev/null +++ b/catalog-ui/src/app/view-models/forms/env-parameters-form/env-parameters-form.less @@ -0,0 +1,178 @@ + +.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 { + height: 650px; + + .w-sdc-env-search { + padding: 10px 20px 20px 0; + white-space: nowrap; + position: relative; + width: 60%; + height: 64px; + + .env-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 + } + + .w-sdc-env-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; + } + } + + .table-container-flex { + height: 570px; + + .table { + height: 100%; + .flex-item:nth-child(1) { + flex-grow: 20; + .show-desc{ + float: right; + top: 10px; + position: relative; + } + } + + .flex-item:nth-child(2) { + flex-grow: 10; + } + + .flex-item:nth-child(3) { + flex-grow: 10; + } + .scrollbar-container{ + max-height: 527px; + } + .left-column-container{ + .required-symbol { + .m_14_m; + color: #f33; + display: none; + position: relative; + left: -4px; + top: 3px; + } + + .i-sdc-form-item{ + border-right: none; + margin: 0px; + + .input-parameter{ + border: none; + height: 30px; + width: 254px; + float: right; + input{ + .m_13_m; + width: 100%; + display: inline-flex; + padding-right: 33px; + } + .action-button{ + border-left: solid 1px @main_color_o; + position: relative; + height: 20px; + width: 25px; + top: -25px; + left: 228px; + padding-left: 6px; + background-color: @main_color_p; + div:not(.disable){ + .hand; + } + } + } + + &.required{ + .required-symbol { + display: inline-flex; + } + .input-parameter { + width: 250px; + } + .action-button{ + left: 224px; + } + } + } + + + + } + } + + .text{ + overflow: hidden; + text-overflow: ellipsis; + display: inline-block; + white-space: nowrap; + } + } + + + .parameter-description{ + background-color: @func_color_r; + border-left: 4px solid @main_color_a; + padding: 10px 30px; + } +} + +.header-info{ + float: right; +} + +.parameter-description-popover{ + z-index: 1100; + min-width: 210px; + .arrow{ + left: 20px !important; + border-width: 7px; + bottom: -8px !important; + } + .popover-content{ + .f-type._13_m;; + } +} diff --git a/catalog-ui/src/app/view-models/forms/env-parameters-form/env-parameters-form.ts b/catalog-ui/src/app/view-models/forms/env-parameters-form/env-parameters-form.ts new file mode 100644 index 0000000000..476af4ada9 --- /dev/null +++ b/catalog-ui/src/app/view-models/forms/env-parameters-form/env-parameters-form.ts @@ -0,0 +1,153 @@ +'use strict'; +import {ValidationUtils} from "app/utils"; +import {ArtifactModel, HeatParameterModel, Component} from "app/models"; + +export interface IEnvParametersFormViewModelScope extends ng.IScope { + isLoading:boolean; + type:string; + heatParameters:Array<HeatParameterModel>; + forms:any; + artifactResource:ArtifactModel; + buttons:Array<any>; + envParametersModal:ng.ui.bootstrap.IModalServiceInstance; + tableHeadersList:Array<any>; + selectedParameter:HeatParameterModel; + templatePopover:string; + + getValidationPattern(type:string):RegExp; + isInstance():boolean; + validateJson(json:string):boolean; + close():void; + save():void; + openDescPopover(selectedParam:HeatParameterModel):void; + closeDescriptionPopover():void; +} + +export class EnvParametersFormViewModel { + + static '$inject' = [ + '$scope', + '$templateCache', + '$state', + '$uibModalInstance', + 'artifact', + 'ValidationUtils', + 'component' + ]; + + constructor(private $scope:IEnvParametersFormViewModelScope, + private $templateCache:ng.ITemplateCacheService, + private $state:any, + private $uibModalInstance:ng.ui.bootstrap.IModalServiceInstance, + private artifact:ArtifactModel, + private ValidationUtils:ValidationUtils, + private component:Component) { + + + this.initScope(); + } + + private updateInstanceHeat = ():void => { + let success = (responseArtifact:ArtifactModel):void => { + this.$scope.isLoading = false; + this.$uibModalInstance.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.forms = {}; + this.$scope.envParametersModal = this.$uibModalInstance; + this.$scope.artifactResource = this.artifact; + this.$scope.heatParameters = angular.copy(this.artifact.heatParameters); + + this.$scope.tableHeadersList = [ + {title: "Parameter", property: "name"}, + {title: "Default Value", property: "defaultValue", info: "DEFAULT_VALUE_INFO"}, + {title: "Current Value", property: "currentValue", info: "CURRENT_VALUE_INFO"} + ]; + + this.$templateCache.put("env-parametr-description-popover.html", require('app/view-models/forms/env-parameters-form/env-parametr-description-popover.html')); + this.$scope.templatePopover = "env-parametr-description-popover.html"; + + 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.buttons[0].disabled = true;//prevent double click (DE246266) + 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:ArtifactModel):void => { + this.$scope.isLoading = false; + this.$uibModalInstance.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.close = ():void => { + //this.artifact.heatParameters.forEach((parameter:any):void => { + // if (!parameter.currentValue && parameter.defaultValue) { + // parameter.currentValue = parameter.defaultValue; + // } + //}); + this.$uibModalInstance.dismiss(); + }; + + this.$scope.openDescPopover = (selectedParam:HeatParameterModel):void => { + this.$scope.selectedParameter = selectedParam; + }; + + this.$scope.closeDescriptionPopover = ():void => { + this.$scope.selectedParameter = null; + }; + + this.$scope.buttons = [ + {'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.buttons[0].disabled = this.$scope.forms.editForm.$invalid; + }); + + }; +} diff --git a/catalog-ui/src/app/view-models/forms/env-parameters-form/env-parametr-description-popover.html b/catalog-ui/src/app/view-models/forms/env-parameters-form/env-parametr-description-popover.html new file mode 100644 index 0000000000..ed127c6bfb --- /dev/null +++ b/catalog-ui/src/app/view-models/forms/env-parameters-form/env-parametr-description-popover.html @@ -0,0 +1,4 @@ +<div> + <span data-tests-id='popover-x-button' data-ng-click='closeDescriptionPopover()' class='tlv-sprite tlv-x-btn close-popover-btn'></span> + {{selectedParameter.description}} +</div> diff --git a/catalog-ui/src/app/view-models/forms/input-form/input-form-view-modal.ts b/catalog-ui/src/app/view-models/forms/input-form/input-form-view-modal.ts new file mode 100644 index 0000000000..e87e5c6c7d --- /dev/null +++ b/catalog-ui/src/app/view-models/forms/input-form/input-form-view-modal.ts @@ -0,0 +1,126 @@ +'use strict'; +import {FormState, PROPERTY_TYPES, ValidationUtils, PROPERTY_VALUE_CONSTRAINTS} from "app/utils"; +import {InputModel} from "app/models"; + +export interface IInputEditModel { + editInput:InputModel; +} + +export interface IInputFormViewModelScope extends ng.IScope { + forms:any; + editForm:ng.IFormController; + footerButtons:Array<any>; + isService:boolean; + modalInstanceInput:ng.ui.bootstrap.IModalServiceInstance; + isLoading:boolean; + inputEditModel:IInputEditModel; + myValue:any; + maxLength:number; + + save():void; + close():void; + validateIntRange(value:string):boolean; + validateJson(json:string):boolean; + getValidationPattern(type:string):RegExp; + showSchema():boolean; +} + +export class InputFormViewModel { + + static '$inject' = [ + '$scope', + '$uibModalInstance', + 'ValidationUtils', + 'input' + ]; + + private formState:FormState; + + + constructor(private $scope:IInputFormViewModelScope, + private $uibModalInstance:ng.ui.bootstrap.IModalServiceInstance, + private ValidationUtils:ValidationUtils, + private input:InputModel) { + this.initScope(); + this.initMyValue(); + } + + private initMyValue = ():void => { + switch (this.$scope.inputEditModel.editInput.type) { + case PROPERTY_TYPES.MAP: + this.$scope.myValue = this.$scope.inputEditModel.editInput.defaultValue ? JSON.parse(this.$scope.inputEditModel.editInput.defaultValue) : {'': null}; + break; + case PROPERTY_TYPES.LIST: + this.$scope.myValue = this.$scope.inputEditModel.editInput.defaultValue ? JSON.parse(this.$scope.inputEditModel.editInput.defaultValue) : []; + break; + } + }; + + private initDefaultValueMaxLength = ():void => { + switch (this.$scope.inputEditModel.editInput.type) { + case PROPERTY_TYPES.MAP: + case PROPERTY_TYPES.LIST: + this.$scope.maxLength = this.$scope.inputEditModel.editInput.schema.property.type == PROPERTY_TYPES.JSON ? + PROPERTY_VALUE_CONSTRAINTS.JSON_MAX_LENGTH : + PROPERTY_VALUE_CONSTRAINTS.MAX_LENGTH; + break; + case PROPERTY_TYPES.JSON: + this.$scope.maxLength = PROPERTY_VALUE_CONSTRAINTS.JSON_MAX_LENGTH; + break; + default: + this.$scope.maxLength = PROPERTY_VALUE_CONSTRAINTS.MAX_LENGTH; + } + }; + + private initScope = ():void => { + this.$scope.forms = {}; + this.$scope.modalInstanceInput = this.$uibModalInstance; + this.$scope.inputEditModel = { + editInput: null + }; + this.$scope.inputEditModel.editInput = this.input; + this.initDefaultValueMaxLength(); + + //scope methods + this.$scope.save = ():void => { + if (this.$scope.showSchema()) { + this.$scope.inputEditModel.editInput.defaultValue = JSON.stringify(this.$scope.myValue); + } + }; + + this.$scope.close = ():void => { + this.$uibModalInstance.close(); + }; + + this.$scope.validateIntRange = (value:string):boolean => { + return !value || this.ValidationUtils.validateIntRange(value); + }; + + this.$scope.validateJson = (json:string):boolean => { + if (!json) { + return true; + } + return this.ValidationUtils.validateJson(json); + }; + + this.$scope.showSchema = ():boolean => { + return ['list', 'map'].indexOf(this.$scope.inputEditModel.editInput.type) > -1; + }; + + this.$scope.getValidationPattern = (type:string):RegExp => { + return this.ValidationUtils.getValidationPattern(type); + }; + + // 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; + }); + + }; +} + diff --git a/catalog-ui/src/app/view-models/forms/input-form/input-form-view.html b/catalog-ui/src/app/view-models/forms/input-form/input-form-view.html new file mode 100644 index 0000000000..1bf6dc4ca9 --- /dev/null +++ b/catalog-ui/src/app/view-models/forms/input-form/input-form-view.html @@ -0,0 +1,125 @@ +<sdc-modal modal="modalInstanceInput" type="classic" class="sdc-edit-input-container" buttons="footerButtons" header="Update Input" show-close-button="true"> + + <div class="sdc-edit-input-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"> + <label class="i-sdc-form-label">Name</label> + <input class="i-sdc-form-input" + data-tests-id="inputName" + data-ng-maxlength="50" + data-ng-disabled="true" + maxlength="50" + data-ng-model="inputEditModel.editInput.name" + type="text" + name="inputName" + autofocus /> + </div> + + <!-- Description --> + <div class="i-sdc-form-item"> + <label class="i-sdc-form-label">Description</label> + <textarea class="i-sdc-form-textarea" + data-ng-disabled="true" + name="description" + data-ng-model="inputEditModel.editInput.description" + data-tests-id="description"></textarea> + </div> + + + </div> + + <div class="w-sdc-form-column"> + <!-- Type --> + <div class="i-sdc-form-item"> + <label class="i-sdc-form-label">Type</label> + <input class="i-sdc-form-input" + data-tests-id="type" + data-ng-disabled="true" + data-ng-model="inputEditModel.editInput.type" + type="text" + name="type"/> + </div> + <!-- schema --> + <div class="i-sdc-form-item" + data-ng-if="showSchema()"> + <label class="i-sdc-form-label">Entry Schema</label> + <input class="i-sdc-form-input" + data-tests-id="schema" + data-ng-disabled="true" + data-ng-model="inputEditModel.editInput.schema.property.type" + type="text" + name="schema"/> + </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> + <div data-ng-switch="inputEditModel.editInput.type"> + <div ng-switch-when="map"> + <type-map value-obj-ref="myValue" + schema-property="inputEditModel.editInput.schema.property" + parent-form-obj="forms.editForm" + fields-prefix-name="'input-value-'" + read-only="true" + default-value="" + types="[]" + max-length="maxLength"></type-map> + </div> + <div ng-switch-when="list"> + <type-list value-obj-ref="myValue" + schema-property="inputEditModel.editInput.schema.property" + parent-form-obj="forms.editForm" + fields-prefix-name="'input-value-'" + read-only="true" + default-value="" + types="[]" + 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="inputEditModel.editInput.type != 'boolean'" + data-ng-maxlength="maxLength" + data-ng-disabled="true" + maxlength="{{maxLength}}" + data-ng-model="inputEditModel.editInput.defaultValue" + type="text" + name="value" + data-ng-pattern="getValidationPattern(input.type)" + data-ng-model-options="{ debounce: 200 }" + data-ng-change="('json'==inputEditModel.editInput.type && forms.editForm.value.$setValidity('pattern', validateJson(inputEditModel.editInput.defaultValue))) + ||(!forms.editForm.value.$error.pattern && ('integer'==inputEditModel.editInput.type && forms.editForm.value.$setValidity('pattern', validateIntRange(inputEditModel.editInput.defaultValue)) || onValueChange()))" + autofocus /> + <select class="i-sdc-form-select" + data-tests-id="booleantype" + ng-if="inputEditModel.editInput.type == 'boolean'" + data-ng-disabled="true" + name="value" + data-ng-model="inputEditModel.editInput.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.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '{{maxLength}}' }"></span> + <span ng-show="forms.editForm.value.$error.pattern" translate="PROPERTY_EDIT_PATTERN"></span> + </div> + </div> + </div> + </div> + </div> + + </div> + + </div> + + </form> + </div> + +</sdc-modal> diff --git a/catalog-ui/src/app/view-models/forms/property-forms/base-property-form/property-form-base-model.ts b/catalog-ui/src/app/view-models/forms/property-forms/base-property-form/property-form-base-model.ts new file mode 100644 index 0000000000..1ba5a90bb4 --- /dev/null +++ b/catalog-ui/src/app/view-models/forms/property-forms/base-property-form/property-form-base-model.ts @@ -0,0 +1,204 @@ +/** + * Created by obarda on 1/19/2017. + */ +'use strict'; +import {DataTypesService} from "app/services/data-types-service"; +import {PropertyModel, DataTypesMap, Component} from "app/models"; +import {ValidationUtils, PROPERTY_DATA} from "app/utils"; + +export interface IPropertyFormBaseViewScope extends ng.IScope { + + forms:any; + editForm:ng.IFormController; + + property:PropertyModel; + types:Array<string>; + nonPrimitiveTypes:Array<string>; + simpleTypes:Array<string>; + + footerButtons:Array<any>; + modalPropertyFormBase:ng.ui.bootstrap.IModalServiceInstance; + currentPropertyIndex:number; + isLastProperty:boolean; + innerViewSrcUrl:string; + + //Disabling filed - each child controller can change this when needed + isNew:boolean; + isTypeSelectorDisable:boolean; + isDeleteDisable:boolean; + isNameDisable:boolean; + isDescriptionDisable:boolean; + isPropertyValueDisable:boolean; + isArrowsDisabled:boolean; + + //Validation pattern + validationPattern:RegExp; + propertyNameValidationPattern:RegExp; + commentValidationPattern:RegExp; + numberValidationPattern:RegExp; + + dataTypes:DataTypesMap; + + isLoading:boolean; + + save():void; + close():void; + getNext():void; + getPrev():void; + getValidationPattern(type:string):RegExp; +} + +export abstract class PropertyFormBaseView { + + + constructor(protected $scope:IPropertyFormBaseViewScope, + protected $uibModalInstance:ng.ui.bootstrap.IModalServiceInstance, + protected $injector:ng.auto.IInjectorService, + protected originalProperty:PropertyModel, + protected component:Component, + protected filteredProperties:Array<PropertyModel>, + protected DataTypesService:DataTypesService) { + + this.initScope(); + + } + + protected validationUtils:ValidationUtils; + + protected isPropertyValueOwner = ():boolean => { + return this.component.isService() || !!this.component.selectedInstance; + }; + + private isDisable = ():boolean => { + return this.isPropertyValueOwner() || this.$scope.property.readonly; + }; + + + //This is the difault state, Childs screens can change if needed + protected initButtonsState = ():void => { + let isDisable = this.isDisable(); + + this.$scope.isArrowsDisabled = false; + this.$scope.isDeleteDisable = isDisable; + this.$scope.isDescriptionDisable = isDisable; + this.$scope.isNameDisable = isDisable; + this.$scope.isTypeSelectorDisable = isDisable; + this.$scope.isPropertyValueDisable = this.$scope.property.readonly && !this.isPropertyValueOwner(); + }; + + protected initValidations = ():void => { + + this.$scope.validationPattern = this.$injector.get('ValidationPattern'); + this.$scope.propertyNameValidationPattern = this.$injector.get('PropertyNameValidationPattern'); + this.$scope.commentValidationPattern = this.$injector.get('CommentValidationPattern'); + this.$scope.numberValidationPattern = this.$injector.get('NumberValidationPattern'); + this.validationUtils = this.$injector.get('ValidationUtils'); + }; + + //Functions implemented on child's scope if needed + abstract save(doNotCloseModal?:boolean):ng.IPromise<boolean>; + + protected onPropertyChange():void { + }; + + private updatePropertyByIndex = (index:number):void => { + this.$scope.property = new PropertyModel(this.filteredProperties[index]); + this.$scope.isLastProperty = this.$scope.currentPropertyIndex == (this.filteredProperties.length - 1); + this.onPropertyChange(); + }; + + private initScope = ():void => { + + this.$scope.forms = {}; + this.$scope.isLoading = false; + this.$scope.property = new PropertyModel(this.originalProperty); //we create a new Object so if user press cance we won't update the property + this.$scope.types = PROPERTY_DATA.TYPES; //All types - simple type + map + list + this.$scope.simpleTypes = PROPERTY_DATA.SIMPLE_TYPES; //All simple types + this.$scope.dataTypes = this.DataTypesService.getAllDataTypes(); //Get all data types in service + this.$scope.modalPropertyFormBase = this.$uibModalInstance; + this.$scope.isNew = !angular.isDefined(this.$scope.property.name); + + this.initValidations(); + this.initButtonsState(); + this.filteredProperties = _.sortBy(this.filteredProperties, 'name'); + this.$scope.currentPropertyIndex = _.findIndex(this.filteredProperties, propety => propety.uniqueId == this.$scope.property.uniqueId); + this.$scope.isLastProperty = this.$scope.currentPropertyIndex == (this.filteredProperties.length - 1); + + this.$scope.nonPrimitiveTypes = _.filter(Object.keys(this.$scope.dataTypes), (type:string)=> { + return this.$scope.types.indexOf(type) == -1; + }); + + this.$scope.close = ():void => { + this.$uibModalInstance.close(); + }; + + this.$scope.save = ():void => { + + let onSuccess = ():void => { + this.$scope.isLoading = false; + }; + let onFailed = ():void => { + this.$scope.isLoading = false; + }; + + this.$scope.isLoading = true; + this.save(true).then(onSuccess, onFailed); // Child controller implement save logic + }; + + // 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.getPrev = ():void=> { + + let onSuccess = ():void => { + this.$scope.isLoading = false; + this.updatePropertyByIndex(--this.$scope.currentPropertyIndex); + }; + let onFailed = ():void => { + this.$scope.isLoading = false; + }; + + if (!this.$scope.property.readonly) { + this.$scope.isLoading = true; + this.save(false).then(onSuccess, onFailed); + + } else { + this.updatePropertyByIndex(--this.$scope.currentPropertyIndex); + } + + }; + + this.$scope.getNext = ():void=> { + + let onSuccess = ():void => { + this.$scope.isLoading = false; + this.updatePropertyByIndex(++this.$scope.currentPropertyIndex); + }; + let onFailed = ():void => { + this.$scope.isLoading = false; + }; + + if (!this.$scope.property.readonly) { + this.$scope.isLoading = true; + this.save(false).then(onSuccess, onFailed); + } else { + this.updatePropertyByIndex(++this.$scope.currentPropertyIndex); + } + + }; + + + this.$scope.$watch("forms.editForm.$invalid", (newVal, oldVal) => { + this.$scope.footerButtons[0].disabled = this.$scope.forms.editForm.$invalid; + }); + + + this.$scope.getValidationPattern = (type:string):RegExp => { + return this.validationUtils.getValidationPattern(type); + }; + } +} diff --git a/catalog-ui/src/app/view-models/forms/property-forms/base-property-form/property-form-base-view.html b/catalog-ui/src/app/view-models/forms/property-forms/base-property-form/property-form-base-view.html new file mode 100644 index 0000000000..7cb05bf4ca --- /dev/null +++ b/catalog-ui/src/app/view-models/forms/property-forms/base-property-form/property-form-base-view.html @@ -0,0 +1,132 @@ +<sdc-modal modal="modalPropertyFormBase" 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"> + <loader data-display="isLoading" relative="false" size="medium"></loader> + <div class="sdc-modal-top-bar" data-ng-if="!isNew"> + <div class="sdc-modal-top-bar-buttons"> + <span ng-click="delete(property)" data-ng-class="{'disabled' : isDeleteDisable}" 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 || forms.editForm.$invalid || isArrowsDisabled}" 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 || forms.editForm.$invalid || isArrowsDisabled}" 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 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="isNameDisable" + maxlength="50" + data-ng-model="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': '50' }"></span> + <span ng-show="forms.editForm.propertyName.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span> + </div> + </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="isTypeSelectorDisable" + name="type" + data-ng-change="onTypeChange()" + data-ng-model="property.type"> + <option value="">Choose Type</option> + <option data-ng-repeat="type in 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() || property.readonly" + name="schemaType" + data-ng-change="onSchemaTypeChange()" + data-ng-model="property.schema.property.type"> + <option value="">Choose Schema Type</option> + <option data-ng-repeat="type in 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="isDescriptionDisable" + maxlength="256" + data-ng-pattern="commentValidationPattern" + name="description" + data-ng-model="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 - insert in dynamic template url --> + <ng-include src="innerViewSrcUrl"></ng-include> + <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/src/app/view-models/forms/property-forms/base-property-form/property-form-base.less b/catalog-ui/src/app/view-models/forms/property-forms/base-property-form/property-form-base.less new file mode 100644 index 0000000000..15e30af4ee --- /dev/null +++ b/catalog-ui/src/app/view-models/forms/property-forms/base-property-form/property-form-base.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/src/app/view-models/forms/property-forms/component-property-form/property-form-view-model.ts b/catalog-ui/src/app/view-models/forms/property-forms/component-property-form/property-form-view-model.ts new file mode 100644 index 0000000000..b3557b055f --- /dev/null +++ b/catalog-ui/src/app/view-models/forms/property-forms/component-property-form/property-form-view-model.ts @@ -0,0 +1,322 @@ +'use strict'; +import { + PROPERTY_TYPES, ModalsHandler, ValidationUtils, PROPERTY_VALUE_CONSTRAINTS, FormState, PROPERTY_DATA} from "app/utils"; +import {DataTypesService} from "app/services"; +import {PropertyModel, DataTypesMap, Component} from "app/models"; + +export interface IEditPropertyModel { + property:PropertyModel; + types:Array<string>; + simpleTypes: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:DataTypesMap; + isTypeDataType:boolean; + maxLength:number; + isPropertyValueOwner:boolean; + + validateJson(json:string):boolean; + save(doNotCloseModal?:boolean):void; + getValidationPattern(type:string):RegExp; + validateIntRange(value:string):boolean; + close():void; + onValueChange():void; + onSchemaTypeChange():void; + onTypeChange(resetSchema:boolean):void; + showSchema():boolean; + delete(property:PropertyModel):void; + getPrev():void; + getNext():void; + isSimpleType(typeName:string):boolean; + getDefaultValue():any; +} + +export class PropertyFormViewModel { + + static '$inject' = [ + '$scope', + 'Sdc.Services.DataTypesService', + '$uibModalInstance', + 'property', + 'ValidationPattern', + 'PropertyNameValidationPattern', + 'CommentValidationPattern', + 'ValidationUtils', + 'component', + '$filter', + 'ModalsHandler', + 'filteredProperties', + '$timeout', + 'isPropertyValueOwner' + ]; + + private formState:FormState; + + constructor(private $scope:IPropertyFormViewModelScope, + private DataTypesService:DataTypesService, + private $uibModalInstance:ng.ui.bootstrap.IModalServiceInstance, + private property:PropertyModel, + private ValidationPattern:RegExp, + private PropertyNameValidationPattern:RegExp, + private CommentValidationPattern:RegExp, + private ValidationUtils:ValidationUtils, + private component:Component, + private $filter:ng.IFilterService, + private ModalsHandler:ModalsHandler, + private filteredProperties:Array<PropertyModel>, + private $timeout:ng.ITimeoutService, + private isPropertyValueOwner:boolean) { + + this.formState = angular.isDefined(property.name) ? FormState.UPDATE : FormState.CREATE; + this.initScope(); + } + + private initResource = ():void => { + this.$scope.editPropertyModel.property = new PropertyModel(this.property); + this.$scope.editPropertyModel.property.type = this.property.type ? this.property.type : null; + this.setMaxLength(); + this.initAddOnLabels(); + }; + + //init property add-ons labels that show up at the left side of the input. + private initAddOnLabels = () => { + if (this.$scope.editPropertyModel.property.name == 'network_role' && this.$scope.isService) { + //the server sends back the normalized name. Remove it (to prevent interference with validation) and set the addon label to the component name directly. + //Note: this cant be done in properties.ts because we dont have access to the component + if (this.$scope.editPropertyModel.property.value) { + let splitProp = this.$scope.editPropertyModel.property.value.split(new RegExp(this.component.normalizedName + '.', "gi")); + this.$scope.editPropertyModel.property.value = splitProp.pop(); + } + this.$scope.editPropertyModel.property.addOn = this.component.name; + } + } + + private initEditPropertyModel = ():void => { + this.$scope.editPropertyModel = { + property: null, + types: PROPERTY_DATA.TYPES, + simpleTypes: PROPERTY_DATA.SIMPLE_TYPES + }; + + this.initResource(); + }; + + private initForNotSimpleType = ():void => { + let property = this.$scope.editPropertyModel.property; + this.$scope.isTypeDataType = this.DataTypesService.isDataTypeForPropertyType(this.$scope.editPropertyModel.property); + if (property.type && this.$scope.editPropertyModel.simpleTypes.indexOf(property.type) == -1) { + if (!(property.value || property.defaultValue)) { + switch (property.type) { + case PROPERTY_TYPES.MAP: + this.$scope.myValue = {'': null}; + break; + case 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 PROPERTY_TYPES.MAP: + case PROPERTY_TYPES.LIST: + this.$scope.maxLength = this.$scope.editPropertyModel.property.schema.property.type == PROPERTY_TYPES.JSON ? + PROPERTY_VALUE_CONSTRAINTS.JSON_MAX_LENGTH : + PROPERTY_VALUE_CONSTRAINTS.MAX_LENGTH; + break; + case PROPERTY_TYPES.JSON: + this.$scope.maxLength = PROPERTY_VALUE_CONSTRAINTS.JSON_MAX_LENGTH; + break; + default: + this.$scope.maxLength =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 === FormState.CREATE); + this.$scope.isService = this.component.isService(); + this.$scope.modalInstanceProperty = this.$uibModalInstance; + this.$scope.currentPropertyIndex = _.findIndex(this.filteredProperties, i=> i.name == this.property.name); + this.$scope.isLastProperty = this.$scope.currentPropertyIndex == (this.filteredProperties.length - 1); + this.$scope.dataTypes = this.DataTypesService.getAllDataTypes(); + this.$scope.isPropertyValueOwner = this.isPropertyValueOwner; + this.initEditPropertyModel(); + + this.$scope.nonPrimitiveTypes = _.filter(Object.keys(this.$scope.dataTypes), (type:string)=> { + return this.$scope.editPropertyModel.types.indexOf(type) == -1; + }); + this.initForNotSimpleType(); + + + this.$scope.validateJson = (json:string):boolean => { + if (!json) { + return true; + } + return this.ValidationUtils.validateJson(json); + }; + + + //scope methods + this.$scope.save = (doNotCloseModal?:boolean):void => { + let property:PropertyModel = this.$scope.editPropertyModel.property; + this.$scope.editPropertyModel.property.description = this.ValidationUtils.stripAndSanitize(this.$scope.editPropertyModel.property.description); + //if read only - or no changes made - just closes the modal + //need to check for property.value changes manually to detect if map properties deleted + if ((this.$scope.editPropertyModel.property.readonly && !this.$scope.isPropertyValueOwner) + || (!this.$scope.forms.editForm.$dirty && angular.equals(JSON.stringify(this.$scope.myValue), this.$scope.editPropertyModel.property.value))) { + this.$uibModalInstance.close(); + return; + } + + this.$scope.isLoading = true; + + let onPropertyFaild = (response):void => { + console.info('onFaild', response); + this.$scope.isLoading = false; + }; + + let onPropertySuccess = (propertyFromBE:PropertyModel):void => { + console.info('onPropertyResourceSuccess : ', propertyFromBE); + this.$scope.isLoading = false; + + if (!doNotCloseModal) { + this.$uibModalInstance.close(); + } else { + this.$scope.forms.editForm.$setPristine(); + this.$scope.editPropertyModel.property = new 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.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 [PROPERTY_TYPES.LIST, 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.$uibModalInstance.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 == PROPERTY_TYPES.MAP) { + this.$scope.myValue = {'': null}; + } else if (this.$scope.editPropertyModel.property.type == PROPERTY_TYPES.LIST) { + this.$scope.myValue = []; + } + this.setMaxLength(); + }; + + this.$scope.delete = (property: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/src/app/view-models/forms/property-forms/component-property-form/property-form-view.html b/catalog-ui/src/app/view-models/forms/property-forms/component-property-form/property-form-view.html new file mode 100644 index 0000000000..f92d9a5ddc --- /dev/null +++ b/catalog-ui/src/app/view-models/forms/property-forms/component-property-form/property-form-view.html @@ -0,0 +1,201 @@ +<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"> + <loader data-display="isLoading" relative="false" size="medium"></loader> + <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': '50' }"></span> + <span ng-show="forms.editForm.propertyName.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span> + </div> + </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="400" + data-ng-disabled="isPropertyValueOwner || editPropertyModel.property.readonly" + maxlength="400" + 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 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()}}" + 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()}}" + 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()}}" + 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-group' : editPropertyModel.property.addOn}"> + <span ng-if="editPropertyModel.property.addOn" class="input-group-addon">{{editPropertyModel.property.addOn}}</span> + <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="('json'==editPropertyModel.property.type && forms.editForm.value.$setValidity('pattern', validateJson(editPropertyModel.property.value))) + ||(!forms.editForm.value.$error.pattern && ('integer'==editPropertyModel.property.type && forms.editForm.value.$setValidity('pattern', validateIntRange(editPropertyModel.property.value)) || onValueChange()))" + data-ng-change="" + 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> + </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/src/app/view-models/forms/property-forms/module-property-modal/module-property-model.ts b/catalog-ui/src/app/view-models/forms/property-forms/module-property-modal/module-property-model.ts new file mode 100644 index 0000000000..7359ac0e91 --- /dev/null +++ b/catalog-ui/src/app/view-models/forms/property-forms/module-property-modal/module-property-model.ts @@ -0,0 +1,206 @@ +/** + * Created by obarda on 1/18/2017. + */ +'use strict'; +import {PropertyModel, DisplayModule, Component, Resource, Service, ComponentInstance} from "app/models"; +import {UNIQUE_GROUP_PROPERTIES_NAME} from "app/utils"; +import {IPropertyFormBaseViewScope, PropertyFormBaseView} from "../base-property-form/property-form-base-model"; +import {DataTypesService} from "app/services/data-types-service"; + +export interface IModulePropertyViewScope extends IPropertyFormBaseViewScope { + onValueChange():void; +} + +export class ModulePropertyView extends PropertyFormBaseView { + + static '$inject' = [ + '$scope', + '$templateCache', + '$uibModalInstance', + '$injector', + 'originalProperty', + 'component', + 'selectedModule', + 'Sdc.Services.DataTypesService', + '$q' + ]; + + constructor(protected $scope:IModulePropertyViewScope, + protected $templateCache:ng.ITemplateCacheService, + protected $uibModalInstance:ng.ui.bootstrap.IModalServiceInstance, + protected $injector:ng.auto.IInjectorService, + protected originalProperty:PropertyModel, + protected component:Component, + private selectedModule:DisplayModule, + protected DataTypesService:DataTypesService, + private $q:ng.IQService) { + super($scope, $uibModalInstance, $injector, originalProperty, component, selectedModule.properties, DataTypesService); + + this.$templateCache.put("module-property-view.html", require('app/view-models/forms/property-forms/module-property-modal/module-property-view.html')); + this.$scope.innerViewSrcUrl = "module-property-view.html"; + this.initChildScope(); + } + + private findPropertyByName = (propertyName:string):PropertyModel => { + let property:PropertyModel = _.find(this.filteredProperties, (property:PropertyModel) => { + return property.name === propertyName; + }); + return property; + }; + + save(isNeedToCloseModal):ng.IPromise<boolean> { + + let deferred = this.$q.defer(); + + let onSuccess = (properties:Array<PropertyModel>):void => { + deferred.resolve(true); + if (isNeedToCloseModal === true) { + this.$scope.close(); + } + }; + + let onFailed = ():void => { + deferred.resolve(false); + }; + + let property = _.find(this.selectedModule.properties, (property) => { + return property.uniqueId === this.$scope.property.uniqueId; + }); + if (property.value !== this.$scope.property.value) { + if (this.component.isResource()) { + (<Resource>this.component).updateResourceGroupProperties(this.selectedModule, [this.$scope.property]).then(onSuccess, onFailed); // for now we only update one property at a time + } + if (this.component.isService()) { + // Find the component instance of the group instance + let componentInstance:ComponentInstance = _.find(this.component.componentInstances, (componentInstance:ComponentInstance) => { + let groupInstance = _.find(componentInstance.groupInstances, {uniqueId: this.selectedModule.groupInstanceUniqueId}); + return groupInstance !== undefined; + + }); + (<Service>this.component).updateGroupInstanceProperties(componentInstance.uniqueId, this.selectedModule, [this.$scope.property]).then(onSuccess, onFailed); // for now we only update one property at a time + } + } else { + deferred.resolve(true); + } + return deferred.promise; + } + + onPropertyChange():void { + this.initValidation(); + } + + protected initValidation = ():void => { + + this.$scope.isDeleteDisable = true; + this.$scope.isNameDisable = true; + this.$scope.isTypeSelectorDisable = true; + this.$scope.isDescriptionDisable = true; + + switch (this.$scope.property.name) { + case UNIQUE_GROUP_PROPERTIES_NAME.IS_BASE: + case UNIQUE_GROUP_PROPERTIES_NAME.VF_MODULE_TYPE: + case UNIQUE_GROUP_PROPERTIES_NAME.VOLUME_GROUP: + case UNIQUE_GROUP_PROPERTIES_NAME.VF_MODULE_LABEL: + this.$scope.property.readonly = true; + break; + case UNIQUE_GROUP_PROPERTIES_NAME.VF_MODULE_DESCRIPTION: + if (this.component.isService()) { + this.$scope.property.readonly = true; + } else { + this.$scope.property.readonly = false; + } + break; + } + }; + + private isUniqueProperty = ():boolean => { + return this.$scope.property.name === UNIQUE_GROUP_PROPERTIES_NAME.MIN_VF_MODULE_INSTANCES || + this.$scope.property.name === UNIQUE_GROUP_PROPERTIES_NAME.MAX_VF_MODULE_INSTANCES || + this.$scope.property.name === UNIQUE_GROUP_PROPERTIES_NAME.INITIAL_COUNT; + }; + + + private initChildScope = ():void => { + + this.initValidation(); + + // put default value when instance value is empty + this.$scope.onValueChange = ():void => { + + if (!this.$scope.property.value) { // Resetting to default value + if (this.isPropertyValueOwner()) { + if (this.component.isService()) { + this.$scope.property.value = this.$scope.property.parentValue; + } else { + this.$scope.property.value = this.$scope.property.defaultValue; + } + } + } + + if (this.isUniqueProperty()) { + + let isValid = true; + let maxProperty:PropertyModel = this.findPropertyByName(UNIQUE_GROUP_PROPERTIES_NAME.MAX_VF_MODULE_INSTANCES); + let minProperty:PropertyModel = this.findPropertyByName(UNIQUE_GROUP_PROPERTIES_NAME.MIN_VF_MODULE_INSTANCES); + let initialCountProperty:PropertyModel = this.findPropertyByName(UNIQUE_GROUP_PROPERTIES_NAME.INITIAL_COUNT); + + let maxPropertyValue = parseInt(maxProperty.value); + let minPropertyValue = parseInt(minProperty.value); + let initialCountPropertyValue = parseInt(initialCountProperty.value); + let propertyValue = parseInt(this.$scope.property.value); + let parentPropertyValue = parseInt(this.$scope.property.parentValue); + + switch (this.$scope.property.name) { + + case UNIQUE_GROUP_PROPERTIES_NAME.MIN_VF_MODULE_INSTANCES: + if (!maxPropertyValue || maxPropertyValue === null) { + isValid = propertyValue <= initialCountPropertyValue; + } + else { + isValid = propertyValue && (propertyValue <= maxPropertyValue && propertyValue <= initialCountPropertyValue); + } + this.$scope.forms.editForm["value"].$setValidity('maxValidation', isValid); + if (this.component.isService()) { + if (!parentPropertyValue || parentPropertyValue === null) { + isValid = true; + } else { + isValid = propertyValue >= parentPropertyValue; + this.$scope.forms.editForm["value"].$setValidity('minValidationVfLevel', isValid); + } + } + break; + case UNIQUE_GROUP_PROPERTIES_NAME.MAX_VF_MODULE_INSTANCES: + if (!minPropertyValue || minPropertyValue === null) { + isValid = propertyValue >= initialCountPropertyValue; + } else { + isValid = !propertyValue || (propertyValue >= minPropertyValue && propertyValue >= initialCountPropertyValue); + } + this.$scope.forms.editForm["value"].$setValidity('minValidation', isValid); + if (this.component.isService()) { + if (!parentPropertyValue || parentPropertyValue === null) { + isValid = true; + } + else { + isValid = propertyValue <= parentPropertyValue; + this.$scope.forms.editForm["value"].$setValidity('maxValidationVfLevel', isValid); + } + } + break; + case UNIQUE_GROUP_PROPERTIES_NAME.INITIAL_COUNT: + if ((!minPropertyValue || minPropertyValue === null) && (!maxPropertyValue || maxPropertyValue === null)) { + isValid = true; + } else if (!minPropertyValue || minPropertyValue === null) { + isValid = propertyValue <= maxPropertyValue; + } else if (!maxPropertyValue || maxPropertyValue === null) { + isValid = propertyValue >= minPropertyValue; + } else { + isValid = minPropertyValue <= propertyValue && propertyValue <= maxPropertyValue; + } + this.$scope.forms.editForm["value"].$setValidity('minOrMaxValidation', isValid); + break; + } + } + ; + } + } +} diff --git a/catalog-ui/src/app/view-models/forms/property-forms/module-property-modal/module-property-view.html b/catalog-ui/src/app/view-models/forms/property-forms/module-property-modal/module-property-view.html new file mode 100644 index 0000000000..175f4c199b --- /dev/null +++ b/catalog-ui/src/app/view-models/forms/property-forms/module-property-modal/module-property-view.html @@ -0,0 +1,41 @@ +<div class="default-value-section i-sdc-form-item"> + <label class="i-sdc-form-label">Default Value</label> + <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="!((property.simpleType||property.type) == 'boolean')" + data-ng-maxlength="maxLength" + data-ng-disabled="property.readonly && !isPropertyValueOwner()" + maxlength="100" + data-ng-model="property.value" + type="text" + name="value" + data-ng-pattern="getValidationPattern(property.type)" + data-ng-model-options="{ debounce: 200 }" + data-ng-change="onValueChange()" + /> + <select class="i-sdc-form-select" + data-tests-id="booleantype" + ng-if="(property.simpleType||property.type) == 'boolean'" + data-ng-disabled="property.readonly && !isPropertyValueOwner()" + name="value" + data-ng-model="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.minValidation" translate="MIN_VALIDATION_ERROR"></span> + <span ng-show="forms.editForm.value.$error.maxValidation" translate="MAX_VALIDATION_ERROR"></span> + <span ng-show="forms.editForm.value.$error.minOrMaxValidation" translate="MIN_MAX_VALIDATION"></span> + <span ng-show="forms.editForm.value.$error.minValidationVfLevel" translate="MIN_VALIDATION_VF_LEVE_ERROR"></span> + <span ng-show="forms.editForm.value.$error.maxValidationVfLevel" translate="MAX_VALIDATION_VF_LEVE_ERROR"></span> + + </div> + </div> +</div> diff --git a/catalog-ui/src/app/view-models/forms/property-forms/select-datatype-modal/select-datatype-modal-view-model.ts b/catalog-ui/src/app/view-models/forms/property-forms/select-datatype-modal/select-datatype-modal-view-model.ts new file mode 100644 index 0000000000..48aa47fdd0 --- /dev/null +++ b/catalog-ui/src/app/view-models/forms/property-forms/select-datatype-modal/select-datatype-modal-view-model.ts @@ -0,0 +1,97 @@ +'use strict'; +import {DataTypesService} from "app/services/data-types-service"; +import {PropertyModel, InputPropertyBase, Component} from "app/models"; +import {IPropertyFormBaseViewScope, PropertyFormBaseView} from "../base-property-form/property-form-base-model"; +import {PROPERTY_TYPES} from "app/utils/constants"; + +interface ISelectDataTypeViewModelScope extends IPropertyFormBaseViewScope { + selectedPropertiesName:string; + dataTypesService:DataTypesService; + path:string; + isTypeDataType:boolean; + myValue:any; + isReadOnly:boolean; +} + +export class SelectDataTypeViewModel extends PropertyFormBaseView { + + static '$inject' = [ + '$scope', + '$templateCache', + '$uibModalInstance', + '$injector', + 'originalProperty', + 'component', + 'filteredProperties', + 'Sdc.Services.DataTypesService', + 'propertiesMap', + '$q' + ]; + + constructor(protected $scope:ISelectDataTypeViewModelScope, + protected $templateCache:ng.ITemplateCacheService, + protected $uibModalInstance:ng.ui.bootstrap.IModalServiceInstance, + protected $injector:ng.auto.IInjectorService, + protected originalProperty:PropertyModel, + protected component:Component, + protected filteredProperties:Array<PropertyModel>, + protected DataTypesService:DataTypesService, + private propertiesMap:Array<InputPropertyBase>, + private $q:ng.IQService) { + super($scope, $uibModalInstance, $injector, originalProperty, component, filteredProperties, DataTypesService); + + this.$templateCache.put("select-datatype-modal-view.html", require('app/view-models/forms/property-forms/select-datatype-modal/select-datatype-modal-view.html')); + this.$scope.innerViewSrcUrl = "select-datatype-modal-view.html"; + this.initChildScope(); + } + + //scope methods + save(isNeedToCloseModal):ng.IPromise<boolean> { + let deferred = this.$q.defer(); + this.$scope.property.propertiesName = this.DataTypesService.selectedPropertiesName; + this.$scope.property.input = this.DataTypesService.selectedInput; + this.$scope.property.isAlreadySelected = true; + this.$uibModalInstance.close(this.$scope.property); + deferred.resolve(true); + return deferred.promise; + }; + + private initForNotSimpleType = ():void => { + let property = this.$scope.property; + this.$scope.isTypeDataType = this.DataTypesService.isDataTypeForPropertyType(this.$scope.property); + if (property.type && this.$scope.simpleTypes.indexOf(property.type) == -1) { + if (!(property.value || property.defaultValue)) { + switch (property.type) { + case PROPERTY_TYPES.MAP: + this.$scope.myValue = {'': null}; + break; + case PROPERTY_TYPES.LIST: + this.$scope.myValue = []; + break; + default: + this.$scope.myValue = {}; + } + } else { + this.$scope.myValue = JSON.parse(property.value || property.defaultValue); + } + } + }; + + //remove selection property on the modal + private removeSelected = ():void => { + this.DataTypesService.selectedPropertiesName = null; + this.DataTypesService.selectedInput = null; + }; + + private initChildScope = ():void => { + //scope properties + this.$scope.forms = {}; + this.$scope.path = this.$scope.property.name; + this.$scope.isArrowsDisabled = true; + this.DataTypesService.alreadySelectedProperties = this.propertiesMap; + this.$scope.dataTypesService = this.DataTypesService; + this.$scope.isReadOnly = true; + this.initForNotSimpleType(); + this.removeSelected(); + } +} diff --git a/catalog-ui/src/app/view-models/forms/property-forms/select-datatype-modal/select-datatype-modal-view.html b/catalog-ui/src/app/view-models/forms/property-forms/select-datatype-modal/select-datatype-modal-view.html new file mode 100644 index 0000000000..acb0f292ff --- /dev/null +++ b/catalog-ui/src/app/view-models/forms/property-forms/select-datatype-modal/select-datatype-modal-view.html @@ -0,0 +1,74 @@ +<!--<div>selectedPropertiesName - {{dataTypesService.selectedPropertiesName}}</div>--> +<!--<div>selectedInput - {{dataTypesService.selectedInput}}</div>--> + +<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"> + <select-fields-structure value-obj-ref="myValue" + type-name="property.type" + parent-form-obj="forms.editForm" + fields-prefix-name="currentPropertyIndex" + read-only="true" + default-value="{{getDefaultValue()}}" + path="{{property.name}}" + is-parent-already-input="false" + expand-by-default="true"></select-fields-structure> + + </div> + <div data-ng-if="!isTypeDataType" ng-switch="property.type"> + <div ng-switch-when="map"> + + <select-type-map value-obj-ref="myValue" + schema-property="property.schema.property" + parent-form-obj="forms.editForm" + fields-prefix-name="currentPropertyIndex" + read-only="true" + default-value="{{getDefaultValue()}}" + max-length="maxLength"></select-type-map> + </div> + <div ng-switch-when="list"> + <select-type-list value-obj-ref="myValue" + schema-property="property.schema.property" + parent-form-obj="forms.editForm" + fields-prefix-name="currentPropertyIndex" + read-only="true" + default-value="{{getDefaultValue()}}" + max-length="maxLength"></select-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="!((property.simpleType||property.type) == 'boolean')" + data-ng-maxlength="maxLength" + data-ng-disabled="isReadOnly" + maxlength="{{maxLength}}" + data-ng-model="property.value" + type="text" + name="value" + data-ng-pattern="getValidationPattern((property.simpleType||property.type))" + data-ng-model-options="{ debounce: 200 }" + data-ng-change="('json'==property.type && forms.editForm.value.$setValidity('pattern', validateJson(property.value))) + ||(!forms.editForm.value.$error.pattern && ('integer'==property.type && forms.editForm.value.$setValidity('pattern', validateIntRange(property.value)) || onValueChange()))" + data-ng-change="" + autofocus /> + <select class="i-sdc-form-select" + data-tests-id="booleantype" + ng-if="(property.simpleType||property.type) == 'boolean'" + data-ng-disabled="isReadOnly" + name="value" + data-ng-change="onValueChange()" + data-ng-model="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> + </div> + </div> + </div> + </div> +</div> diff --git a/catalog-ui/src/app/view-models/forms/property-forms/select-datatype-modal/select-datatype-modal.less b/catalog-ui/src/app/view-models/forms/property-forms/select-datatype-modal/select-datatype-modal.less new file mode 100644 index 0000000000..15e30af4ee --- /dev/null +++ b/catalog-ui/src/app/view-models/forms/property-forms/select-datatype-modal/select-datatype-modal.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/src/app/view-models/forms/resource-instance-name-form/resource-instance-name-model.ts b/catalog-ui/src/app/view-models/forms/resource-instance-name-form/resource-instance-name-model.ts new file mode 100644 index 0000000000..869e3db584 --- /dev/null +++ b/catalog-ui/src/app/view-models/forms/resource-instance-name-form/resource-instance-name-model.ts @@ -0,0 +1,95 @@ +'use strict'; +import {ComponentInstanceFactory} from "app/utils"; +import {ComponentInstance} from "app/models"; +import {Requirement, Component, Capability} from "app/models"; + +interface IResourceInstanceViewModelScope extends ng.IScope { + + componentInstanceModel: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', + '$uibModalInstance', + 'component' + ]; + + + constructor(private $scope:IResourceInstanceViewModelScope, + private ValidationPattern:RegExp, + private $uibModalInstance:ng.ui.bootstrap.IModalServiceInstance, + private component:Component) { + + this.initScope(); + } + + + private initScope = ():void => { + this.$scope.forms = {}; + this.$scope.validationPattern = this.ValidationPattern; + this.$scope.componentInstanceModel = ComponentInstanceFactory.createComponentInstance(this.component.selectedInstance); + this.$scope.oldName = this.component.selectedInstance.name; + this.$scope.modalInstanceName = this.$uibModalInstance; + + this.$scope.isAlreadyPressed = false; + + + this.$scope.close = ():void => { + this.$uibModalInstance.dismiss(); + }; + + this.$scope.save = ():void => { + + let onFailed = () => { + this.$scope.isAlreadyPressed = true; + }; + + let onSuccess = (componentInstance:ComponentInstance) => { + this.$uibModalInstance.close(); + this.$scope.isAlreadyPressed = false; + this.$scope.componentInstanceModel = componentInstance; + //this.component.name = componentInstance.name;//DE219124 + this.component.selectedInstance.name = componentInstance.name; + //update requirements and capabilities owner name + _.forEach(this.component.selectedInstance.requirements, (requirementsArray:Array<Requirement>) => { + _.forEach(requirementsArray, (requirement:Requirement):void => { + requirement.ownerName = componentInstance.name; + }); + }); + + _.forEach(this.component.selectedInstance.capabilities, (capabilitiesArray:Array<Capability>) => { + _.forEach(capabilitiesArray, (capability:Capability):void => { + capability.ownerName = 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}, + {'name': 'Cancel', 'css': 'grey', 'callback': this.$scope.close} + ]; + + this.$scope.$watch("[forms.editNameForm.$invalid,componentInstanceModel.name,isAlreadyPressed]", (newVal, oldVal) => { + this.$scope.footerButtons[0].disabled = this.$scope.forms.editNameForm.$invalid || this.$scope.isAlreadyPressed || this.$scope.componentInstanceModel.name === this.$scope.oldName; + }); + } +} diff --git a/catalog-ui/src/app/view-models/forms/resource-instance-name-form/resource-instance-name-view.html b/catalog-ui/src/app/view-models/forms/resource-instance-name-form/resource-instance-name-view.html new file mode 100644 index 0000000000..e04343adbd --- /dev/null +++ b/catalog-ui/src/app/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/src/app/view-models/forms/resource-instance-name-form/resource-instance-name.less b/catalog-ui/src/app/view-models/forms/resource-instance-name-form/resource-instance-name.less new file mode 100644 index 0000000000..57698bef17 --- /dev/null +++ b/catalog-ui/src/app/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/src/app/view-models/modals/confirmation-modal/confirmation-modal-view-model.ts b/catalog-ui/src/app/view-models/modals/confirmation-modal/confirmation-modal-view-model.ts new file mode 100644 index 0000000000..3d8b6c3053 --- /dev/null +++ b/catalog-ui/src/app/view-models/modals/confirmation-modal/confirmation-modal-view-model.ts @@ -0,0 +1,73 @@ +'use strict'; +import {ValidationUtils, ModalType} from "app/utils"; + +export interface IConfirmationModalModel { + title:string; + message:string; + showComment:boolean; + type: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', '$uibModalInstance', 'confirmationModalModel', 'CommentValidationPattern', 'ValidationUtils']; + + constructor(private $scope:IConfirmationModalViewModelScope, + private $uibModalInstance:ng.ui.bootstrap.IModalServiceInstance, + confirmationModalModel:IConfirmationModalModel, + private CommentValidationPattern:RegExp, + private ValidationUtils:ValidationUtils) { + + this.initScope(confirmationModalModel); + } + + private initScope = (confirmationModalModel:IConfirmationModalModel):void => { + let self = this; + this.$scope.hideCancelButton = false; + this.$scope.modalInstanceConfirmation = this.$uibModalInstance; + this.$scope.confirmationModalModel = confirmationModalModel; + this.$scope.comment = {"text": ''}; + this.$scope.commentValidationPattern = this.CommentValidationPattern; + + this.$scope.ok = ():any => { + self.$uibModalInstance.close(this.ValidationUtils.stripAndSanitize(self.$scope.comment.text)); + }; + + this.$scope.cancel = ():void => { + console.info('Cancel pressed on: ' + this.$scope.confirmationModalModel.title); + self.$uibModalInstance.dismiss(); + }; + + // Set the OK button color according to modal type (standard, error, alert) + let _okButtonColor = 'blue'; // Default + switch (confirmationModalModel.type) { + case ModalType.STANDARD: + _okButtonColor = 'blue'; + break; + case ModalType.ERROR: + _okButtonColor = 'red'; + break; + case ModalType.ALERT: + this.$scope.hideCancelButton = true; + _okButtonColor = 'grey'; + break; + default: + _okButtonColor = 'blue'; + break; + } + this.$scope.okButtonColor = _okButtonColor; + + } +} diff --git a/catalog-ui/src/app/view-models/modals/confirmation-modal/confirmation-modal-view.html b/catalog-ui/src/app/view-models/modals/confirmation-modal/confirmation-modal-view.html new file mode 100644 index 0000000000..09c27f8cd3 --- /dev/null +++ b/catalog-ui/src/app/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/src/app/view-models/modals/confirmation-modal/confirmation-modal.less b/catalog-ui/src/app/view-models/modals/confirmation-modal/confirmation-modal.less new file mode 100644 index 0000000000..666c41d5ed --- /dev/null +++ b/catalog-ui/src/app/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/src/app/view-models/modals/email-modal/email-modal-view-model.ts b/catalog-ui/src/app/view-models/modals/email-modal/email-modal-view-model.ts new file mode 100644 index 0000000000..f1fb56d0ff --- /dev/null +++ b/catalog-ui/src/app/view-models/modals/email-modal/email-modal-view-model.ts @@ -0,0 +1,96 @@ +'use strict'; +import {IAppConfigurtaion, Component, AsdcComment} from "app/models"; +import {ValidationUtils} from "app/utils"; + +export interface IEmailModalModel_Email { + to:string; + subject:string; + message:string; +} + +export interface IEmailModalModel_Data { + component: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', '$uibModalInstance', 'emailModalModel', 'ValidationUtils', 'CommentValidationPattern']; + + constructor(private $scope:IEmailModalViewModelScope, + private $filter:ng.IFilterService, + private sdcConfig:IAppConfigurtaion, + private $uibModalInstance:ng.ui.bootstrap.IModalServiceInstance, + private emailModalModel:IEmailModalModel, + private ValidationUtils: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.$uibModalInstance; + + this.$scope.submit = ():any => { + + let onSuccess = (component:Component) => { + this.$scope.isLoading = false; + this.$scope.submitInProgress = false; + // showing the outlook modal according to the config json + if (this.sdcConfig.showOutlook) { + 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 + "'}"); + window.location.href = outlook; // Open outlook with the email to send + } + this.$uibModalInstance.close(component); // Close the dialog + }; + + let onError = () => { + this.$scope.isLoading = false; + this.$scope.submitInProgress = false; + this.$uibModalInstance.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:AsdcComment = new AsdcComment(); + comment.userRemarks = emailModalModel.email.message; + emailModalModel.data.component.changeLifecycleState(emailModalModel.data.stateUrl, comment).then(onSuccess, onError); + } + }; + + this.$scope.cancel = ():void => { + this.$uibModalInstance.dismiss(); + }; + + this.$scope.validateField = (field:any):boolean => { + if (field && field.$dirty && field.$invalid) { + return true; + } + return false; + }; + } +} diff --git a/catalog-ui/src/app/view-models/modals/email-modal/email-modal-view.html b/catalog-ui/src/app/view-models/modals/email-modal/email-modal-view.html new file mode 100644 index 0000000000..bf65428033 --- /dev/null +++ b/catalog-ui/src/app/view-models/modals/email-modal/email-modal-view.html @@ -0,0 +1,77 @@ +<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="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> + + </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/src/app/view-models/modals/email-modal/email-modal.less b/catalog-ui/src/app/view-models/modals/email-modal/email-modal.less new file mode 100644 index 0000000000..471089fa1a --- /dev/null +++ b/catalog-ui/src/app/view-models/modals/email-modal/email-modal.less @@ -0,0 +1,57 @@ +.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; + margin-bottom: 20px; + } + + 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/src/app/view-models/modals/error-modal/error-403-view.html b/catalog-ui/src/app/view-models/modals/error-modal/error-403-view.html new file mode 100644 index 0000000000..41b1c6df1d --- /dev/null +++ b/catalog-ui/src/app/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/src/app/view-models/modals/error-modal/error-view-model.ts b/catalog-ui/src/app/view-models/modals/error-modal/error-view-model.ts new file mode 100644 index 0000000000..f622a6f53b --- /dev/null +++ b/catalog-ui/src/app/view-models/modals/error-modal/error-view-model.ts @@ -0,0 +1,19 @@ +'use strict'; +import {CookieService} from "app/services"; + +interface IErrorViewModelScope { + mailto:string; +} + +export class ErrorViewModel { + + static ADMIN_EMAIL = 'dl-asdcaccessrequest@att.com'; + static SUBJECT_PRFIEX = 'SDC Access Request for'; + + static '$inject' = ['$scope', 'Sdc.Services.CookieService', '$window']; + + constructor($scope:IErrorViewModelScope, cookieService:CookieService, $window) { + let userDetails = cookieService.getFirstName() + ' ' + cookieService.getLastName() + ' (' + cookieService.getUserId() + ')'; + $scope.mailto = ErrorViewModel.ADMIN_EMAIL + '?subject=' + $window.encodeURIComponent(ErrorViewModel.SUBJECT_PRFIEX + ' ' + userDetails); + } +} diff --git a/catalog-ui/src/app/view-models/modals/error-modal/error.less b/catalog-ui/src/app/view-models/modals/error-modal/error.less new file mode 100644 index 0000000000..8297b5053d --- /dev/null +++ b/catalog-ui/src/app/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/src/app/view-models/modals/message-modal/message-base-modal-model.ts b/catalog-ui/src/app/view-models/modals/message-modal/message-base-modal-model.ts new file mode 100644 index 0000000000..3c9e75238a --- /dev/null +++ b/catalog-ui/src/app/view-models/modals/message-modal/message-base-modal-model.ts @@ -0,0 +1,43 @@ +'use strict'; +import {SEVERITY} from "app/utils"; + +export interface IMessageModalModel { + title:string; + message:string; + severity: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/src/app/view-models/modals/message-modal/message-client-modal/client-message-modal-view-model.ts b/catalog-ui/src/app/view-models/modals/message-modal/message-client-modal/client-message-modal-view-model.ts new file mode 100644 index 0000000000..053ea41ba3 --- /dev/null +++ b/catalog-ui/src/app/view-models/modals/message-modal/message-client-modal/client-message-modal-view-model.ts @@ -0,0 +1,22 @@ +'use strict'; +import {IMessageModalModel, MessageModalViewModel, IMessageModalViewModelScope} from "../message-base-modal-model"; + +export interface IClientMessageModalModel extends IMessageModalModel { +} + +export interface IClientMessageModalViewModelScope extends IMessageModalViewModelScope { + clientMessageModalModel:IClientMessageModalModel; +} + +export class ClientMessageModalViewModel extends MessageModalViewModel { + + static '$inject' = ['$scope', '$uibModalInstance', 'clientMessageModalModel']; + + constructor(private $scope:IClientMessageModalViewModelScope, + private $uibModalInstance:ng.ui.bootstrap.IModalServiceInstance, + private clientMessageModalModel:IClientMessageModalModel) { + + super($scope, $uibModalInstance, clientMessageModalModel); + } + +} diff --git a/catalog-ui/src/app/view-models/modals/message-modal/message-client-modal/client-message-modal-view.html b/catalog-ui/src/app/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/src/app/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/src/app/view-models/modals/message-modal/message-client-modal/client-message-modal.less b/catalog-ui/src/app/view-models/modals/message-modal/message-client-modal/client-message-modal.less new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/catalog-ui/src/app/view-models/modals/message-modal/message-client-modal/client-message-modal.less diff --git a/catalog-ui/src/app/view-models/modals/message-modal/message-server-modal/server-message-modal-view-model.ts b/catalog-ui/src/app/view-models/modals/message-modal/message-server-modal/server-message-modal-view-model.ts new file mode 100644 index 0000000000..5f1d5e7a92 --- /dev/null +++ b/catalog-ui/src/app/view-models/modals/message-modal/message-server-modal/server-message-modal-view-model.ts @@ -0,0 +1,24 @@ +'use strict'; +import {IMessageModalModel, IMessageModalViewModelScope, MessageModalViewModel} from "../message-base-modal-model"; + +export interface IServerMessageModalModel extends IMessageModalModel { + status:string; + messageId:string; +} + +export interface IServerMessageModalViewModelScope extends IMessageModalViewModelScope { + serverMessageModalModel:IServerMessageModalModel; +} + +export class ServerMessageModalViewModel extends MessageModalViewModel { + + static '$inject' = ['$scope', '$uibModalInstance', 'serverMessageModalModel']; + + constructor(private $scope:IServerMessageModalViewModelScope, + private $uibModalInstance:ng.ui.bootstrap.IModalServiceInstance, + private serverMessageModalModel:IServerMessageModalModel) { + + super($scope, $uibModalInstance, serverMessageModalModel); + } + +} diff --git a/catalog-ui/src/app/view-models/modals/message-modal/message-server-modal/server-message-modal-view.html b/catalog-ui/src/app/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/src/app/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/src/app/view-models/modals/message-modal/message-server-modal/server-message-modal.less b/catalog-ui/src/app/view-models/modals/message-modal/message-server-modal/server-message-modal.less new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/catalog-ui/src/app/view-models/modals/message-modal/message-server-modal/server-message-modal.less diff --git a/catalog-ui/src/app/view-models/modals/onboarding-modal/onboarding-modal-view-model.ts b/catalog-ui/src/app/view-models/modals/onboarding-modal/onboarding-modal-view-model.ts new file mode 100644 index 0000000000..8e7e79c576 --- /dev/null +++ b/catalog-ui/src/app/view-models/modals/onboarding-modal/onboarding-modal-view-model.ts @@ -0,0 +1,249 @@ +'use strict'; +import {ComponentType, CHANGE_COMPONENT_CSAR_VERSION_FLAG, SEVERITY, FileUtils, ModalsHandler, ComponentFactory} from "app/utils"; +import {OnboardingService, CacheService} from "app/services"; +import {Component, IComponent, IUser, IAppConfigurtaion, Resource} from "app/models"; +import {IServerMessageModalModel} from "../message-modal/message-server-modal/server-message-modal-view-model"; +import {Dictionary} from "app/utils"; +import * as _ from 'underscore'; + +interface IOnboardingModalViewModelScope { + modalOnboarding:ng.ui.bootstrap.IModalServiceInstance; + componentsList:Array<IComponent>; + tableHeadersList:Array<any>; + selectedComponent:Component; + componentFromServer:Component; + reverse:boolean; + sortBy:string; + searchBind:string; + okButtonText:string; + isCsarComponentExists:boolean; + user:IUser; + isLoading:boolean; + + //this is for UI paging + numberOfItemsToDisplay:number; + allItemsDisplayed:boolean; + + doSelectComponent(component:Component):void; + doUpdateCsar():void; + doImportCsar():void; + sort(sortBy:string):void; + downloadCsar(packageId:string):void; + increaseNumItemsToDisplay():void; +} + +export class OnboardingModalViewModel { + + static '$inject' = [ + '$scope', + '$filter', + '$state', + 'sdcConfig', + '$uibModalInstance', + 'Sdc.Services.OnboardingService', + 'okButtonText', + 'currentCsarUUID', + 'Sdc.Services.CacheService', + 'FileUtils', + 'ComponentFactory', + 'ModalsHandler' + ]; + + constructor(private $scope:IOnboardingModalViewModelScope, + private $filter:ng.IFilterService, + private $state:any, + private sdcConfig:IAppConfigurtaion, + private $uibModalInstance:ng.ui.bootstrap.IModalServiceInstance, + private onBoardingService:OnboardingService, + private okButtonText:string, + private currentCsarUUID:string, + private cacheService:CacheService, + private fileUtils:FileUtils, + private componentFactory:ComponentFactory, + private modalsHandler: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; + this.$scope.numberOfItemsToDisplay = 0; + this.$scope.allItemsDisplayed = false; + + // Dismiss the modal and pass the "mini" component to workspace general page + this.$scope.doImportCsar = ():void => { + this.$uibModalInstance.dismiss(); + this.$state.go('workspace.general', { + type: 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.$uibModalInstance.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:Components.Component = this.componentFactory.createComponent(this.$scope.componentFromServer); + this.$state.go('workspace.general', {vspComponent: component, disableButtons: true });*/ + this.cacheService.set(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.$scope.isLoading = true; + this.onBoardingService.downloadOnboardingCsar(packageId).then( + (file:any):void => { + this.$scope.isLoading = false; + if (file) { + this.fileUtils.downloadFile(file, packageId + '.csar'); + } + }, ():void => { + this.$scope.isLoading = false; + var data:IServerMessageModalModel = { + title: 'Download error', + message: "Error downloading file", + severity: SEVERITY.ERROR, + messageId: "", + status: "" + }; + this.modalsHandler.openServerMessageModal(data); + } + ); + }; + + this.$scope.increaseNumItemsToDisplay = ():void => { + this.$scope.numberOfItemsToDisplay = this.$scope.numberOfItemsToDisplay + 40; + if (this.$scope.componentsList) { + this.$scope.allItemsDisplayed = this.$scope.numberOfItemsToDisplay >= this.$scope.componentsList.length; + } + }; + + // When the user select a row, set the component as selectedComponent + this.$scope.doSelectComponent = (component: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: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.$uibModalInstance; + }; + + private initOnboardingComponentsList = ():void => { + let onSuccess = (onboardingResponse:Array<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<IComponent>):void => { + // Get only the latest version of each item + this.$scope.componentsList = []; + + // Get all unique items from the list + let uniqueItems:Array<any> = _.uniq(onboardingResponse, false, (item:any):void=>{ + return item.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<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/src/app/view-models/modals/onboarding-modal/onboarding-modal-view.html b/catalog-ui/src/app/view-models/modals/onboarding-modal/onboarding-modal-view.html new file mode 100644 index 0000000000..3657fad017 --- /dev/null +++ b/catalog-ui/src/app/view-models/modals/onboarding-modal/onboarding-modal-view.html @@ -0,0 +1,144 @@ +<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" id="onboarding-modal-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> + + <div infinite-scroll-disabled='allItemsDisplayed' infinite-scroll="increaseNumItemsToDisplay()" infinite-scroll-container="'#onboarding-modal-scrollbar-container'"> + + <!-- Loop on components list --> + <div data-ng-repeat-start="component in componentsList | filter: searchBind | orderBy:sortBy:reverse | limitTo:numberOfItemsToDisplay 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> + </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/src/app/view-models/modals/onboarding-modal/onboarding-modal.less b/catalog-ui/src/app/view-models/modals/onboarding-modal/onboarding-modal.less new file mode 100644 index 0000000000..c745a86888 --- /dev/null +++ b/catalog-ui/src/app/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/src/app/view-models/onboard-vendor/onboard-vendor-view-model.ts b/catalog-ui/src/app/view-models/onboard-vendor/onboard-vendor-view-model.ts new file mode 100644 index 0000000000..faeaefb5e5 --- /dev/null +++ b/catalog-ui/src/app/view-models/onboard-vendor/onboard-vendor-view-model.ts @@ -0,0 +1,125 @@ +'use strict'; +import {IUserProperties} from "app/models"; +import {MenuItemGroup, MenuItem} from "app/utils"; +import {CacheService} from "app/services"; + +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<MenuItemGroup>; + topNavRootMenu:MenuItemGroup; + user:IUserProperties; + version:string; +} + +export class OnboardVendorViewModel { + static '$inject' = [ + '$scope', + '$q', + 'Sdc.Services.CacheService' + ]; + + private firstControlledTopNavMenu:MenuItemGroup; + + constructor(private $scope:IOnboardVendorViewModelScope, + private $q:ng.IQService, + private cacheService: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 MenuItemGroup(); + topNavMenu.menuItems = breadcrumbMenu.menuItems.map(menuItem => + new 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/src/app/view-models/onboard-vendor/onboard-vendor-view.html b/catalog-ui/src/app/view-models/onboard-vendor/onboard-vendor-view.html new file mode 100644 index 0000000000..734fb93daf --- /dev/null +++ b/catalog-ui/src/app/view-models/onboard-vendor/onboard-vendor-view.html @@ -0,0 +1,16 @@ +<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> + + <ecomp-footer></ecomp-footer> + +</div> diff --git a/catalog-ui/src/app/view-models/onboard-vendor/onboard-vendor.less b/catalog-ui/src/app/view-models/onboard-vendor/onboard-vendor.less new file mode 100644 index 0000000000..4a16ca2b5b --- /dev/null +++ b/catalog-ui/src/app/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('/assets/styles/images/sprites/sprite-global-old.png') no-repeat -53px -2889px; + width: 14px; + height: 14px; + + } + } + + &.CERTIFIED { + .i-sdc-categories-list-item-icon { + background: url('/assets/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('/assets/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('/assets/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('/assets/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/src/app/view-models/preloading/preloading-view.html b/catalog-ui/src/app/view-models/preloading/preloading-view.html new file mode 100644 index 0000000000..c0512dd9ec --- /dev/null +++ b/catalog-ui/src/app/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/src/app/view-models/preloading/preloading-view.ts b/catalog-ui/src/app/view-models/preloading/preloading-view.ts new file mode 100644 index 0000000000..f299f2a30f --- /dev/null +++ b/catalog-ui/src/app/view-models/preloading/preloading-view.ts @@ -0,0 +1,27 @@ +'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/src/app/view-models/support/support-view-model.ts b/catalog-ui/src/app/view-models/support/support-view-model.ts new file mode 100644 index 0000000000..2f43d87b18 --- /dev/null +++ b/catalog-ui/src/app/view-models/support/support-view-model.ts @@ -0,0 +1,16 @@ +'use strict'; +import {CacheService} from "app/services"; + +interface ISupportViewModelScope { + version:string; +} + +export class SupportViewModel { + + static '$inject' = ['$scope', 'Sdc.Services.CacheService']; + + constructor(private $scope:ISupportViewModelScope, + private cacheService:CacheService) { + this.$scope.version = this.cacheService.get('version'); + } +} diff --git a/catalog-ui/src/app/view-models/support/support-view.html b/catalog-ui/src/app/view-models/support/support-view.html new file mode 100644 index 0000000000..88609774bb --- /dev/null +++ b/catalog-ui/src/app/view-models/support/support-view.html @@ -0,0 +1,31 @@ +<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" translate="PROJECT_TITLE"></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/src/app/view-models/support/support.less b/catalog-ui/src/app/view-models/support/support.less new file mode 100644 index 0000000000..8159e38320 --- /dev/null +++ b/catalog-ui/src/app/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/src/app/view-models/tabs/general-tab.less b/catalog-ui/src/app/view-models/tabs/general-tab.less new file mode 100644 index 0000000000..936b3e3414 --- /dev/null +++ b/catalog-ui/src/app/view-models/tabs/general-tab.less @@ -0,0 +1,122 @@ +.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 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; + } + } + } + + .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; + } + .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: 10px 0 0 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/src/app/view-models/tabs/hierarchy/hierarchy-view-model.ts b/catalog-ui/src/app/view-models/tabs/hierarchy/hierarchy-view-model.ts new file mode 100644 index 0000000000..7c505512c4 --- /dev/null +++ b/catalog-ui/src/app/view-models/tabs/hierarchy/hierarchy-view-model.ts @@ -0,0 +1,98 @@ +'use strict'; +import {ModalsHandler} from "app/utils"; +import {PropertyModel, DisplayModule, Component, ComponentInstance, Tab, Module} from "app/models"; +import {ExpandCollapseListData} from "app/directives/utils/expand-collapse-list-header/expand-collapse-list-header"; + +export interface IHierarchyScope extends ng.IScope { + component:Component; + selectedIndex:number; + selectedModule:DisplayModule; + singleTab:Tab; + isLoading:boolean; + expandCollapseArtifactsList:ExpandCollapseListData; + expandCollapsePropertiesList:ExpandCollapseListData; + selectedInstanceId:string; + + onModuleSelected(moduleId:string, selectedIndex:number):void; + onModuleNameChanged(module:DisplayModule):void; + updateHeatName():void; + loadInstanceModules(instance:ComponentInstance):ng.IPromise<boolean>; + openEditPropertyModal(property:PropertyModel):void; +} + +export class HierarchyViewModel { + + static '$inject' = [ + '$scope', + '$q', + 'ModalsHandler' + ]; + + constructor(private $scope:IHierarchyScope, private $q:ng.IQService, private ModalsHandler:ModalsHandler) { + this.$scope.component = this.$scope.singleTab.data; + this.$scope.isLoading = false; + this.$scope.expandCollapseArtifactsList = new ExpandCollapseListData(); + this.$scope.expandCollapsePropertiesList = new ExpandCollapseListData(); + this.initScopeMethods(); + } + + private initScopeMethods():void { + + let collapseModuleData = ():void => { + this.$scope.expandCollapseArtifactsList.expandCollapse = false; + this.$scope.expandCollapsePropertiesList.expandCollapse = false; + this.$scope.expandCollapseArtifactsList.orderByField = "artifactName"; + this.$scope.expandCollapsePropertiesList.orderByField = "name"; + }; + + this.$scope.onModuleSelected = (moduleId:string, selectedIndex:number, componentInstanceId?:string):void => { + + let onSuccess = (module:DisplayModule) => { + console.log("Module Loaded: ", module); + this.$scope.selectedModule = module; + this.$scope.isLoading = false; + collapseModuleData(); + }; + + 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; + if (this.$scope.component.isService()) { + this.$scope.selectedInstanceId = componentInstanceId; + this.$scope.component.getModuleInstanceForDisplay(componentInstanceId, moduleId).then(onSuccess, onFailed); + } else { + 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: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 DisplayModule(this.$scope.selectedModule)).then(onSuccess, onFailed); + }; + + this.$scope.openEditPropertyModal = (property:PropertyModel):void => { + this.ModalsHandler.openEditModulePropertyModal(property, this.$scope.component, this.$scope.selectedModule).then(() => { + }); + } + } +} diff --git a/catalog-ui/src/app/view-models/tabs/hierarchy/hierarchy-view.html b/catalog-ui/src/app/view-models/tabs/hierarchy/hierarchy-view.html new file mode 100644 index 0000000000..9eaa3a0f76 --- /dev/null +++ b/catalog-ui/src/app/view-models/tabs/hierarchy/hierarchy-view.html @@ -0,0 +1,107 @@ +<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="DEPLOYMENT_TAB_TITLE"></div> + + <div class="resizable-container"> + <div data-ng-if="!component.isService()"class="resizable-section"> + + <perfect-scrollbar scroll-y-margin-offset="0" include-padding="true" + class="general-tab-scrollbar-container"> + <div class="sdc-general-tab-sub-title" data-tests-id="tab-sub-header">{{component.name}}</div> + <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 first-level" 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 data-ng-if="component.isService()"class="resizable-section"> + <perfect-scrollbar scroll-y-margin-offset="0" include-padding="true" + class="general-tab-scrollbar-container"> + <expand-collapse expanded-selector=".hierarchy-modules-list.{{$index}}" + class="general-tab-expand-collapse" is-close-on-init="true" + data-tests-id="hierarchy-instance-{{$index}}" + + data-ng-repeat-start="instance in component.componentInstances"> + <div class="expand-collapse-title first-level" data-tests-id="hierarchy-instance-{{$index}}-title"> + <div class="expand-collapse-title-icon"></div> + <span class="expand-collapse-title-text" data-ng-bind="instance.name" tooltips + tooltip-content="{{instance.name}}"></span> + + </div> + </expand-collapse> + <!--TODO: Rachel : --> + <div data-ng-repeat-end="" class="hierarchy-modules-list {{$index}}"> + <expand-collapse expanded-selector=".outer-index-{{$parent.$index}}.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 instance.groupInstances"> + <div class="expand-collapse-title second-level" data-tests-id="hierarchy-module-{{$index}}-title" ng-class="{'selected': selectedIndex === $index && selectedInstanceId === instance.uniqueId}" data-ng-click="onModuleSelected(module.uniqueId, $index, instance.uniqueId)"> + <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="outer-index-{{$parent.$index}} 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> + </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" data-ng-if="!component.isService()"> + <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="" data-tests-id="selected-module-group-uuid" tooltips tooltip-content="{{selectedModule.groupUUID}}"><span class="bold">Module ID:</span><br><span class="small-font">{{selectedModule.groupUUID}}</span></div> + <div class="" data-tests-id="selected-module-group-customization-uuid" data-ng-if="component.isService() && isViewOnly" tooltips tooltip-content="{{selectedModule.customizationUUID}}"><span class="bold">Customization ID:</span ><br><span class="small-font">{{selectedModule.customizationUUID}}</span></div> + <div class="" data-tests-id="selected-module-group-invariant-uuid" tooltips tooltip-content="{{selectedModule.invariantUUID}}"><span class="bold">Invariant UUID:</span><span class="small-font">{{selectedModule.invariantUUID}}</span></div> + <div data-tests-id="selected-module-version"><span class="bold">Version:</span> {{selectedModule.version}}</div> + <div data-tests-id="selected-module-is-base"><span class="bold">IsBase:</span> {{selectedModule.isBase}}</div> + + </div> + <expand-collapse-list-header title="Properties" expand-collapse-list-data="expandCollapsePropertiesList"></expand-collapse-list-header> + <div ng-repeat="property in selectedModule.properties | filter: expandCollapsePropertiesList.filter | orderBy:expandCollapsePropertiesList.orderByField track by $index" data-ng-if="expandCollapsePropertiesList.expandCollapse"> + <div class="list-item property-data" data-ng-class="{'last':$last}"> + <div class="property-name module-text-overflow" data-tests-id="selected-module-property-name"> + <span tooltips tooltip-content="{{property.name}}" + data-ng-class="{'hand': !isViewOnly}" + data-ng-click="!isViewOnly && openEditPropertyModal(property)">{{property.name}}</span> + </div> + <div class="module-text-overflow property-info" data-tests-id="selected-module-property-type"> Type: {{property.type}}</div> + <div class="module-text-overflow property-info" data-tests-id="selected-module-property-schema-type">Value: {{property.value}}</div> + </div> + </div> + <expand-collapse-list-header title="Artifacts" expand-collapse-list-data="expandCollapseArtifactsList"></expand-collapse-list-header> + <div ng-repeat="artifact in selectedModule.artifacts| filter: expandCollapseArtifactsList.filter | orderBy:expandCollapseArtifactsList.orderByField track by $index" data-ng-if="expandCollapseArtifactsList.expandCollapse"> + <div class="list-item artifact-data" data-ng-class="{'last':$last}"> + <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/src/app/view-models/tabs/hierarchy/hierarchy.less b/catalog-ui/src/app/view-models/tabs/hierarchy/hierarchy.less new file mode 100644 index 0000000000..dee0eeb38b --- /dev/null +++ b/catalog-ui/src/app/view-models/tabs/hierarchy/hierarchy.less @@ -0,0 +1,101 @@ +.hierarchy-tab{ + width: 100%; + .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); + + .small-font{ + font-size: 12px; + } + } + + .list-item{ + padding: 10px 0px 10px 0px; + margin: 0px 20px 0px 20px; + &:not(.last){ + border-bottom: 1px solid rgba(0, 159, 219, 0.6); + } + } + + .artifact-data{ + .selectable; + .f-type._12_r; + .f-color.m; + .artifact-name { + .f-type._14_r; + font-weight: bold; + } + } + + .property-data{ + .property-name{ + width: 100%; + .f-type._14_m; + font-weight: 400; + color: @main_color_a; + } + .property-info{ + color: @func_color_s; + .f-type._14_r; + width: 100%; + } + } + + .module-text-overflow { + max-width: 240px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + //display: inline-block; + } + } + + .hierarchy-modules-list{ + .expand-collapse-title{ + .expand-collapse-title-text{ + max-width: 202px; + } + } + } + + .hierarchy-module-member-list { + overflow: hidden; + background-color: @main_color_p; + } + + .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/src/app/view-models/tutorial-end/tutorial-end.html b/catalog-ui/src/app/view-models/tutorial-end/tutorial-end.html new file mode 100644 index 0000000000..6e478fc471 --- /dev/null +++ b/catalog-ui/src/app/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/src/app/view-models/tutorial-end/tutorial-end.less b/catalog-ui/src/app/view-models/tutorial-end/tutorial-end.less new file mode 100644 index 0000000000..30fa4f7e8b --- /dev/null +++ b/catalog-ui/src/app/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('/assets/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/src/app/view-models/tutorial-end/tutorial-end.ts b/catalog-ui/src/app/view-models/tutorial-end/tutorial-end.ts new file mode 100644 index 0000000000..1787cd5937 --- /dev/null +++ b/catalog-ui/src/app/view-models/tutorial-end/tutorial-end.ts @@ -0,0 +1,20 @@ +'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/src/app/view-models/welcome/welcome-view.html b/catalog-ui/src/app/view-models/welcome/welcome-view.html new file mode 100644 index 0000000000..18ca4d51cd --- /dev/null +++ b/catalog-ui/src/app/view-models/welcome/welcome-view.html @@ -0,0 +1,9 @@ +<div class="sdc-welcome-new-page"> + <div data-ng-click="onCloseButtonClick()" class="sdc-welcome-close"></div> + <div class="sdc-welcome-wrapper"> + <div class="sdc-welcome-cover"></div> + <div class="sdc-welcome-main"> + <h1>Welcome to SDC</h1> + </div> + </div> +</div> diff --git a/catalog-ui/src/app/view-models/welcome/welcome-view.ts b/catalog-ui/src/app/view-models/welcome/welcome-view.ts new file mode 100644 index 0000000000..154a70c301 --- /dev/null +++ b/catalog-ui/src/app/view-models/welcome/welcome-view.ts @@ -0,0 +1,57 @@ +'use strict'; + +export interface IWelcomeViewMode { + onCloseButtonClick():void; +} + +export class WelcomeViewModel { + + firstLoad:boolean = true; + alreadyAnimated:Array<number> = []; + + static '$inject' = [ + '$scope', + '$state' + ]; + + constructor(private $scope:IWelcomeViewMode, + private $state:ng.ui.IStateService + ) { + this.init(); + this.initScope(); + window.setTimeout(():void => { + this.loadImages(():void=> { + window.setTimeout(():void =>{ + $(".sdc-welcome-new-page").addClass("animated fadeIn"); + },1000); + }); + },0); + } + + private initScope = ():void => { + this.$scope.onCloseButtonClick = ():void => { + this.$state.go("dashboard", {}); + }; + }; + + private init = ():void => { + let viewModelsHtmlBasePath:string = 'src/app/view-models/'; + $('body').keyup((e):void=> { + if (e.keyCode == 27) { // escape key maps to keycode `27` + this.$state.go('dashboard'); + } + }); + }; + + private loadImages = (callback:Function):void => { + let src = $('.sdc-welcome-wrapper').css('background-image'); + let url = src.match(/\((.*?)\)/)[1].replace(/('|")/g,''); + + let img = new Image(); + img.onload = function() { + callback(); + }; + img.src = url; + }; + +} diff --git a/catalog-ui/src/app/view-models/workspace/conformance-level-modal/conformance-level-modal-view-model.ts b/catalog-ui/src/app/view-models/workspace/conformance-level-modal/conformance-level-modal-view-model.ts new file mode 100644 index 0000000000..61a83c88f7 --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/conformance-level-modal/conformance-level-modal-view-model.ts @@ -0,0 +1,29 @@ +'use strict'; + +export interface IConformanceLevelModalModelScope { + footerButtons:Array<any>; + modalInstance:ng.ui.bootstrap.IModalServiceInstance; +} + +export class ConformanceLevelModalViewModel { + + static '$inject' = ['$scope', '$uibModalInstance']; + + constructor(private $scope:IConformanceLevelModalModelScope, + private $uibModalInstance:ng.ui.bootstrap.IModalServiceInstance) { + + this.initScope(); + } + + private initScope = ():void => { + + this.$scope.modalInstance = this.$uibModalInstance; + + this.$scope.footerButtons = [ + {'name': 'Continue', 'css': 'grey', 'callback': this.$uibModalInstance.close}, + {'name': 'Reject', 'css': 'blue', 'callback': this.$uibModalInstance.dismiss} + ]; + + }; + +} diff --git a/catalog-ui/src/app/view-models/workspace/conformance-level-modal/conformance-level-modal-view.html b/catalog-ui/src/app/view-models/workspace/conformance-level-modal/conformance-level-modal-view.html new file mode 100644 index 0000000000..3577e4d77b --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/conformance-level-modal/conformance-level-modal-view.html @@ -0,0 +1,22 @@ +<sdc-modal modal="modalInstance" + type="classic" + class="w-sdc-modal modal-type-alert conformance-level-modal" + header="Warning" + buttons="footerButtons" + show-close-button="false"> + + <perfect-scrollbar include-padding="true"> + <div class="w-sdc-modal-body-content" data-tests-id="message"> + <p> + You are about to distribute a service with models and artifacts created with an <b>older version of the platform</b>. + For such service, new properties, metadata and requirements needed by ECOMP components will not be available. + </p><p> + It is highly recommended that you upgrade the service models and artifacts. + </p><p> + Click "Continue" if you need to distribute the current service version.<br /> + Click "Reject" if you need to stop the distribution and manually upgrade the service. + </p> + </div> + </perfect-scrollbar> + +</sdc-modal> diff --git a/catalog-ui/src/app/view-models/workspace/conformance-level-modal/conformance-level-modal.less b/catalog-ui/src/app/view-models/workspace/conformance-level-modal/conformance-level-modal.less new file mode 100644 index 0000000000..7f195ade83 --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/conformance-level-modal/conformance-level-modal.less @@ -0,0 +1,3 @@ +.conformance-level-modal{ + +} diff --git a/catalog-ui/src/app/view-models/workspace/tabs/activity-log/activity-log.html b/catalog-ui/src/app/view-models/workspace/tabs/activity-log/activity-log.html new file mode 100644 index 0000000000..23c08f6ec6 --- /dev/null +++ b/catalog-ui/src/app/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/src/app/view-models/workspace/tabs/activity-log/activity-log.less b/catalog-ui/src/app/view-models/workspace/tabs/activity-log/activity-log.less new file mode 100644 index 0000000000..61bb3e9f01 --- /dev/null +++ b/catalog-ui/src/app/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/src/app/view-models/workspace/tabs/activity-log/activity-log.ts b/catalog-ui/src/app/view-models/workspace/tabs/activity-log/activity-log.ts new file mode 100644 index 0000000000..ed583dc4c0 --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/activity-log/activity-log.ts @@ -0,0 +1,103 @@ +'use strict'; +import {IWorkspaceViewModelScope} from "app/view-models/workspace/workspace-view-model"; +import {Activity} from "app/models"; +import {ActivityLogService} from "app/services"; + +export interface IActivityLogViewModelScope extends IWorkspaceViewModelScope { + activityDateArray:Array<any>; //this is in order to sort the dates + activityLog:Array<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: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<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/src/app/view-models/workspace/tabs/attributes/attributes-view-model.ts b/catalog-ui/src/app/view-models/workspace/tabs/attributes/attributes-view-model.ts new file mode 100644 index 0000000000..d8a60444be --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/attributes/attributes-view-model.ts @@ -0,0 +1,80 @@ +'use strict'; +import {IWorkspaceViewModelScope} from "app/view-models/workspace/workspace-view-model"; +import {Component, AttributeModel} from "app/models"; +import {ModalsHandler} from "app/utils"; +import {ComponentServiceNg2} from "../../../../ng2/services/component-services/component.service"; +import {ComponentGenericResponse} from "../../../../ng2/services/responses/component-generic-response"; + +interface IAttributesViewModelScope extends IWorkspaceViewModelScope { + tableHeadersList:Array<any>; + reverse:boolean; + sortBy:string; + + addOrUpdateAttribute(attribute?:AttributeModel):void; + delete(attribute:AttributeModel):void; + sort(sortBy:string):void; +} + +export class AttributesViewModel { + + static '$inject' = [ + '$scope', + '$filter', + '$uibModal', + 'ModalsHandler', + 'ComponentServiceNg2' + ]; + + + constructor(private $scope:IAttributesViewModelScope, + private $filter:ng.IFilterService, + private $uibModal:ng.ui.bootstrap.IModalService, + private ModalsHandler:ModalsHandler, + private ComponentServiceNg2: ComponentServiceNg2) { + + this.initComponentAttributes(); + this.$scope.updateSelectedMenuItem(); + } + + private initComponentAttributes = () => { + if(this.$scope.component.attributes) { + this.initScope(); + } else { + this.ComponentServiceNg2.getComponentAttributes(this.$scope.component).subscribe((response:ComponentGenericResponse) => { + this.$scope.component.attributes = response.attributes; + this.initScope(); + }); + } + } + + + private initScope = ():void => { + + 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?:AttributeModel):void => { + this.ModalsHandler.openEditAttributeModal(attribute ? attribute : new AttributeModel(), this.$scope.component); + }; + + this.$scope.delete = (attribute: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/src/app/view-models/workspace/tabs/attributes/attributes-view.html b/catalog-ui/src/app/view-models/workspace/tabs/attributes/attributes-view.html new file mode 100644 index 0000000000..59ba933a0a --- /dev/null +++ b/catalog-ui/src/app/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/src/app/view-models/workspace/tabs/attributes/attributes.less b/catalog-ui/src/app/view-models/workspace/tabs/attributes/attributes.less new file mode 100644 index 0000000000..ffd28afce4 --- /dev/null +++ b/catalog-ui/src/app/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/src/app/view-models/workspace/tabs/composition/composition-view-model.ts b/catalog-ui/src/app/view-models/workspace/tabs/composition/composition-view-model.ts new file mode 100644 index 0000000000..e2d95280c8 --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/composition/composition-view-model.ts @@ -0,0 +1,242 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ +'use strict'; +import {Component, Product, ComponentInstance, IAppMenu} from "app/models"; +import {SharingService, CacheService, EventListenerService, LeftPaletteLoaderService} from "app/services"; +import {ModalsHandler, GRAPH_EVENTS, ComponentFactory, ChangeLifecycleStateHandler, MenuHandler} from "app/utils"; +import {IWorkspaceViewModelScope} from "../../workspace-view-model"; +import {ComponentServiceNg2} from "app/ng2/services/component-services/component.service"; +import {ComponentGenericResponse} from "app/ng2/services/responses/component-generic-response"; + +export interface ICompositionViewModelScope extends IWorkspaceViewModelScope { + + currentComponent:Component; + selectedComponent:Component; + isLoading:boolean; + graphApi:any; + sharingService:SharingService; + sdcMenu:IAppMenu; + version:string; + isViewOnly:boolean; + isLoadingRightPanel:boolean; + onComponentInstanceVersionChange(component:Component); + isComponentInstanceSelected():boolean; + updateSelectedComponent():void + openUpdateModal(); + deleteSelectedComponentInstance():void; + onBackgroundClick():void; + setSelectedInstance(componentInstance:ComponentInstance):void; + printScreen():void; + + cacheComponentsInstancesFullData:Component; +} + +export class CompositionViewModel { + + static '$inject' = [ + '$scope', + '$log', + 'sdcMenu', + 'MenuHandler', + '$uibModal', + '$state', + 'Sdc.Services.SharingService', + '$filter', + 'Sdc.Services.CacheService', + 'ComponentFactory', + 'ChangeLifecycleStateHandler', + 'LeftPaletteLoaderService', + 'ModalsHandler', + 'EventListenerService', + 'ComponentServiceNg2' + ]; + + constructor(private $scope:ICompositionViewModelScope, + private $log:ng.ILogService, + private sdcMenu:IAppMenu, + private MenuHandler:MenuHandler, + private $uibModal:ng.ui.bootstrap.IModalService, + private $state:ng.ui.IStateService, + private sharingService:SharingService, + private $filter:ng.IFilterService, + private cacheService:CacheService, + private ComponentFactory:ComponentFactory, + private ChangeLifecycleStateHandler:ChangeLifecycleStateHandler, + private LeftPaletteLoaderService:LeftPaletteLoaderService, + private ModalsHandler:ModalsHandler, + private eventListenerService:EventListenerService, + private ComponentServiceNg2: ComponentServiceNg2) { + + this.$scope.setValidState(true); + this.initScope(); + this.initGraphData(); + this.$scope.updateSelectedMenuItem(); + this.registerGraphEvents(this.$scope); + } + + + private initGraphData = ():void => { + if(!this.$scope.component.componentInstances || !this.$scope.component.componentInstancesRelations ) { + this.$scope.isLoading = true; + this.ComponentServiceNg2.getComponentInstancesAndRelation(this.$scope.component).subscribe((response:ComponentGenericResponse) => { + this.$scope.component.componentInstances = response.componentInstances; + this.$scope.component.componentInstancesRelations = response.componentInstancesRelations; + this.$scope.isLoading = false; + this.initComponent(); + this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_COMPOSITION_GRAPH_DATA_LOADED); + }); + } else { + this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_COMPOSITION_GRAPH_DATA_LOADED); + } + this.eventListenerService.unRegisterObserver(GRAPH_EVENTS.ON_COMPOSITION_GRAPH_DATA_LOADED); + }; + + + private cacheComponentsInstancesFullData:Array<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(GRAPH_EVENTS.ON_NODE_SELECTED, scope.setSelectedInstance); + this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_GRAPH_BACKGROUND_CLICKED, scope.onBackgroundClick); + + }; + + private openUpdateComponentInstanceNameModal = ():void => { + this.ModalsHandler.openUpdateComponentInstanceNameModal(this.$scope.currentComponent).then(()=> { + this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_COMPONENT_INSTANCE_NAME_CHANGED, this.$scope.currentComponent.selectedInstance); + + }); + }; + + private removeSelectedComponentInstance = ():void => { + this.eventListenerService.notifyObservers(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<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:Component) => { + this.$scope.isLoadingRightPanel = false; + this.$scope.selectedComponent = component; + this.cacheComponentsInstancesFullData.push(component); + }; + let onError = (component: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: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.onComponentInstanceVersionChange = (component:Product):void => { + this.$scope.currentComponent = component; + this.$scope.setComponent(this.$scope.currentComponent); + this.$scope.updateSelectedComponent(); + } + + } +} diff --git a/catalog-ui/src/app/view-models/workspace/tabs/composition/composition-view.html b/catalog-ui/src/app/view-models/workspace/tabs/composition/composition-view.html new file mode 100644 index 0000000000..761ae53909 --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/composition/composition-view.html @@ -0,0 +1,95 @@ +<div class="workspace-composition"> + <loader data-display="isLoading"></loader> + <div class="w-sdc-designer-canvas" data-ng-class="{sidebaractive: displayDesignerRightSidebar}"> + <palette current-component="currentComponent" + is-view-only="isViewOnly" + is-loading="isLoading"></palette> + + <composition-graph component="currentComponent" data-tests-id="canvas" + is-view-only="isViewOnly"></composition-graph> + </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" + data-tests-id="information-tab"> + <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/src/app/view-models/workspace/tabs/composition/composition.less b/catalog-ui/src/app/view-models/workspace/tabs/composition/composition.less new file mode 100644 index 0000000000..501805be3f --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/composition/composition.less @@ -0,0 +1,873 @@ + +.composition{ + .sdc-workspace-container{ + .w-sdc-main-container{ + .w-sdc-main-right-container{ + left:0; + //overflow-y: scroll; + .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: @footer_height; + 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: 60px; + .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: 0; + width: 100%; + padding-bottom: 10px; + + } + + .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; + } + + &.update-env { + background-color: transparent; + border: 0; + right: 15px; + 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/src/app/view-models/workspace/tabs/composition/tabs/artifacts/artifacts-view-model.ts b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/artifacts/artifacts-view-model.ts new file mode 100644 index 0000000000..0ac5fd0799 --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/artifacts/artifacts-view-model.ts @@ -0,0 +1,301 @@ +'use strict'; +import { + ArtifactModel, + Service, + IAppConfigurtaion, + Resource, + Component, + ComponentInstance, + ArtifactGroupModel, + IFileDownload +} from "app/models"; +import {ICompositionViewModelScope} from "../../composition-view-model"; +import {ArtifactsUtils, ModalsHandler, ArtifactGroupType} from "app/utils"; +import {GRAPH_EVENTS} from "app/utils/constants"; +import {EventListenerService} from "app/services/event-listener-service"; + +export interface IArtifactsViewModelScope extends ICompositionViewModelScope { + artifacts:Array<ArtifactModel>; + artifactType:string; + downloadFile:IFileDownload; + isLoading:boolean; + + getTitle():string; + addOrUpdate(artifact:ArtifactModel):void; + delete(artifact:ArtifactModel):void; + download(artifact:ArtifactModel):void; + openEditEnvParametersModal(artifact:ArtifactModel):void; + getEnvArtifact(heatArtifact:ArtifactModel):any; + getEnvArtifactName(artifact:ArtifactModel):string; + isLicenseArtifact(artifact:ArtifactModel):boolean; + isVFiArtifact(artifact:ArtifactModel):boolean; +} + +export class ResourceArtifactsViewModel { + + static '$inject' = [ + '$scope', + '$filter', + '$state', + 'sdcConfig', + 'ArtifactsUtils', + 'ModalsHandler', + '$q', + 'EventListenerService' + ]; + + constructor(private $scope:IArtifactsViewModelScope, + private $filter:ng.IFilterService, + private $state:any, + private sdcConfig:IAppConfigurtaion, + private artifactsUtils:ArtifactsUtils, + private ModalsHandler:ModalsHandler, + private $q:ng.IQService, + private eventListenerService: EventListenerService) { + + this.initScope(); + } + + + private initArtifactArr = (artifactType:string):void => { + let artifacts:Array<ArtifactModel> = []; + + if (this.$scope.selectedComponent) { + if ('interface' == artifactType) { + let interfaces = this.$scope.currentComponent.interfaces; + if (interfaces && interfaces.standard && interfaces.standard.operations) { + + angular.forEach(interfaces.standard.operations, (operation:any, interfaceName:string):void => { + let item:ArtifactModel = <ArtifactModel>{}; + if (operation.implementation) { + item = <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:ArtifactGroupModel; + switch (artifactType) { + case "api": + artifactsObj = (<Service>this.$scope.currentComponent).serviceApiArtifacts; + break; + case "deployment": + if (!this.$scope.isComponentInstanceSelected()) { + artifactsObj = this.$scope.currentComponent.deploymentArtifacts; + } else { + artifactsObj = this.$scope.currentComponent.selectedInstance.deploymentArtifacts; + } + break; + default: + //artifactsObj = this.$scope.selectedComponent.artifacts; + if (!this.$scope.isComponentInstanceSelected()) { + artifactsObj = this.$scope.currentComponent.artifacts; + } else { + artifactsObj = this.$scope.currentComponent.selectedInstance.artifacts; + } + break; + } + _.forEach(artifactsObj, (artifact:ArtifactModel, key) => { + artifacts.push(artifact); + }); + } + } + this.$scope.artifacts = artifacts; + }; + + + private convertToArtifactUrl = (artifactType:string):string => { + + switch (artifactType) { + case 'deployment': + return 'DEPLOYMENT'; + case 'api': + return 'SERVICE_API'; + default: + return 'INFORMATIONAL'; + } + + } + + private loadComponentArtifactIfNeeded = (forceLoad?: boolean) => { + + let onGetComponentArtifactsSuccess = (artifacts:ArtifactGroupModel)=> { + switch (this.$scope.artifactType) { + case 'deployment': + this.$scope.currentComponent.deploymentArtifacts = artifacts; + break; + case 'api': + (<Service>this.$scope.currentComponent).serviceApiArtifacts = artifacts; + break; + default: + this.$scope.currentComponent.artifacts = artifacts; + break; + } + this.$scope.isLoading = false; + this.initArtifactArr(this.$scope.artifactType); + } + + let onError = ()=> { + this.$scope.isLoading = false; + }; + + switch (this.$scope.artifactType) { + case 'deployment': + if(forceLoad || !this.$scope.currentComponent.deploymentArtifacts) { + this.$scope.component.getArtifactByGroupType(this.convertToArtifactUrl(this.$scope.artifactType)).then(onGetComponentArtifactsSuccess, onError); + } else { + this.initArtifactArr(this.$scope.artifactType); + } + + break; + case 'api': + if(!(<Service>this.$scope.currentComponent).serviceApiArtifacts) { + this.$scope.component.getArtifactByGroupType(this.convertToArtifactUrl(this.$scope.artifactType)).then(onGetComponentArtifactsSuccess, onError); + } else { + this.initArtifactArr(this.$scope.artifactType); + } + break; + default: + if(!this.$scope.currentComponent.artifacts) { + this.$scope.component.getArtifactByGroupType(this.convertToArtifactUrl(this.$scope.artifactType)).then(onGetComponentArtifactsSuccess, onError); + } else { + this.initArtifactArr(this.$scope.artifactType); + } + break; + } + } + private loadArtifacts = (forceLoad?: boolean):void => { + + let onGetInstanceArtifactsSuccess = (artifacts:ArtifactGroupModel)=> { + switch (this.$scope.artifactType) { + case 'deployment': + this.$scope.currentComponent.selectedInstance.deploymentArtifacts = artifacts; + break; + default: + this.$scope.currentComponent.selectedInstance.artifacts = artifacts; + break; + } + this.loadComponentArtifactIfNeeded(); + }; + + let onError = ()=> { + this.$scope.isLoading = false; + }; + + this.$scope.isLoading = true; + if (this.$scope.isComponentInstanceSelected()) { + this.$scope.component.getComponentInstanceArtifactsByGroupType(this.$scope.component.selectedInstance.uniqueId, this.convertToArtifactUrl(this.$scope.artifactType)).then(onGetInstanceArtifactsSuccess, onError); + } else { + this.loadComponentArtifactIfNeeded(forceLoad); + } + } + + private updateArtifactsIfNeeded = ():void => { + if (this.$scope.artifactType === "deployment") { + this.loadArtifacts(true); + } else { + this.initArtifactArr(this.$scope.artifactType); + } + }; + + private openEditArtifactModal = (artifact:ArtifactModel):void => { + this.ModalsHandler.openArtifactModal(artifact, this.$scope.currentComponent).then(():void => { + this.updateArtifactsIfNeeded(); + }); + }; + + private initScope = ():void => { + + this.$scope.isLoading = false; + this.$scope.artifactType = this.artifactsUtils.getArtifactTypeByState(this.$state.current.name); + this.loadArtifacts(); + this.$scope.getTitle = ():string => { + return this.artifactsUtils.getTitle(this.$scope.artifactType, this.$scope.currentComponent); + }; + + this.$scope.isVFiArtifact = (artifact:ArtifactModel):boolean=> { + if (artifact.artifactGroupType === ArtifactGroupType.INFORMATION) {//fix DE256847 + return this.$scope.currentComponent.artifacts && (!this.$scope.currentComponent.artifacts[artifact.artifactLabel] || !this.$scope.currentComponent.artifacts[artifact.artifactLabel].artifactName); + } + return this.$scope.currentComponent.deploymentArtifacts && (!this.$scope.currentComponent.deploymentArtifacts[artifact.artifactLabel]);//fix DE251314 + }; + + this.$scope.addOrUpdate = (artifact:ArtifactModel):void => { + this.artifactsUtils.setArtifactType(artifact, this.$scope.artifactType); + let artifactCopy = new ArtifactModel(artifact); + this.openEditArtifactModal(artifactCopy); + }; + + + this.$scope.delete = (artifact:ArtifactModel):void => { + + let onOk = ():void => { + this.$scope.isLoading = true; + this.artifactsUtils.removeArtifact(artifact, this.$scope.artifacts); + + let success = (responseArtifact: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:ArtifactModel):any=> { + return _.find(this.$scope.artifacts, (item:ArtifactModel)=> { + return item.generatedFromId === heatArtifact.uniqueId; + }); + }; + + this.$scope.getEnvArtifactName = (artifact:ArtifactModel):string => { + let envArtifact = this.$scope.getEnvArtifact(artifact); + if (envArtifact) { + return envArtifact.artifactDisplayName; + } + }; + + this.$scope.isLicenseArtifact = (artifact: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:ArtifactModel):void => { + this.ModalsHandler.openEditEnvParametersModal(artifact, this.$scope.currentComponent).then(()=> { + this.updateArtifactsIfNeeded(); + }, ()=> { + // ERROR + }); + }; + + this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_NODE_SELECTED, this.loadArtifacts); + this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_GRAPH_BACKGROUND_CLICKED, this.loadArtifacts); + + this.$scope.$on('$destroy', () => { + + this.eventListenerService.unRegisterObserver(GRAPH_EVENTS.ON_NODE_SELECTED, this.loadArtifacts); + this.eventListenerService.unRegisterObserver(GRAPH_EVENTS.ON_GRAPH_BACKGROUND_CLICKED, this.loadArtifacts); + }); + } +} diff --git a/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/artifacts/artifacts-view.html b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/artifacts/artifacts-view.html new file mode 100644 index 0000000000..b0d81b3437 --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/artifacts/artifacts-view.html @@ -0,0 +1,67 @@ +<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"> + <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" + data-tests-id="artifact-item-{{artifact.artifactDisplayName}}"> + <span data-ng-if="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-{{artifact.artifactDisplayName}}" + data-ng-bind="artifact.artifactName" tooltips tooltip-content="{{artifact.artifactName}}" + 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': (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="artifact.heatParameters.length"> + <span data-ng-bind="getEnvArtifactName(artifact)"data-tests-id="heat_env_{{artifact.artifactDisplayName}}"></span> + <button class="i-sdc-designer-sidebar-section-content-item-button update-env sprite e-sdc-small-icon-pencil" data-tests-id="edit_{{artifact.artifactDisplayName}}" + data-ng-if="!isViewMode()" data-ng-click="addOrUpdate(getEnvArtifact(artifact))"></button> + <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="isComponentInstanceSelected()" + data-tests-id="download_env_{{artifact.artifactDisplayName}}"></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_{{artifact.artifactDisplayName}}" data-ng-click="delete(artifact)" type="button"></button> + <button ng-if="!isViewMode() && artifact.isHEAT() && isComponentInstanceSelected() && 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" + data-tests-id="edit-parameters-of-{{artifact.artifactDisplayName}}"></button> + <!--need to remove this button --> + <button ng-if="!isViewMode() && artifact.isHEAT() && !isComponentInstanceSelected() && artifact.heatParameters.length" + class="i-sdc-designer-sidebar-section-content-item-button attach sprite e-sdc-small-icon-pad" + data-ng-click="openEditEnvParametersModal(artifact)" type="button" + data-tests-id="edit-parameters-of-{{artifact.artifactDisplayName}}"></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="currentComponent" data-tests-id="download-{{artifact.artifactDisplayName}}" instance="isComponentInstanceSelected()"></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_{{artifact.artifactDisplayName}}" + show-loader="artifact.isHEAT()" + download-icon-class="i-sdc-designer-sidebar-section-content-item-button download sprite e-sdc-small-download hand"></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/src/app/view-models/workspace/tabs/composition/tabs/artifacts/artifacts.less b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/artifacts/artifacts.less new file mode 100644 index 0000000000..7c8b8315d9 --- /dev/null +++ b/catalog-ui/src/app/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/src/app/view-models/workspace/tabs/composition/tabs/details/details-view-model.ts b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/details/details-view-model.ts new file mode 100644 index 0000000000..a81bb9176e --- /dev/null +++ b/catalog-ui/src/app/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========================================================= + */ + +'use strict'; +import {Component} from "app/models"; +import {GRAPH_EVENTS} from "app/utils"; +import {LeftPaletteLoaderService, EventListenerService} from "app/services"; +import {ICompositionViewModelScope} from "../../composition-view-model"; +import {LeftPaletteComponent} from "../../../../../../models/components/displayComponent"; + +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:LeftPaletteLoaderService, + private eventListenerService: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)); + + if (parseFloat(highestVersion) % 1) { //if highest is minor, make sure it is the latest checked in - + let latestVersionComponent:LeftPaletteComponent = _.find(this.LeftPaletteLoaderService.getLeftPanelComponentsForDisplay(this.$scope.currentComponent.componentType), (component:LeftPaletteComponent) => { //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:Component) => { + if (this.$scope.isComponentInstanceSelected()) { + this.initEditResourceVersion(); + } + }); + + this.$scope.changeResourceVersion = ():void => { + this.$scope.isLoading = true; + this.$scope.$parent.isLoading = true; + + let onSuccess = (component:Component)=> { + this.$scope.isLoading = false; + this.$scope.$parent.isLoading = false; + this.$scope.onComponentInstanceVersionChange(component); + + this.eventListenerService.notifyObservers(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/src/app/view-models/workspace/tabs/composition/tabs/details/details-view.html b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/details/details-view.html new file mode 100644 index 0000000000..70dc58075a --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/details/details-view.html @@ -0,0 +1,136 @@ +<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-tests-id="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_contactId"></span> + </div> + <div class="i-sdc-designer-sidebar-section-content-item" data-ng-if="isViewMode() && currentComponent.isService() && selectedComponent.isResource()"> + <span class="i-sdc-designer-sidebar-section-content-item-label">Resource Customization UUID:</span><br> + <span class="customization-uuid selectable" data-ng-bind="currentComponent.selectedInstance.customizationUUID" + data-tests-id="rightTab_customizationModuleUUID"></span><br> + </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-if="selectedComponent.tags.indexOf(selectedComponent.name)===-1" data-ng-bind="selectedComponent.name" + data-tests-id="rightTab_tag" tooltips tooltip-content="{{selectedComponent.name}}"></span> + <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/src/app/view-models/workspace/tabs/composition/tabs/details/details.less b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/details/details.less new file mode 100644 index 0000000000..841ab3aa49 --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/details/details.less @@ -0,0 +1,72 @@ +.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; + } + + .customization-uuid{ + .f-type._12_m; + } + + .w-sdc-designer-sidebar-section.tags { + .i-sdc-designer-sidebar-section-content-item { + white-space: normal; + } + } + +} diff --git a/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/properties-and-attributes/properties-view-model.ts b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/properties-and-attributes/properties-view-model.ts new file mode 100644 index 0000000000..84769d7a62 --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/properties-and-attributes/properties-view-model.ts @@ -0,0 +1,217 @@ +'use strict'; +import { + AttributeModel, + AttributesGroup, + Component, + ComponentInstance, + PropertyModel, + PropertiesGroup +} from "app/models"; +import {ICompositionViewModelScope} from "../../composition-view-model"; +import {ModalsHandler} from "app/utils"; +import {ComponentServiceNg2} from "app/ng2/services/component-services/component.service"; +import {ComponentGenericResponse} from "app/ng2/services/responses/component-generic-response"; + +interface IResourcePropertiesAndAttributesViewModelScope extends ICompositionViewModelScope { + properties:PropertiesGroup; + attributes:AttributesGroup; + propertiesMessage:string; + groupPropertiesByInstance:boolean; + showGroupsOfInstanceProperties:Array<boolean>; + addProperty():void; + updateProperty(property:PropertyModel):void; + deleteProperty(property:PropertyModel):void; + viewAttribute(attribute:AttributeModel):void; + groupNameByKey(key:string):string; + isPropertyOwner():boolean; + getComponentInstanceNameFromInstanceByKey(key:string):string; +} + +export class ResourcePropertiesViewModel { + + static '$inject' = [ + '$scope', + '$filter', + '$uibModal', + 'ModalsHandler', + 'ComponentServiceNg2' + + ]; + + + constructor(private $scope:IResourcePropertiesAndAttributesViewModelScope, + private $filter:ng.IFilterService, + private $uibModal:ng.ui.bootstrap.IModalService, + private ModalsHandler:ModalsHandler, + private ComponentServiceNg2:ComponentServiceNg2) { + + this.getComponentInstancesPropertiesAndAttributes(); + } + + private initComponentProperties = ():void => { + let result:PropertiesGroup = {}; + + if (this.$scope.selectedComponent) { + this.$scope.propertiesMessage = undefined; + this.$scope.groupPropertiesByInstance = false; + if (this.$scope.isComponentInstanceSelected()) { + if (this.$scope.currentComponent.selectedInstance.originType === 'VF') { + this.$scope.groupPropertiesByInstance = true; + } + 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<PropertyModel>(); + let derived = Array<PropertyModel>(); + _.forEach(this.$scope.selectedComponent.properties, (property: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: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; + } + }; + + /** + * This function is checking if the component is the value owner of the current property + * in order to notify the edit property modal which fields to disable + */ + private isPropertyValueOwner = ():boolean => { + return this.$scope.currentComponent.isService() || !!this.$scope.currentComponent.selectedInstance; + }; + + /** + * The function opens the edit property modal. + * It checks if the property is from the VF or from one of it's resource instances and sends the needed property list. + * For create property reasons an empty array is transferd + * + * @param property the wanted property to edit/create + */ + private openEditPropertyModal = (property:PropertyModel):void => { + this.ModalsHandler.openEditPropertyModal(property, + this.$scope.component, + (this.$scope.isPropertyOwner() ? + this.$scope.properties[property.parentUniqueId] : + this.$scope.properties[property.resourceInstanceUniqueId]) || [], + this.isPropertyValueOwner()).then(() => { + }); + }; + + private openAttributeModal = (atrribute:AttributeModel):void => { + + let modalOptions:ng.ui.bootstrap.IModalSettings = { + template: 'app/view-models/forms/attribute-form/attribute-form-view.html', + controller: 'Sdc.ViewModels.AttributeFormViewModel', + size: 'sdc-md', + backdrop: 'static', + keyboard: false, + resolve: { + attribute: ():AttributeModel => { + return atrribute; + }, + component: ():Component => { + return this.$scope.currentComponent; + } + } + }; + this.$uibModal.open(modalOptions); + }; + + private getComponentInstancesPropertiesAndAttributes = () => { + + this.ComponentServiceNg2.getComponentInstanceAttributesAndProperties(this.$scope.currentComponent).subscribe((genericResponse:ComponentGenericResponse) => { + this.$scope.currentComponent.componentInstancesAttributes = genericResponse.componentInstancesAttributes; + this.$scope.currentComponent.componentInstancesProperties = genericResponse.componentInstancesProperties; + this.initScope(); + }); + }; + + private initScope = ():void => { + + + this.initComponentProperties(); + this.initComponentAttributes(); + + this.$scope.$watchCollection('currentComponent.properties', (newData:any):void => { + this.initComponentProperties(); + }); + + this.$scope.$watch('currentComponent.selectedInstance', (newInstance:ComponentInstance):void => { + if (angular.isDefined(newInstance)) { + this.initComponentProperties(); + this.initComponentAttributes(); + + } + }); + + this.$scope.isPropertyOwner = ():boolean => { + return this.$scope.currentComponent && this.$scope.currentComponent.isResource() && !this.$scope.isComponentInstanceSelected(); + }; + + this.$scope.updateProperty = (property:PropertyModel):void => { + this.openEditPropertyModal(property); + }; + + this.$scope.deleteProperty = (property: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: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); + } + }; + + this.$scope.getComponentInstanceNameFromInstanceByKey = (key:string):string => { + let instanceName:string = ""; + if (key !== undefined && this.$scope.selectedComponent.uniqueId == this.$scope.currentComponent.selectedInstance.componentUid) { + instanceName = this.$filter("resourceName")((_.find(this.$scope.selectedComponent.componentInstances, {uniqueId: key})).name); + } + return instanceName; + }; + + } +} diff --git a/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/properties-and-attributes/properties-view.html b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/properties-and-attributes/properties-view.html new file mode 100644 index 0000000000..6df8b6a4d6 --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/properties-and-attributes/properties-view.html @@ -0,0 +1,117 @@ +<perfect-scrollbar class="w-sdc-designer-sidebar-tab-content properties" id="main-scroll"> + + <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}}"> + <div class="first-level"> + <div class="expand-collapse-title-icon"></div> + <span class="w-sdc-designer-sidebar-section-title-text" data-ng-bind="groupNameByKey(key) + ' Properties'" + tooltips tooltip-content="{{groupNameByKey(key)}} Properties" + data-tests-id="vfi-properties"></span> + </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" data-ng-if="!groupPropertiesByInstance"> + <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)" + data-tests-id="{{property.name}}">{{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}}" + data-tests-id="value_{{property.name}}">{{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 class="i-sdc-designer-sidebar-section-content-item" data-ng-if="groupPropertiesByInstance"> + <div data-ng-repeat-start="(instancesIds , InstanceProperties) in (group | groupBy:'path')" + class="vfci-properties-group" + data-ng-click="showGroupsOfInstanceProperties[$index]=!showGroupsOfInstanceProperties[$index]" + data-ng-class="{'expanded':showGroupsOfInstanceProperties[$index]}"> + <div class="second-level"> + <div class="expand-collapse-title-icon"></div> + <span class="w-sdc-designer-sidebar-section-title-text" data-ng-bind="getComponentInstanceNameFromInstanceByKey(InstanceProperties[0].path[1]) + ' Properties'" + tooltips tooltip-content="{{getComponentInstanceNameFromInstanceByKey(InstanceProperties[0].path[1])}} Properties" + data-tests-id="vfci-properties"></span> + </div> + </div> + <div data-ng-repeat-end="" class="w-sdc-designer-sidebar-section-content instance-properties {{$index}}" data-ng-if="showGroupsOfInstanceProperties[$index]"> + <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="instanceProperty in InstanceProperties | orderBy: 'name'"> + <div> + <span class="i-sdc-designer-sidebar-section-content-item-property-and-attribute-label" + data-ng-class="{'hand enabled': !$parent.isViewOnly}" + tooltips tooltip-content="{{instanceProperty.name}}" + data-tests-id="vfci-property">{{instanceProperty.name}}</span> + </div> + <div> + <span class="i-sdc-designer-sidebar-section-content-item-property-value" + tooltips tooltip-content="{{instanceProperty.value === undefined ? instanceProperty.defaultValue : instanceProperty.value}}"> + {{instanceProperty.value === undefined ? instanceProperty.defaultValue : instanceProperty.value}}</span> + </div> + </div> + </div> + </div> + </div> + <!--<div class="w-sdc-designer-sidebar-section-footer" data-ng-if="(!$parent.isViewOnly && isPropertyOwner()) || showAddPropertyButton">--> + <!--<button class="w-sdc-designer-sidebar-section-footer-action tlv-btn blue" data-tests-id="addGrey" data-ng-click="addProperty()" type="button">--> + <!--Add Property--> + <!--</button>--> + <!--</div>--> + </div> + + + <!--attributes--> + <expand-collapse data-ng-repeat-start="(key, group) in attributes" + expanded-selector=".w-sdc-designer-sidebar-section-content.attributes.{{$index}}"> + <div class="first-level"> + <div class="expand-collapse-title-icon"></div> + <span class="w-sdc-designer-sidebar-section-title-text" data-ng-bind="groupNameByKey(key) + ' Attributes'" + tooltips tooltip-content="{{groupNameByKey(key)}} Attributes"></span> + </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/src/app/view-models/workspace/tabs/composition/tabs/properties-and-attributes/properties.less b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/properties-and-attributes/properties.less new file mode 100644 index 0000000000..41a90bff9d --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/properties-and-attributes/properties.less @@ -0,0 +1,38 @@ +.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; + } + + .vfci-properties-group{ + background-color: @func_color_r; + } + + .expand-collapse-title-icon{ + .hand; + .sprite-new; + .expand-collapse-plus-icon; + vertical-align: middle; + margin: 0 6px; + } + + .expanded { + .expand-collapse-title-icon { + .expand-collapse-minus-icon; + } + } + + .w-sdc-designer-sidebar-section-title-text{ + vertical-align: middle; + } +} diff --git a/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/relations/relations-view-model.ts b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/relations/relations-view-model.ts new file mode 100644 index 0000000000..325f250ebe --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/relations/relations-view-model.ts @@ -0,0 +1,156 @@ +'use strict'; +import {ICompositionViewModelScope} from "../../composition-view-model"; +import {CapabilitiesGroup, Requirement, RequirementsGroup} from "app/models"; +import {ComponentServiceNg2} from "app/ng2/services/component-services/component.service"; +import {ComponentGenericResponse} from "app/ng2/services/responses/component-generic-response"; +import {GRAPH_EVENTS} from "app/utils"; +import {EventListenerService} from "app/services"; +import {ComponentInstance, Capability} from "app/models"; + +interface IRelationsViewModelScope extends ICompositionViewModelScope { + isLoading:boolean; + $parent:ICompositionViewModelScope; + getRelation(requirement:any):any; + capabilities:Array<Capability>; + requirements:Array<Requirement>; + + //for complex components + capabilitiesInstancesMap:InstanceCapabilitiesMap; + requirementsInstancesMap:InstanceRequirementsMap; +} +export class InstanceCapabilitiesMap { + [key:string]:Array<Capability>; +} + +export class InstanceRequirementsMap { + [key:string]:Array<Requirement>; +} + +export class RelationsViewModel { + + static '$inject' = [ + '$scope', + '$filter', + 'ComponentServiceNg2', + 'EventListenerService' + ]; + + constructor(private $scope:IRelationsViewModelScope, + private $filter:ng.IFilterService, + private ComponentServiceNg2:ComponentServiceNg2, + private eventListenerService:EventListenerService) { + this.initScope(); + } + + private loadComplexComponentData = () => { + this.$scope.isLoading = true; + this.ComponentServiceNg2.getCapabilitiesAndRequirements(this.$scope.currentComponent.componentType, this.$scope.currentComponent.uniqueId).subscribe((response:ComponentGenericResponse) => { + this.$scope.currentComponent.capabilities = response.capabilities; + this.$scope.currentComponent.requirements = response.requirements; + this.setScopeCapabilitiesRequirements(this.$scope.currentComponent.capabilities, this.$scope.currentComponent.requirements); + this.initInstancesMap(); + this.$scope.isLoading = false; + }); + } + + + private extractValuesFromMap = (map:CapabilitiesGroup | RequirementsGroup):Array<any> => { + let values = []; + _.forEach(map, (capabilitiesOrRequirements:Array<Capability> | Array<Requirement>, key) => { + values = values.concat(capabilitiesOrRequirements) + } + ); + return values; + } + + private setScopeCapabilitiesRequirements = (capabilities:CapabilitiesGroup, requirements:RequirementsGroup) => { + this.$scope.capabilities = this.extractValuesFromMap(capabilities); + this.$scope.requirements = this.extractValuesFromMap(requirements); + } + + + private initInstancesMap = ():void => { + + this.$scope.capabilitiesInstancesMap = new InstanceCapabilitiesMap(); + _.forEach(this.$scope.capabilities, (capability:Capability) => { + if (this.$scope.capabilitiesInstancesMap[capability.ownerName]) { + this.$scope.capabilitiesInstancesMap[capability.ownerName] = this.$scope.capabilitiesInstancesMap[capability.ownerName].concat(capability); + } else { + this.$scope.capabilitiesInstancesMap[capability.ownerName] = new Array<Capability>(capability); + } + }); + + this.$scope.requirementsInstancesMap = new InstanceRequirementsMap(); + _.forEach(this.$scope.requirements, (requirement:Requirement) => { + if (this.$scope.requirementsInstancesMap[requirement.ownerName]) { + this.$scope.requirementsInstancesMap[requirement.ownerName] = this.$scope.requirementsInstancesMap[requirement.ownerName].concat(requirement); + } else { + this.$scope.requirementsInstancesMap[requirement.ownerName] = new Array<Requirement>(requirement); + } + }); + } + + private initRequirementsAndCapabilities = (needUpdate?: boolean) => { + + // if instance selected, we take the requirement and capabilities of the instance - always exist because we load them with the graph + if (this.$scope.isComponentInstanceSelected()) { + this.$scope.isLoading = false; + this.setScopeCapabilitiesRequirements(this.$scope.currentComponent.selectedInstance.capabilities, this.$scope.currentComponent.selectedInstance.requirements); + if (this.$scope.currentComponent.selectedInstance.originType === 'VF') { + this.initInstancesMap(); + } + } else { + // if instance not selected, we take the requirement and capabilities of the VF/SERVICE, if not exist we call api + if (needUpdate || !this.$scope.currentComponent.capabilities || !this.$scope.currentComponent.requirements) { + this.loadComplexComponentData(); + + } else { + this.$scope.isLoading = false; + this.setScopeCapabilitiesRequirements(this.$scope.currentComponent.capabilities, this.$scope.currentComponent.requirements); + this.initInstancesMap(); + } + } + } + + private updateRequirementCapabilities = () => { + if (!this.$scope.isComponentInstanceSelected()) { + this.loadComplexComponentData(); + } + } + + private initEvents = ():void => { + this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_NODE_SELECTED, this.initRequirementsAndCapabilities); + this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_GRAPH_BACKGROUND_CLICKED, this.updateRequirementCapabilities); + this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_CREATE_COMPONENT_INSTANCE, this.updateRequirementCapabilities); + this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_DELETE_COMPONENT_INSTANCE, this.updateRequirementCapabilities); + } + + private initScope = ():void => { + + this.$scope.requirements = []; + this.$scope.capabilities = []; + + this.initEvents(); + this.initRequirementsAndCapabilities(); + + this.$scope.isCurrentDisplayComponentIsComplex = ():boolean => { + if (this.$scope.isComponentInstanceSelected()) { + if (this.$scope.currentComponent.selectedInstance.originType === 'VF') { + return true; + } + return false; + } else { + return this.$scope.currentComponent.isComplex(); + } + } + + this.$scope.$on('$destroy', () => { + + this.eventListenerService.unRegisterObserver(GRAPH_EVENTS.ON_NODE_SELECTED, this.initRequirementsAndCapabilities); + this.eventListenerService.unRegisterObserver(GRAPH_EVENTS.ON_GRAPH_BACKGROUND_CLICKED, this.updateRequirementCapabilities); + this.eventListenerService.unRegisterObserver(GRAPH_EVENTS.ON_CREATE_COMPONENT_INSTANCE, this.updateRequirementCapabilities); + this.eventListenerService.unRegisterObserver(GRAPH_EVENTS.ON_DELETE_COMPONENT_INSTANCE, this.updateRequirementCapabilities); + }); + + } +} diff --git a/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/relations/relations-view.html b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/relations/relations-view.html new file mode 100644 index 0000000000..5ecb12cd6f --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/relations/relations-view.html @@ -0,0 +1,61 @@ +<perfect-scrollbar class="w-sdc-designer-sidebar-tab-content sdc-general-tab relations"> + <div ng-if="!isCurrentDisplayComponentIsComplex()"> + <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"> + <capabilities-list capabilities="capabilities"></capabilities-list> + </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"> + <requirements-list component='currentComponent' requirements="requirements"></requirements-list> + </div> + </div> + </div> + + <div ng-if="isCurrentDisplayComponentIsComplex()"> + <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> + <div class="w-sdc-designer-sidebar-section-content capabilities"> + <expand-collapse expanded-selector=".capabilities-component-instances.{{$index}}" is-close-on-init="true" class="general-tab-expand-collapse" + data-ng-repeat-start="(key, instanceCapabilities) in capabilitiesInstancesMap track by $index"> + <div class="expand-collapse-title second-level"> + <div class="expand-collapse-title-icon"></div> + <span class="expand-collapse-title-text" data-ng-bind="key"></span> + </div> + </expand-collapse> + + <div data-ng-repeat-end="" class="capabilities-component-instances {{$index}}"> + <capabilities-list capabilities="instanceCapabilities"></capabilities-list> + </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> + <div class="w-sdc-designer-sidebar-section-content requirements"> + <expand-collapse expanded-selector=".requirements-component-instances.{{$index}}" is-close-on-init="true" class="general-tab-expand-collapse" + data-ng-repeat-start="(key, instanceRequirements) in requirementsInstancesMap track by $index"> + <div class="expand-collapse-title second-level"> + <div class="expand-collapse-title-icon"></div> + <span class="expand-collapse-title-text" data-ng-bind="key"></span> + </div> + </expand-collapse> + + <div data-ng-repeat-end="" class="requirements-component-instances {{$index}}"> + <requirements-list component='currentComponent' requirements="instanceRequirements"></requirements-list> + </div> + </div> + </div> +</perfect-scrollbar> diff --git a/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/relations/relations.less b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/relations/relations.less new file mode 100644 index 0000000000..c3b224d5a6 --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/relations/relations.less @@ -0,0 +1,14 @@ +.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; + } + } +} + + diff --git a/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/structure/structure-view.html b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/structure/structure-view.html new file mode 100644 index 0000000000..2070041990 --- /dev/null +++ b/catalog-ui/src/app/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/src/app/view-models/workspace/tabs/composition/tabs/structure/structure-view.ts b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/structure/structure-view.ts new file mode 100644 index 0000000000..1c28a46d37 --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/composition/tabs/structure/structure-view.ts @@ -0,0 +1,14 @@ +'use strict'; +import {ICompositionViewModelScope} from "../../composition-view-model"; + +interface IStructureViewModel extends ICompositionViewModelScope { +} + +export class StructureViewModel { + static '$inject' = [ + '$scope' + ]; + + constructor(private $scope:IStructureViewModel) { + } +} diff --git a/catalog-ui/src/app/view-models/workspace/tabs/deployment-artifacts/deployment-artifacts-description-popover.html b/catalog-ui/src/app/view-models/workspace/tabs/deployment-artifacts/deployment-artifacts-description-popover.html new file mode 100644 index 0000000000..0d7d5dc8f4 --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/deployment-artifacts/deployment-artifacts-description-popover.html @@ -0,0 +1,23 @@ +<!-- Description Popover --> +<div > + <span data-tests-id='popover-x-button' data-ng-click='closeDescriptionPopover()' class='tlv-sprite tlv-x-btn close-popover-btn'></span> + <div class="w-sdc-form-item" ng-form="descriptionForm" data-ng-class="{error:(descriptionForm.$dirty && descriptionForm.$invalid)}"> + <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> +</div> diff --git a/catalog-ui/src/app/view-models/workspace/tabs/deployment-artifacts/deployment-artifacts-view-model.ts b/catalog-ui/src/app/view-models/workspace/tabs/deployment-artifacts/deployment-artifacts-view-model.ts new file mode 100644 index 0000000000..2816c312f9 --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/deployment-artifacts/deployment-artifacts-view-model.ts @@ -0,0 +1,276 @@ +//@require "./*.html" +'use strict'; +import {IWorkspaceViewModelScope} from "app/view-models/workspace/workspace-view-model"; +import {ArtifactModel, ArtifactGroupModel, Resource} from "app/models"; +import {ArtifactsUtils, ModalsHandler, ValidationUtils} from "app/utils"; +import {ComponentServiceNg2} from "app/ng2/services/component-services/component.service"; +import {ComponentGenericResponse} from "../../../../ng2/services/responses/component-generic-response"; + +interface IDeploymentArtifactsViewModelScope extends IWorkspaceViewModelScope { + tableHeadersList:Array<any>; + reverse:boolean; + sortBy:string; + artifacts:Array<ArtifactModel>; + editForm:ng.IFormController; + isLoading:boolean; + artifactDescriptions:any; + selectedArtifactId:string; + popoverTemplate:string; + + addOrUpdate(artifact:ArtifactModel):void; + updateSelectedArtifact():void; + delete(artifact: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:ArtifactModel):void; + getEnvArtifact(heatArtifact:ArtifactModel):ArtifactModel; + getEnvArtifactName(artifact:ArtifactModel):string; + openEditEnvParametersModal(artifact:ArtifactModel):void; + openDescriptionPopover(artifactId:string):void; + closeDescriptionPopover():void; +} + +export class DeploymentArtifactsViewModel { + + static '$inject' = [ + '$scope', + '$templateCache', + '$filter', + 'ValidationUtils', + 'ArtifactsUtils', + 'ModalsHandler', + 'ComponentServiceNg2' + ]; + + constructor(private $scope:IDeploymentArtifactsViewModelScope, + private $templateCache:ng.ITemplateCacheService, + private $filter:ng.IFilterService, + private validationUtils:ValidationUtils, + private artifactsUtils:ArtifactsUtils, + private ModalsHandler:ModalsHandler, + private ComponentServiceNg2: ComponentServiceNg2) { + this.initScope(); + this.$scope.updateSelectedMenuItem(); + } + + private initDescriptions = ():void => { + this.$scope.artifactDescriptions = {}; + _.forEach(this.$scope.component.deploymentArtifacts, (artifact:ArtifactModel):void => { + this.$scope.artifactDescriptions[artifact.artifactLabel] = artifact.description; + }); + }; + + private setArtifact = (artifact:ArtifactModel):void => { + if (!artifact.description || !this.$scope.getValidationPattern('string').test(artifact.description)) { + artifact.description = this.$scope.artifactDescriptions[artifact.artifactLabel]; + } + }; + + private initScopeArtifacts = ()=> { + this.$scope.artifacts = <ArtifactModel[]>_.values(this.$scope.component.deploymentArtifacts); + _.forEach(this.$scope.artifacts, (artifact:ArtifactModel):void => { + artifact.envArtifact = this.getEnvArtifact(artifact); + }); + }; + + private initArtifacts = (loadFromServer:boolean):void => { + if (loadFromServer) { + this.$scope.isLoading = true; + this.ComponentServiceNg2.getComponentDeploymentArtifacts(this.$scope.component).subscribe((response:ComponentGenericResponse) => { + this.$scope.component.deploymentArtifacts = response.deploymentArtifacts; + this.initScopeArtifacts(); + this.$scope.isLoading = false; + }); + } else { + this.initScopeArtifacts(); + } + + }; + + private getEnvArtifact = (heatArtifact:ArtifactModel):ArtifactModel=> { + return _.find(this.$scope.artifacts, (item:ArtifactModel)=> { + return item.generatedFromId === heatArtifact.uniqueId; + }); + }; + + private getCurrentArtifact = ():ArtifactModel => { + if (!this.$scope.selectedArtifactId) { + return null; + } + let artifact:ArtifactModel = this.$scope.artifacts.filter((art) => { + return art.uniqueId == this.$scope.selectedArtifactId; + })[0]; + return artifact; + } + + private initScope = ():void => { + let self = this; + this.$scope.isLoading = false; + this.$scope.selectedArtifactId = null; + this.initDescriptions(); + if(this.$scope.component.deploymentArtifacts) { + this.initArtifacts(false); + } else { + this.initArtifacts(true); + } + this.$scope.setValidState(true); + + this.$scope.tableHeadersList = [ + {title: 'Name', property: 'artifactDisplayName'}, + {title: 'Type', property: 'artifactType'}, + {title: 'Deployment timeout', property: 'timeout'}, + {title: 'Version', property: 'artifactVersion'}, + {title: 'UUID', property: 'artifactUUID'} + ]; + + this.$templateCache.put("deployment-artifacts-description-popover.html", require('app/view-models/workspace/tabs/deployment-artifacts/deployment-artifacts-description-popover.html')); + this.$scope.popoverTemplate = "deployment-artifacts-description-popover.html"; + + this.$scope.isLicenseArtifact = (artifact: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:ArtifactModel):void => { + artifact.artifactGroupType = 'DEPLOYMENT'; + let artifactCopy = new ArtifactModel(artifact); + + let success = (response:any):void => { + this.$scope.artifactDescriptions[artifactCopy.artifactLabel] = artifactCopy.description; + this.initArtifacts(true); + // this.$scope.artifacts = _.values(this.$scope.component.deploymentArtifacts); + }; + + let error = (err:any):void => { + console.log(err); + this.initArtifacts(true); + // self.$scope.artifacts = _.values(self.$scope.component.deploymentArtifacts); + }; + + this.ModalsHandler.openArtifactModal(artifactCopy, this.$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.updateSelectedArtifact = ():void => { + if (!this.$scope.isViewMode() && !this.$scope.isLoading) { + let artifact:ArtifactModel = this.getCurrentArtifact(); + this.setArtifact(artifact); //resets artifact description to original value if invalid. + if (artifact && artifact.originalDescription != artifact.description) { + this.$scope.isLoading = true; + let onSuccess = (responseArtifact:ArtifactModel):void => { + this.$scope.artifactDescriptions[responseArtifact.artifactLabel] = responseArtifact.description; + // this.$scope.artifacts = _.values(this.$scope.component.deploymentArtifacts); + this.initArtifacts(true); + this.$scope.isLoading = false; + }; + + let onFailed = (error:any):void => { + console.log('Delete artifact returned error:', error); + this.$scope.isLoading = false; + }; + + this.$scope.component.addOrUpdateArtifact(artifact).then(onSuccess, onFailed); + } + } + }; + + this.$scope.delete = (artifact:ArtifactModel):void => { + let onOk = ():void => { + this.$scope.isLoading = true; + let onSuccess = ():void => { + this.$scope.isLoading = false; + this.initArtifacts(true); + //this.$scope.artifacts = _.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); + }; + + this.$scope.getEnvArtifactName = (artifact:ArtifactModel):string => { + let envArtifact = this.$scope.getEnvArtifact(artifact); + if (envArtifact) { + return envArtifact.artifactDisplayName; + } + }; + + this.$scope.openEditEnvParametersModal = (artifact:ArtifactModel):void => { + this.ModalsHandler.openEditEnvParametersModal(artifact, this.$scope.component).then(()=> { + this.initArtifacts(true); + }, ()=> { + this.initArtifacts(true); + }); + }; + + this.$scope.openDescriptionPopover = (artifactId:string):void => { + if (this.$scope.selectedArtifactId && this.$scope.selectedArtifactId != artifactId) { + this.$scope.updateSelectedArtifact(); + } + this.$scope.selectedArtifactId = artifactId; + + }; + + this.$scope.closeDescriptionPopover = ():void => { + if (this.$scope.selectedArtifactId) { + this.$scope.updateSelectedArtifact(); + this.$scope.selectedArtifactId = null; + } + }; + }; +} diff --git a/catalog-ui/src/app/view-models/workspace/tabs/deployment-artifacts/deployment-artifacts-view.html b/catalog-ui/src/app/view-models/workspace/tabs/deployment-artifacts/deployment-artifacts-view.html new file mode 100644 index 0000000000..cc914b07a8 --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/deployment-artifacts/deployment-artifacts-view.html @@ -0,0 +1,126 @@ +<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': selectedArtifactId == artifact.uniqueId }" + data-ng-if="artifact.esId && 'HEAT_ENV' !== artifact.artifactType" + data-tests-id="artifact-item-{{artifact.artifactDisplayName}}"> + <div class="table-col-general flex-item" > + <div class="heat-env-connect-container" ng-class="{'heat-env-connect-container-view-mode': isViewMode()}" data-ng-if="artifact.envArtifact"> + <span class="heat-env-connect"></span> + </div> + <span data-tests-id="artifactDisplayName_{{artifact.artifactDisplayName}}" sdc-smart-tooltip class="artifact-name">{{artifact.artifactDisplayName}}</span> + + <span class="sprite-new show-desc hand description-popover-icon" + uib-popover-template="popoverTemplate" + popover-class="parameter-description-popover deployment-artifact-view top" + popover-title="Description" + popover-placement="top-left" + popover-is-open="selectedArtifactId == artifact.uniqueId && !isLoading" + popover-trigger="'none'" + popover-append-to-body="true" + data-ng-click="openDescriptionPopover(artifact.uniqueId)" + data-tests-id="descriptionIcon_{{artifact.artifactDisplayName}}"></span> + </div> + + <div class="table-col-general flex-item text" data-tests-id="artifactType_{{artifact.artifactDisplayName}}" tooltips tooltip-content="{{artifact.artifactType}}"> + {{artifact.artifactType}} + </div> + <div class="table-col-general flex-item" data-tests-id="timeout_{{artifact.artifactDisplayName}}"> + {{artifact.timeout? artifact.timeout:''}} + </div> + <div class="table-col-general flex-item" data-tests-id="artifactVersion_{{artifact.artifactDisplayName}}"> + {{artifact.artifactVersion}} + </div> + <div class="table-col-general flex-item text" data-tests-id="artifactUUID_{{artifact.artifactDisplayName}}" tooltips tooltip-content="{{artifact.artifactUUID}}"> + <span>{{artifact.artifactUUID}}</span> + </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.artifactDisplayName" component="component" artifact="artifact"></button> + <button ng-if="!isViewMode() && artifact.isHEAT()" + class="sprite e-sdc-small-icon-pad edit-paramtes-button" + data-ng-click="openEditEnvParametersModal(artifact)" type="button" + data-tests-id="edit-parameters-of-{{artifact.artifactDisplayName}}"></button> + </div> + </div> + <div data-ng-repeat-end="" class="flex-container data-row" data-ng-if="artifact.envArtifact"> + + <div class="table-col-general flex-item" zzdata-ng-click="!isViewMode() && addOrUpdate(artifact.envArtifact)"> + <span>{{artifact.envArtifact.artifactDisplayName}}</span> + </div> + + <div class="table-col-general flex-item" data-tests-id="{{artifact.envArtifact.artifactType}}"> + {{artifact.envArtifact.artifactType}} + </div> + <div class="table-col-general flex-item" data-tests-id="{{artifact.envArtifact.timeout}}"> + {{artifact.envArtifact.timeout? artifact.envArtifact.timeout:''}} + </div> + <div class="table-col-general flex-item" data-tests-id="artifactEnvVersion_{{artifact.artifactDisplayName}}"> + {{artifact.envArtifact.artifactVersion}} + </div> + <div class="table-col-general flex-item text" data-tests-id="{{artifact.envArtifact.artifactUUID}}" tooltips tooltip-content="{{artifact.envArtifact.artifactUUID}}"> + <span>{{artifact.envArtifact.artifactUUID}}</span> + </div> + + + <div class="table-btn-col flex-item" > + <button class="table-edit-btn" data-tests-id="edit_{{artifact.artifactLabel}}env" + data-ng-if="!isViewMode()" data-ng-click="addOrUpdate(artifact.envArtifact)"></button> + <button class="table-download-btn" data-tests-id="download_env_{{artifact.artifactDisplayName}}" download-artifact + data-ng-if="artifact.artifactName" component="component" artifact="artifact.envArtifact"></button> + + </div> + </div> + + <!--<div class="i-sdc-designer-sidebar-section-content-item-artifact-heat-env" ng-if="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>--> + + + + <!-- 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/src/app/view-models/workspace/tabs/deployment-artifacts/deployment-artifacts.less b/catalog-ui/src/app/view-models/workspace/tabs/deployment-artifacts/deployment-artifacts.less new file mode 100644 index 0000000000..dd3b16447c --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/deployment-artifacts/deployment-artifacts.less @@ -0,0 +1,196 @@ +.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; + } + + + .heat-env-connect-container{ + background-color: white; + position: absolute; + height: 70px; + width:20px; + left: 0; + top:0; + } + .heat-env-connect-container-view-mode{ + background-color: @tlv_color_t; + } + .heat-env-connect{ + border-left: 1px #848586 solid; + height: 50px; + margin-left: 10px; + margin-top: 10px; + border-top: 1px #848586 solid; + border-bottom: 1px #848586 solid; + width: 11px; + float: left; + + } + + .artifact-name{ + width:85%; + } + + .table-container-flex .table .body .data-row div .heat-env-connect-container{ + border-right: none; + } + + .i-sdc-designer-sidebar-section-content-item-file-link::before{ + content:""; + background-color: white; + width: 12px; + + } + + + + .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; + + .text{ + overflow: hidden; + text-overflow: ellipsis; + display: inline-block; + white-space: nowrap; + } + + .flex-item:nth-child(1) { + flex-grow: 15; + .hand; + padding-left: 30px; + position: relative; + span.table-arrow { + margin-right: 7px; + } + .description-popover-icon{ + float:right; + margin-top:6px; + } + } + + .flex-item:nth-child(2) { + flex-grow: 6; + } + + .flex-item:nth-child(3) { + flex-grow: 9; + } + + .flex-item:nth-child(4) { + flex-grow: 3; + } + + .flex-item:nth-child(5) { + flex-grow: 20; + } + + .flex-item:nth-child(6) { + flex-grow: 5; + + &.table-btn-col { + display: flex; + justify-content: space-between; + align-items: center; + + button { + flex: 0 1 auto; + background-color: transparent; + border: 0; + margin: 0; + } + .edit-paramtes-button { + order: -1; + } + } + } + } + .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; + } + } + } +} + +.parameter-description-popover.deployment-artifact-view { + margin-left: -10px; + z-index: 1040; + min-width: 300px; + .input-error { + .q_12_m; + } + .error textarea{ + border-color: @main_color_g; + color: @color_h; + outline: none; + } + .popover-content textarea { + width:100%; + } +} diff --git a/catalog-ui/src/app/view-models/workspace/tabs/deployment/deployment-view-model.ts b/catalog-ui/src/app/view-models/workspace/tabs/deployment/deployment-view-model.ts new file mode 100644 index 0000000000..c94342a51e --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/deployment/deployment-view-model.ts @@ -0,0 +1,127 @@ +'use strict'; +import {IWorkspaceViewModelScope} from "app/view-models/workspace/workspace-view-model"; +import {ComponentFactory, MenuHandler, ChangeLifecycleStateHandler, ModalsHandler} from "app/utils"; +import {LeftPaletteLoaderService, CacheService, SharingService} from "app/services"; +import {Component, IAppMenu, Tab, ComponentInstance} from "app/models"; +import {GRAPH_EVENTS} from "../../../../utils/constants"; +import {ComponentGenericResponse} from "../../../../ng2/services/responses/component-generic-response"; +import {EventListenerService} from "../../../../services/event-listener-service"; +import {ComponentServiceNg2} from "../../../../ng2/services/component-services/component.service"; + +export interface IDeploymentViewModelScope extends IWorkspaceViewModelScope { + + currentComponent:Component; + selectedComponent:Component; + isLoading:boolean; + sharingService:SharingService; + sdcMenu:IAppMenu; + version:string; + isViewOnly:boolean; + tabs:Array<Tab>; + selectedTab: Tab; + isComponentInstanceSelected():boolean; + updateSelectedComponent():void + openUpdateModal(); + deleteSelectedComponentInstance():void; + onBackgroundClick():void; + setSelectedInstance(componentInstance:ComponentInstance):void; + printScreen():void; + +} + +export class DeploymentViewModel { + + static '$inject' = [ + '$scope', + '$templateCache', + 'sdcMenu', + 'MenuHandler', + '$state', + 'Sdc.Services.SharingService', + '$filter', + 'Sdc.Services.CacheService', + 'ComponentFactory', + 'ChangeLifecycleStateHandler', + 'LeftPaletteLoaderService', + 'ModalsHandler', + 'EventListenerService', + 'ComponentServiceNg2' + ]; + + constructor(private $scope:IDeploymentViewModelScope, + private $templateCache:ng.ITemplateCacheService, + private sdcMenu:IAppMenu, + private MenuHandler:MenuHandler, + private $state:ng.ui.IStateService, + private sharingService:SharingService, + private $filter:ng.IFilterService, + private cacheService:CacheService, + private ComponentFactory:ComponentFactory, + private ChangeLifecycleStateHandler:ChangeLifecycleStateHandler, + private LeftPaletteLoaderService:LeftPaletteLoaderService, + private ModalsHandler:ModalsHandler, + private eventListenerService: EventListenerService, + private ComponentServiceNg2: ComponentServiceNg2) { + + this.$scope.setValidState(true); + this.initScope(); + this.initGraphData(); + 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) { + this.$templateCache.put("hierarchy-view.html", require('app/view-models/tabs/hierarchy/hierarchy-view.html')); + let hierarchyTab = new Tab("hierarchy-view.html", 'Sdc.ViewModels.HierarchyViewModel', 'hierarchy', this.$scope.isViewMode(), this.$scope.currentComponent, 'hierarchy'); + this.$scope.tabs.push(hierarchyTab) + } + } + + private initGraphData = ():void => { + if(!this.$scope.component.componentInstances || !this.$scope.component.componentInstancesRelations || !this.$scope.component.groups) { + this.ComponentServiceNg2.getDeploymentGraphData(this.$scope.component).subscribe((response:ComponentGenericResponse) => { + this.$scope.component.componentInstances = response.componentInstances; + this.$scope.component.componentInstancesRelations = response.componentInstancesRelations; + this.$scope.component.groups = response.groups; + this.$scope.isLoading = false; + this.initComponent(); + this.initRightTabs(); + this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_DEPLOYMENT_GRAPH_DATA_LOADED); + this.$scope.selectedTab = this.$scope.tabs[0]; + }); + } else { + this.$scope.isLoading = false; + this.initRightTabs(); + this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_DEPLOYMENT_GRAPH_DATA_LOADED); + + } + }; + + private initScope = ():void => { + this.$scope.isLoading = true; + this.$scope.sharingService = this.sharingService; + this.$scope.sdcMenu = this.sdcMenu; + this.$scope.version = this.cacheService.get('version'); + this.initComponent(); + this.$scope.tabs = Array<Tab>(); + } +} diff --git a/catalog-ui/src/app/view-models/workspace/tabs/deployment/deployment-view.html b/catalog-ui/src/app/view-models/workspace/tabs/deployment/deployment-view.html new file mode 100644 index 0000000000..f8b5f23a25 --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/deployment/deployment-view.html @@ -0,0 +1,10 @@ +<div class="deployment-view"> + <loader display="isLoading"></loader> + <div class="w-sdc-deployment-canvas" data-ng-class="{sidebaractive: displayDesignerRightSidebar}"> + <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" selected-tab="selectedTab"></sdc-tabs> + </div> +</div> diff --git a/catalog-ui/src/app/view-models/workspace/tabs/deployment/deployment.less b/catalog-ui/src/app/view-models/workspace/tabs/deployment/deployment.less new file mode 100644 index 0000000000..4c548c7331 --- /dev/null +++ b/catalog-ui/src/app/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/src/app/view-models/workspace/tabs/distribution/disribution-status-modal/disribution-status-modal-view-model.ts b/catalog-ui/src/app/view-models/workspace/tabs/distribution/disribution-status-modal/disribution-status-modal-view-model.ts new file mode 100644 index 0000000000..ce2e0169bf --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/distribution/disribution-status-modal/disribution-status-modal-view-model.ts @@ -0,0 +1,83 @@ +'use strict'; +import {Distribution, DistributionComponent, ExportExcel} from "app/models"; + +interface IDistributionStatusModalViewModelScope { + distribution:Distribution; + status:string; + getStatusCount(distributionComponent:Array<DistributionComponent>):any; + getUrlName(url:string):string; + modalDitributionStatus:ng.ui.bootstrap.IModalServiceInstance; + footerButtons:Array<any>; + //exportExcelData:ExportExcel; + close():void; + initDataForExportExcel():ExportExcel; +} + +export class DistributionStatusModalViewModel { + + static '$inject' = ['$scope', '$uibModalInstance', 'data', '$filter']; + + constructor(private $scope:IDistributionStatusModalViewModelScope, + private $uibModalInstance:ng.ui.bootstrap.IModalServiceInstance, + private data:any, + private $filter:ng.IFilterService) { + this.initScope(); + } + + private generateMetaDataForExportExcel = ():Array<string>=> { + let metaData = []; + metaData[0] = 'Name:' + this.data.component.name + '| UUID:' + this.data.component.uuid + '| Invariant UUID:' + this.data.component.invariantUUID; + metaData[1] = 'Distribution ID:' + this.$scope.distribution.distributionID + + '| USER ID:' + this.$scope.distribution.userId + + '| Time[UTC]:' + this.$filter('date')(this.$scope.distribution.timestamp, 'MM/dd/yyyy h:mma', 'UTC') + + '| Status:' + this.$scope.distribution.deployementStatus; + return metaData; + }; + + private generateDataObjectForExportExcel = ():any=> { + let correctFormatDataObj = []; + _.each(this.$scope.distribution.distributionComponents, (dComponent:DistributionComponent) => { + if (dComponent.status == this.$scope.status) { + correctFormatDataObj.push({ + 'omfComponentID': dComponent.omfComponentID, + 'artiFactName': this.$scope.getUrlName(dComponent.url), + 'url': dComponent.url, + 'timestamp': this.$filter('date')(dComponent.timestamp, 'MM/dd/yyyy h:mma', 'UTC'), + 'status': dComponent.status + }); + } + }); + return correctFormatDataObj; + }; + + private initScope = ():void => { + this.$scope.distribution = this.data.distribution; + this.$scope.status = this.data.status; + this.$scope.modalDitributionStatus = this.$uibModalInstance; + + + this.$scope.getUrlName = (url:string):string => { + let urlName:string = _.last(url.split('/')); + return urlName; + }; + + this.$scope.initDataForExportExcel = ():ExportExcel => { + let exportExcelData = new ExportExcel(); + exportExcelData.fileName = this.$scope.status; + exportExcelData.groupByField = "omfComponentID"; + exportExcelData.tableHeaders = ["Component ID", "Artifact Name", "URL", "Time(UTC)", "Status"]; + exportExcelData.metaData = this.generateMetaDataForExportExcel(); + exportExcelData.dataObj = this.generateDataObjectForExportExcel(); + return exportExcelData; + }; + + this.$scope.close = ():void => { + this.$uibModalInstance.close(); + }; + + this.$scope.footerButtons = [ + {'name': 'Close', 'css': 'blue', 'callback': this.$scope.close} + ]; + + }; +} diff --git a/catalog-ui/src/app/view-models/workspace/tabs/distribution/disribution-status-modal/disribution-status-modal-view.html b/catalog-ui/src/app/view-models/workspace/tabs/distribution/disribution-status-modal/disribution-status-modal-view.html new file mode 100644 index 0000000000..3367193fc7 --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/distribution/disribution-status-modal/disribution-status-modal-view.html @@ -0,0 +1,130 @@ +<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> + + <div class="actions-buttons"> + <json-export-excel init-export-excel-data="initDataForExportExcel()"> + </json-export-excel> + </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/src/app/view-models/workspace/tabs/distribution/disribution-status-modal/disribution-status-modal.less b/catalog-ui/src/app/view-models/workspace/tabs/distribution/disribution-status-modal/disribution-status-modal.less new file mode 100644 index 0000000000..d167083a2b --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/distribution/disribution-status-modal/disribution-status-modal.less @@ -0,0 +1,40 @@ +.w-sdc-classic-top-line-modal { + + .w-sdc-modal-head { + // border-bottom: none; + } + .w-sdc-distribution-view { + .actions-buttons { + height: 29px; + padding: 0 25px 0 0px; + span{ + float: right; + } + } + + .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/src/app/view-models/workspace/tabs/distribution/distribution-view-model.ts b/catalog-ui/src/app/view-models/workspace/tabs/distribution/distribution-view-model.ts new file mode 100644 index 0000000000..002b16f63f --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/distribution/distribution-view-model.ts @@ -0,0 +1,111 @@ +'use strict'; +import {Distribution, DistributionComponent, Service} from "app/models"; +import {ModalsHandler, Dictionary} from "app/utils"; +import {IWorkspaceViewModelScope} from "app/view-models/workspace/workspace-view-model"; + +interface IDistributionViewModel extends IWorkspaceViewModelScope { + modalDistribution:ng.ui.bootstrap.IModalServiceInstance; + service:Service; + distributions:Array<Distribution>; + showComponents(distribution:Distribution):void; + markAsDeployed(distribution:Distribution):void; + getStatusCount(distributionComponent:Array<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:ModalsHandler) { + this.initScope(); + this.$scope.setValidState(true); + this.$scope.updateSelectedMenuItem(); + } + + private initScope = ():void => { + this.$scope.service = <Service>this.$scope.component; + + + // Open Distribution status modal + this.$scope.openDisributionStatusModal = (distribution:Distribution, status:string):void => { + this.ModalsHandler.openDistributionStatusModal(distribution, status, this.$scope.component).then(()=> { + // OK + }, ()=> { + // ERROR + }); + }; + + + this.$scope.showComponents = (distribution:Distribution):void => { + let onError = (response) => { + console.info('onError showComponents', response); + }; + let onSuccess = (distributionComponents:Array<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<DistributionComponent>):any => { + return _.countBy(distributionComponent, 'status') + }; + + this.$scope.getUrlName = (url:string):string => { + let urlName:string = _.last(url.split('/')); + return urlName; + }; + + this.$scope.markAsDeployed = (distribution: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<Distribution>) => { + this.$scope.distributions = distributions; + }; + this.$scope.service.getDistributionsList().then(onSuccess, onError); + }; + + this.$scope.initDistributions(); + + }; + + + private aggregateDistributionComponent = (distributionComponents:Array<DistributionComponent>):any => { + let aggregateDistributions:Dictionary<string,Dictionary<string,Array<DistributionComponent>>> = new Dictionary<string,Dictionary<string,Array<DistributionComponent>>>(); + let tempAggregateDistributions:any = _.groupBy(distributionComponents, 'omfComponentID'); + let aa = new Dictionary<string,Array<DistributionComponent>>(); + + let tempAggregate:any; + _.forEach(tempAggregateDistributions, (distributionComponents:Array<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/src/app/view-models/workspace/tabs/distribution/distribution-view.html b/catalog-ui/src/app/view-models/workspace/tabs/distribution/distribution-view.html new file mode 100644 index 0000000000..710336af15 --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/distribution/distribution-view.html @@ -0,0 +1,181 @@ +<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-if="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,'DEPLOY_ERROR')" >Deploy Errors:</sapn><span + data-ng-bind="item.statusCount.DEPLOY_ERROR || 0" class="red-font "></span><span + data-ng-class="{'error':(item.statusCount.DEPLOY_ERROR > 0)}" data-tests-id="errors_{{$index}}"></span></div> + <div class="status-item-7"><sapn class="link" data-ng-click="openDisributionStatusModal(item,'DOWNLOAD_ERROR')" >Download 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-if="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">Deploy Errors:<span + data-ng-bind="statusCount.DEPLOY_ERROR || 0" class="red-font"></span><span + data-ng-class="{'error':(statusCount.DEPLOY_ERROR > 0)}"></span></div> + <div class="status-item-7">Download 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-if="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" sdc-smart-tooltip> + <div class="w-sdc-distribution-arrow-btn" data-ng-click="urlListExtends=!urlListExtends" + data-ng-class="{'extends': urlListExtends}" + data-ng-init="urlListListExtends=false;urlList[0].displayUrl=getUrlName(urlList[0].url)" + ></div> + {{urlList[0].omfComponentID}} + </div> + <div class="w-sdc-distribute-cell item-2" sdc-smart-tooltip> + {{urlList[0].displayUrl}} + </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" sdc-smart-tooltip> + {{urlList[0].status}} + </div> + </div> + + + <div data-ng-repeat-end data-ng-if="urlListExtends" class="disable-hover" > + <div class="w-sdc-distribute-row extends disable-hover"> + <ul data-ng-if="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-if="distributionComponent.status == 'NOT_NOTIFIED'">Reason: Component has determined artifact is not needed.</span> + <span + class="disable-hover reason" data-ng-if="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/src/app/view-models/workspace/tabs/distribution/distribution.less b/catalog-ui/src/app/view-models/workspace/tabs/distribution/distribution.less new file mode 100644 index 0000000000..ee1f7ed2d6 --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/distribution/distribution.less @@ -0,0 +1,362 @@ + +.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; + display: inline-table; + } + .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/src/app/view-models/workspace/tabs/general/general-view-model.ts b/catalog-ui/src/app/view-models/workspace/tabs/general/general-view-model.ts new file mode 100644 index 0000000000..44953985fc --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/general/general-view-model.ts @@ -0,0 +1,349 @@ +'use strict'; +import {ModalsHandler, ValidationUtils, EVENTS, CHANGE_COMPONENT_CSAR_VERSION_FLAG, ComponentType, DEFAULT_ICON, + ResourceType} from "app/utils"; +import {CacheService, EventListenerService, ProgressService} from "app/services"; +import {IAppConfigurtaion, Product, IValidate, IMainCategory, Resource, ISubCategory,Service} from "app/models"; +import {IWorkspaceViewModelScope} from "app/view-models/workspace/workspace-view-model"; + +export class Validation { + componentNameValidationPattern:RegExp; + contactIdValidationPattern:RegExp; + tagValidationPattern:RegExp; + vendorValidationPattern:RegExp; + commentValidationPattern:RegExp; + projectCodeValidationPattern:RegExp; +} + +export class componentCategories {//categories field bind to this obj in order to solve this bug: DE242059 + selectedCategory:string; +} + +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; + componentCategories:componentCategories; + + 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<IMainCategory>; + onCategoryChange():void; + onEcompGeneratedNamingChange():void; + openOnBoardingModal():void; + initCategoreis():void; +} + +export class GeneralViewModel { + + static '$inject' = [ + '$scope', + 'Sdc.Services.CacheService', + 'ComponentNameValidationPattern', + 'ContactIdValidationPattern', + 'TagValidationPattern', + 'VendorValidationPattern', + 'CommentValidationPattern', + 'ValidationUtils', + 'sdcConfig', + 'ProjectCodeValidationPattern', + '$state', + 'ModalsHandler', + 'EventListenerService', + 'Notification', + 'Sdc.Services.ProgressService', + '$interval', + '$filter', + '$timeout' + ]; + + constructor(private $scope:IGeneralScope, + private cacheService:CacheService, + private ComponentNameValidationPattern:RegExp, + private ContactIdValidationPattern:RegExp, + private TagValidationPattern:RegExp, + private VendorValidationPattern:RegExp, + private CommentValidationPattern:RegExp, + private ValidationUtils:ValidationUtils, + private sdcConfig:IAppConfigurtaion, + private ProjectCodeValidationPattern:RegExp, + private $state:ng.ui.IStateService, + private ModalsHandler:ModalsHandler, + private EventListenerService:EventListenerService, + private Notification:any, + private progressService:ProgressService, + protected $interval:any, + private $filter:ng.IFilterService, + private $timeout:ng.ITimeoutService) { + + this.initScopeValidation(); + this.initScopeMethods(); + this.initScope(); + this.$scope.updateSelectedMenuItem(); + } + + + + + private initScopeValidation = ():void => { + this.$scope.validation = new Validation(); + this.$scope.validation.componentNameValidationPattern = this.ComponentNameValidationPattern; + 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(CHANGE_COMPONENT_CSAR_VERSION_FLAG)) { + (<Resource>this.$scope.component).csarVersion = this.cacheService.get(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; + this.$scope.componentCategories = new componentCategories(); + this.$scope.componentCategories.selectedCategory = this.$scope.component.selectedCategory; + + // Workaround to short vendor name to 25 chars + // Amdocs 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:Resource = <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()) { + (<Product>this.$scope.component).contacts = []; + (<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<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 = <IMainCategory>_.find(this.$scope.categories, function (item) { + return item["name"] === mainCategory; + + }); + + let mainCategoryClone = angular.copy(selectedMainCategory); + if (subCategory) { + let selectedSubcategory = <ISubCategory>_.find(selectedMainCategory.subcategories, function (item) { + return item["name"] === subCategory; + }); + mainCategoryClone['subcategories'] = [angular.copy(selectedSubcategory)]; + } + let tmpSelected = <IMainCategory> mainCategoryClone; + + let result:Array<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 === ComponentType.RESOURCE) { + this.$scope.categories = this.cacheService.get('resourceCategories'); + + } + if (this.$scope.componentType === 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 = ComponentType.RESOURCE == this.$scope.componentType ? this.$scope.component.getComponentSubType() : undefined; + + let onFailed = (response) => { + //console.info('onFaild', response); + //this.$scope.isLoading = false; + }; + + let onSuccess = (validation: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 === 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.originComponent.name || this.$scope.component.name.toUpperCase() !== this.$scope.originComponent.name.toUpperCase()) + ) { + if (!(this.$scope.componentType === ComponentType.RESOURCE && (<Resource>this.$scope.component).csarUUID !== undefined) + ) { + this.$scope.component.validateName(name, subtype).then(onSuccess, onFailed); + } + } else if (this.$scope.originComponent.name && this.$scope.component.name.toUpperCase() === this.$scope.originComponent.name.toUpperCase()) { + // 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.selectedCategory = this.$scope.componentCategories.selectedCategory; + this.$scope.component.categories = this.convertCategoryStringToOneArray(); + this.$scope.component.icon = DEFAULT_ICON; + }; + + this.$scope.onEcompGeneratedNamingChange = ():void =>{ + if(!(<Service>this.$scope.component).ecompGeneratedNaming){ + (<Service>this.$scope.component).namingPolicy = ''; + } + }; + + this.$scope.onVendorNameChange = (oldVendorName:string):void => { + if (this.$scope.component.icon === oldVendorName) { + this.$scope.component.icon = DEFAULT_ICON; + } + }; + }; +} diff --git a/catalog-ui/src/app/view-models/workspace/tabs/general/general-view.html b/catalog-ui/src/app/view-models/workspace/tabs/general/general-view.html new file mode 100644 index 0000000000..2ad0cbacd6 --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/general/general-view.html @@ -0,0 +1,354 @@ +<div include-padding="true" class="sdc-workspace-general-step"> + + <form novalidate class="w-sdc-form" name="editForm" validation-on-load form-to-validate="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" data-ng-class="{'required':isCreateMode()}">{{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.componentNameValidationPattern" + 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_INVALID_NAME"></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="componentCategories.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="componentCategories.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 }" + maxlength="50" + data-required + name="projectCode" + data-ng-pattern="validation.projectCodeValidationPattern" + 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> + + + <!--------------------- ECOMPGENERATEDNAMING --------------------> + + <div class="i-sdc-form-item" + data-ng-class="{'error': validateField(editForm.ecompGeneratedNaming)}" + data-ng-if="component.isService()"> + <label class="i-sdc-form-label">Ecomp Generated Naming</label> + <select class="i-sdc-form-select" + data-required + name="ecompGeneratedNaming" + data-ng-change="onEcompGeneratedNamingChange()" + data-ng-class="{'view-mode': isViewMode()}" + data-ng-model="component.ecompGeneratedNaming" + data-tests-id="ecompGeneratedNaming"> + <option ng-value="true">true</option> + <option ng-value="false">false</option> + </select> + <div class="input-error" data-ng-show="validateField(editForm.ecompGeneratedNaming)"> + + </div> + </div> + <!--------------------- CATEGORIES --------------------> + + <!--------------------- NAMING POLICY --------------------> + <div ng-if="component.isService()" class="i-sdc-form-item" data-ng-class="{'error': validateField(editForm.namingPolicy)}"> + <label class="i-sdc-form-label">Naming policy</label> + <input class="i-sdc-form-input" + name="fullName" + data-ng-class="{'view-mode': isViewMode() || !component.ecompGeneratedNaming}" + data-ng-maxlength="100" + maxlength="100" + data-ng-model="component.namingPolicy" + type="text" + data-ng-model-options="{ debounce: 500 }" + data-ng-pattern="validation.commentValidationPattern" + data-tests-id="namingPolicy" + autofocus + ng-readonly="isViewMode() || !component.ecompGeneratedNaming" + /> + + <div class="input-error" data-ng-show="validateField(editForm.namingPolicy)"> + <span ng-show="editForm.namingPolicy.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '100' }"></span> + <span ng-show="editForm.namingPolicy.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span> + </div> + </div> + <!--------------------- NAMING POLICY --------------------> + + + <!--------------------- 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 --------------------> + + <!--------------------- USER 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="contactId" + 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="contactId" + 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> + <!--------------------- USER ID --------------------> + + + </div><!-- Close w-sdc-form-column --> + </div> + + </div><!-- Close w-sdc-form-section-container --> + + </form> + +</div> diff --git a/catalog-ui/src/app/view-models/workspace/tabs/general/general.less b/catalog-ui/src/app/view-models/workspace/tabs/general/general.less new file mode 100644 index 0000000000..1861d02e98 --- /dev/null +++ b/catalog-ui/src/app/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/src/app/view-models/workspace/tabs/icons/icons-view-model.ts b/catalog-ui/src/app/view-models/workspace/tabs/icons/icons-view-model.ts new file mode 100644 index 0000000000..03dad2cc06 --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/icons/icons-view-model.ts @@ -0,0 +1,111 @@ +/** + * Created by obarda on 4/4/2016. + */ +'use strict'; +import {ComponentFactory} from "app/utils"; +import {AvailableIconsService} from "app/services"; +import {IWorkspaceViewModelScope} from "app/view-models/workspace/workspace-view-model"; +import {IMainCategory, ISubCategory} from "app/models"; + +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:AvailableIconsService, + private ComponentFactory: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:IMainCategory):void => { + if (category.icons) { + this.$scope.icons = this.$scope.icons.concat(category.icons); + } + if (category.subcategories) { + _.forEach(category.subcategories, (subcategory: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 === 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/src/app/view-models/workspace/tabs/icons/icons-view.html b/catalog-ui/src/app/view-models/workspace/tabs/icons/icons-view.html new file mode 100644 index 0000000000..aac14e0e84 --- /dev/null +++ b/catalog-ui/src/app/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/src/app/view-models/workspace/tabs/icons/icons.less b/catalog-ui/src/app/view-models/workspace/tabs/icons/icons.less new file mode 100644 index 0000000000..65f946f395 --- /dev/null +++ b/catalog-ui/src/app/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/src/app/view-models/workspace/tabs/information-artifacts/information-artifacts-view-model.ts b/catalog-ui/src/app/view-models/workspace/tabs/information-artifacts/information-artifacts-view-model.ts new file mode 100644 index 0000000000..e10a9944d3 --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/information-artifacts/information-artifacts-view-model.ts @@ -0,0 +1,138 @@ +'use strict'; +import {ModalsHandler} from "app/utils"; +import {SharingService} from "app/services"; +import {IAppConfigurtaion, ArtifactModel, IFileDownload} from "app/models"; +import {IWorkspaceViewModelScope} from "app/view-models/workspace/workspace-view-model"; +import {ComponentServiceNg2} from "../../../../ng2/services/component-services/component.service"; +import {ArtifactGroupModel} from "../../../../models/artifacts"; +import {ComponentGenericResponse} from "../../../../ng2/services/responses/component-generic-response"; + +export interface IInformationArtifactsScope extends IWorkspaceViewModelScope { + artifacts:Array<ArtifactModel>; + tableHeadersList:Array<any>; + artifactType:string; + isResourceInstance:boolean; + downloadFile:IFileDownload; + isLoading:boolean; + sortBy:string; + reverse:boolean; + + getTitle():string; + addOrUpdate(artifact:ArtifactModel):void; + delete(artifact:ArtifactModel):void; + download(artifact:ArtifactModel):void; + clickArtifactName(artifact:any):void; + openEditEnvParametersModal(artifactResource:ArtifactModel):void; + sort(sortBy:string):void; + showNoArtifactMessage():boolean; +} + +export class InformationArtifactsViewModel { + + static '$inject' = [ + '$scope', + '$filter', + '$state', + 'sdcConfig', + 'ModalsHandler', + 'ComponentServiceNg2' + ]; + + constructor(private $scope:IInformationArtifactsScope, + private $filter:ng.IFilterService, + private $state:any, + private sdcConfig:IAppConfigurtaion, + private ModalsHandler:ModalsHandler, + private ComponentServiceNg2: ComponentServiceNg2) { + this.initInformationalArtifacts(); + this.$scope.updateSelectedMenuItem(); + } + + private initInformationalArtifacts = ():void => { + if(!this.$scope.component.artifacts) { + this.$scope.isLoading = true; + this.ComponentServiceNg2.getComponentInformationalArtifacts(this.$scope.component).subscribe((response:ComponentGenericResponse) => { + this.$scope.component.artifacts = response.artifacts; + this.initScope(); + this.$scope.isLoading = false; + }); + } else { + this.initScope(); + } + } + + private initScope = ():void => { + + this.$scope.isLoading = false; + this.$scope.sortBy = 'artifactDisplayName'; + this.$scope.reverse = false; + this.$scope.setValidState(true); + this.$scope.artifactType = 'informational'; + this.$scope.getTitle = ():string => { + return this.$filter("resourceName")(this.$scope.component.name) + ' Artifacts'; + + }; + + this.$scope.tableHeadersList = [ + {title: 'Name', property: 'artifactDisplayName'}, + {title: 'Type', property: 'artifactType'}, + {title: 'Version', property: 'artifactVersion'}, + {title: 'UUID', property: 'artifactUUID'} + ]; + + 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:ArtifactModel):void => { + artifact.artifactGroupType = 'INFORMATIONAL'; + this.ModalsHandler.openArtifactModal(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:ArtifactModel)=> { + return artifact.esId; + }); + + if (artifacts.length === 0) { + return true; + } + return false; + }; + + this.$scope.delete = (artifact: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/src/app/view-models/workspace/tabs/information-artifacts/information-artifacts-view.html b/catalog-ui/src/app/view-models/workspace/tabs/information-artifacts/information-artifacts-view.html new file mode 100644 index 0000000000..7c843e9fe8 --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/information-artifacts/information-artifacts-view.html @@ -0,0 +1,66 @@ +<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" data-tests-id="artifactDisplayName_{{artifact.artifactDisplayName}}"> + <span class="sprite table-arrow" data-ng-class="{'opened': artifact.selected}" data-tests-id="artifact_arrow_{{artifact.artifactDisplayName}}"></span> + {{artifact.artifactDisplayName}} + </div> + + <div class="table-col-general flex-item" data-ng-click="artifact.selected = !artifact.selected" data-tests-id="artifactType_{{artifact.artifactDisplayName}}"> + {{artifact.artifactType}} + </div> + + <div class="table-col-general flex-item" data-ng-click="artifact.selected = !artifact.selected" data-tests-id="artifactVersion_{{artifact.artifactDisplayName}}"> + {{artifact.artifactVersion}} + </div> + + <div class="table-col-general flex-item text" data-ng-click="artifact.selected = !artifact.selected" data-tests-id="artifactUUID_{{artifact.artifactDisplayName}}" + tooltips tooltip-content="{{artifact.artifactUUID}}"> + <span>{{artifact.artifactUUID}}</span> + </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/src/app/view-models/workspace/tabs/information-artifacts/information-artifacts.less b/catalog-ui/src/app/view-models/workspace/tabs/information-artifacts/information-artifacts.less new file mode 100644 index 0000000000..3ba9cf47d5 --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/information-artifacts/information-artifacts.less @@ -0,0 +1,57 @@ +.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; + } + + .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: 3; + } + + .flex-item:nth-child(4) { + flex-grow: 20; + } + + .flex-item:nth-child(5) { + flex-grow: 5; + padding-top: 10px; + } + + } + +} + + diff --git a/catalog-ui/src/app/view-models/workspace/tabs/inputs/inputs.less b/catalog-ui/src/app/view-models/workspace/tabs/inputs/inputs.less new file mode 100644 index 0000000000..eff5c5395b --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/inputs/inputs.less @@ -0,0 +1,225 @@ +.workspace-inputs { + + .sdc-workspace-container .w-sdc-main-right-container .w-sdc-main-container-body-content { + padding: 25px 8% 25px 8%; + } + + .w-sdc-main-container .w-sdc-main-right-container > div:first-child { + /* scroll fix */ + padding-bottom: 0px; + } + + + width: 100%; + display: flex; + + .text { + padding-left: 15px; + .no-overflow; + } + + .title-text { + color: @main_color_m; + .f-type._13_m; + .bold; + text-overflow: ellipsis; + overflow: hidden; + } + + .no-overflow { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + + .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; + .no-overflow; + } + } + + .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; + + } + + .table-container-flex { + + .flex-item { + line-height: 22px; + } + .expand-collapse-table-row { + + .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; + } + } + } + + .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; + } + + .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/src/app/view-models/workspace/tabs/inputs/resource-input/resource-inputs-view-model.ts b/catalog-ui/src/app/view-models/workspace/tabs/inputs/resource-input/resource-inputs-view-model.ts new file mode 100644 index 0000000000..49fedd6e21 --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/inputs/resource-input/resource-inputs-view-model.ts @@ -0,0 +1,117 @@ +'use strict'; +import {IWorkspaceViewModelScope} from "app/view-models/workspace/workspace-view-model"; +import {ComponentInstance, InstancesInputsOrPropertiesMapData, Resource, PropertyModel, InputModel} from "app/models"; +import {ModalsHandler} from "app/utils"; + +export interface IInputsViewModelScope extends IWorkspaceViewModelScope { + InstanceInputsProperties:InstancesInputsOrPropertiesMapData; //this is tha map object that hold the selected inputs and the inputs we already used + vfInstancesList:Array<ComponentInstance>; + component:Resource; + + onArrowPressed():void; + getInputPropertiesForInstance(instanceId:string, instance:ComponentInstance):ng.IPromise<boolean> ; + loadInputPropertiesForInstance(instanceId:string, input:InputModel):ng.IPromise<boolean> ; + openEditValueModal(input:InputModel):void; + openEditPropertyModal(property:PropertyModel):void; +} + +export class ResourceInputsViewModel { + + static '$inject' = [ + '$scope', + '$q', + 'ModalsHandler' + ]; + + constructor(private $scope:IInputsViewModelScope, private $q:ng.IQService, private ModalsHandler:ModalsHandler) { + this.initScope(); + this.$scope.updateSelectedMenuItem(); + } + + private initScope = ():void => { + + this.$scope.InstanceInputsProperties = new InstancesInputsOrPropertiesMapData(); + this.$scope.vfInstancesList = this.$scope.component.componentInstances; + + // Need to cast all inputs to InputModel for the search to work + let tmpInputs:Array<InputModel> = new Array<InputModel>(); + _.each(this.$scope.component.inputs, (input):void => { + tmpInputs.push(new 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: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 instance input in the left or right table, this function will load all properties of the selected input + */ + this.$scope.loadInputPropertiesForInstance = (instanceId:string, input:InputModel):ng.IPromise<boolean> => { + let deferred = this.$q.defer(); + + let onSuccess = (properties:Array<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<InputModel>) => { + + //disabled all the inputs in the left table + _.forEach(this.$scope.InstanceInputsProperties, (properties:Array<PropertyModel>) => { + _.forEach(properties, (property: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); + }; + + this.$scope.openEditValueModal = (input:InputModel) => { + this.ModalsHandler.openEditInputValueModal(input); + }; + + this.$scope.openEditPropertyModal = (property:PropertyModel):void => { + this.ModalsHandler.openEditPropertyModal(property, this.$scope.component, this.$scope.component.componentInstancesProperties[property.resourceInstanceUniqueId], false).then(() => { + }); + } + } +} diff --git a/catalog-ui/src/app/view-models/workspace/tabs/inputs/resource-input/resource-inputs-view.html b/catalog-ui/src/app/view-models/workspace/tabs/inputs/resource-input/resource-inputs-view.html new file mode 100644 index 0000000000..a0715973ab --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/inputs/resource-input/resource-inputs-view.html @@ -0,0 +1,86 @@ +<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" data-tests-id="input-instance-{{$index}}"> + <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" data-tests-id="empty-row" ng-if="instance.properties.length===0">No properties to display </div> + <div ng-repeat="property in instance.properties track by $index"> + <property-row property="property" instance-name="instance.name" on-name-clicked="openEditPropertyModal(property)"></property-row> + </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">VF 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"> + <input-row input="resourceInput" is-view-only='isViewOnly' + instance-name='resourceInput.name' + data-tests-id="resource-input-{{$index}}" + class="service-input-row" + on-name-clicked="openEditValueModal(resourceInput)" + ng-class="resourceInput.isNew ? 'new-input': ''"> + + </input-row> + </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"> + <property-row property="property" instance-name="property.name"></property-row> + </div> + </div> + + </perfect-scrollbar> + </div> + </div> + </div> +</div> diff --git a/catalog-ui/src/app/view-models/workspace/tabs/inputs/resource-input/resource-inputs.less b/catalog-ui/src/app/view-models/workspace/tabs/inputs/resource-input/resource-inputs.less new file mode 100644 index 0000000000..ebb32fbdb2 --- /dev/null +++ b/catalog-ui/src/app/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/src/app/view-models/workspace/tabs/inputs/service-input/service-inputs-view-model.ts b/catalog-ui/src/app/view-models/workspace/tabs/inputs/service-input/service-inputs-view-model.ts new file mode 100644 index 0000000000..f3b2de0943 --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/inputs/service-input/service-inputs-view-model.ts @@ -0,0 +1,378 @@ +'use strict'; +import {IWorkspaceViewModelScope} from "app/view-models/workspace/workspace-view-model"; +import {ComponentInstance, InstancesInputsOrPropertiesMapData, Service, IAppMenu, InputModel, PropertyModel, InputPropertyBase} from "app/models"; +import {DataTypesService} from "app/services"; +import {ModalsHandler, ResourceType} from "app/utils"; + + +interface IServiceInputsViewModelScope extends IWorkspaceViewModelScope { + + vfInstancesList:Array<ComponentInstance>; + instanceInputsMap:InstancesInputsOrPropertiesMapData; //this is tha map object that hold the selected inputs and the inputs we already used + instancePropertiesMap:InstancesInputsOrPropertiesMapData; + component:Service; + sdcMenu:IAppMenu; + isViewOnly:boolean; + isArrowDisabled:boolean; + onArrowPressed():void; + checkArrowState():void; + loadComponentInputs():void; + loadInstanceInputs(instance:ComponentInstance):ng.IPromise<boolean> ; + loadInstanceProperties(instance:ComponentInstance):ng.IPromise<boolean> ; + loadInputPropertiesForInstance(instanceId:string, input:InputModel):ng.IPromise<boolean> ; + loadInputInputs(input:InputModel):ng.IPromise<boolean>; + deleteInput(input:InputModel):void + openEditValueModal(input:InputModel):void; + openSelectPropertyDataTypeViewModel(instanceId:string, property:PropertyModel):void; + openEditPropertyDataTypeViewModel(property:PropertyModel):void; + dataTypesService:DataTypesService; +} + +export class ServiceInputsViewModel { + + static '$inject' = [ + '$scope', + '$q', + 'ModalsHandler', + 'Sdc.Services.DataTypesService' + ]; + + constructor(private $scope:IServiceInputsViewModelScope, + private $q:ng.IQService, + private ModalsHandler:ModalsHandler, + private DataTypesService:DataTypesService) { + this.initScope(); + this.$scope.updateSelectedMenuItem(); + this.$scope.isViewOnly = this.$scope.isViewMode(); + } + + + private getInputsOrPropertiesAlreadySelected = (instanceNormalizeName:string, arrayToFilter:Array<InputPropertyBase>):Array<any> => { + let alreadySelectedInput = []; + _.forEach(arrayToFilter, (inputOrProperty:InputPropertyBase) => { + let expectedServiceInputName = instanceNormalizeName + '_' + inputOrProperty.name; + let inputAlreadyInService = _.find(this.$scope.component.inputs, (serviceInput:InputModel) => { + //Checking if the input prefix is the instance name + '_' + property/input name (prefix because we don't need to check full name for complex dataType) + return serviceInput.name.substring(0, expectedServiceInputName.length) === expectedServiceInputName; + }); + if (inputAlreadyInService) { + inputOrProperty.isAlreadySelected = true; + alreadySelectedInput.push(inputOrProperty); + } else { + inputOrProperty.isAlreadySelected = false; + } + }); + return alreadySelectedInput; + }; + + + /* + * 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 disableEnableSelectedInputsOrPropertiesOnInit = (instance:ComponentInstance):void => { + + if (instance.originType === ResourceType.VF) { + this.$scope.instanceInputsMap[instance.uniqueId] = this.getInputsOrPropertiesAlreadySelected(instance.normalizedName, instance.inputs); + } else { + this.$scope.instancePropertiesMap[instance.uniqueId] = this.getInputsOrPropertiesAlreadySelected(instance.normalizedName, instance.properties); + } + }; + + /* + * Enable Input/Property after delete + */ + private enableInputsAfterDelete = (propertiesOrInputsDeletes:Array<InputPropertyBase>):void => { + + _.forEach(propertiesOrInputsDeletes, (deletedInputInput:InputPropertyBase) => { //Enable all component instance inputs deleted + + let inputOrPropertyDeleted:InputPropertyBase = _.find(this.$scope.instanceInputsMap[deletedInputInput.componentInstanceId], (inputOrProperty:InputPropertyBase) => { + return inputOrProperty.uniqueId === deletedInputInput.uniqueId; + }); + inputOrPropertyDeleted.isAlreadySelected = false; + delete _.remove(this.$scope.instanceInputsMap[deletedInputInput.componentInstanceId], {uniqueId: inputOrPropertyDeleted.uniqueId})[0]; + }); + }; + + /* + * Enable Input/Property after delete + */ + private enablePropertiesAfterDelete = (propertiesOrInputsDeletes:Array<InputPropertyBase>):void => { + + _.forEach(propertiesOrInputsDeletes, (deletedInputInput:InputPropertyBase) => { //Enable all component instance inputs deleted + let componentInstance = _.find(this.$scope.vfInstancesList, (instance:ComponentInstance) => { + return instance.uniqueId === deletedInputInput.componentInstanceId; + }); + let inputOrPropertyDeleted:InputPropertyBase = _.find(this.$scope.instancePropertiesMap[deletedInputInput.componentInstanceId], (inputOrProperty:InputPropertyBase) => { + return inputOrProperty.uniqueId === deletedInputInput.uniqueId; + }); + + let expectedName = componentInstance.normalizedName + '_' + inputOrPropertyDeleted.name; + let isAnotherInputExist = _.find(this.$scope.component.inputs, (input:InputModel) => { + return input.name.substring(0, expectedName.length) === expectedName; + }); + if (!isAnotherInputExist) { + inputOrPropertyDeleted.isAlreadySelected = false; + delete _.remove(this.$scope.instancePropertiesMap[deletedInputInput.componentInstanceId], {uniqueId: inputOrPropertyDeleted.uniqueId})[0]; + } + }); + }; + + private initScope = ():void => { + + this.$scope.instanceInputsMap = new InstancesInputsOrPropertiesMapData(); + this.$scope.instancePropertiesMap = new InstancesInputsOrPropertiesMapData(); + this.$scope.isLoading = true; + this.$scope.isArrowDisabled = true; + // 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<InputModel>) => { + instance.inputs = inputs; + this.disableEnableSelectedInputsOrPropertiesOnInit(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; + }; + + + this.$scope.loadInstanceProperties = (instance:ComponentInstance):ng.IPromise<boolean> => { + let deferred = this.$q.defer(); + + let onSuccess = (properties:Array<PropertyModel>) => { + instance.properties = properties; + this.disableEnableSelectedInputsOrPropertiesOnInit(instance); + deferred.resolve(true); + }; + + let onError = () => { + deferred.resolve(false); + }; + + if (!instance.properties) { + this.$scope.component.getComponentInstanceProperties(instance.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:InputModel):ng.IPromise<boolean> => { + let deferred = this.$q.defer(); + + let onSuccess = (properties:Array<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: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.getServiceInputInputsAndProperties(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<InputModel>) => { + + //disabled all the inputs in the left table + _.forEach(this.$scope.instanceInputsMap, (inputs:Array<InputModel>, instanceId:string) => { + _.forEach(inputs, (input:InputModel) => { + input.isAlreadySelected = true; + }); + }); + _.forEach(this.$scope.instancePropertiesMap, (properties:Array<PropertyModel>, instanceId:string) => { + _.forEach(properties, (property:PropertyModel) => { + property.isAlreadySelected = true; + }); + }); + this.addColorToItems(inputsCreated); + }; + + let onFailed = (error:any) => { + this.$scope.isArrowDisabled = false; + console.log("Error declaring input/property"); + }; + + this.$scope.isArrowDisabled = true; + this.$scope.component.createInputsFormInstances(this.$scope.instanceInputsMap, this.$scope.instancePropertiesMap).then(onSuccess, onFailed); + }; + + + /* Iterates through array of selected inputs and properties and returns true if there is at least one new selection on left */ + this.$scope.checkArrowState = ()=> { + + let newInputSelected:boolean = _.some(this.$scope.instanceInputsMap, (inputs:Array<InputModel>) => { + return _.some(inputs, (input:InputModel)=> { + return input.isAlreadySelected === false; + }); + }); + + let newPropSelected:boolean = _.some(this.$scope.instancePropertiesMap, (properties:Array<PropertyModel>) => { + return _.some(properties, (property:PropertyModel) => { + return property.isAlreadySelected === false; + }); + }); + + this.$scope.isArrowDisabled = !(newInputSelected || newPropSelected); + + }; + + this.$scope.deleteInput = (inputToDelete:InputModel):void => { + + let onDelete = ():void => { + + let onSuccess = (deletedInput:InputModel):void => { + if (deletedInput.inputs && deletedInput.inputs.length > 0) { // Enable input declared from input + this.enableInputsAfterDelete(deletedInput.inputs); + } + + if (deletedInput.properties && deletedInput.properties.length > 0) { // Enable properties + this.enablePropertiesAfterDelete(deletedInput.properties); + } + deletedInput.isDeleteDisabled = false; + this.$scope.checkArrowState(); + + }; + + let onFailed = (error:any):void => { + console.log("Error deleting input"); + inputToDelete.isDeleteDisabled = false; + }; + + inputToDelete.isDeleteDisabled = true; + this.addColorToItems([inputToDelete]); + this.$scope.component.deleteServiceInput(inputToDelete.uniqueId).then((deletedInput:InputModel):void => { + onSuccess(deletedInput); + }, onFailed); + }; + + // Get confirmation modal text from menu.json + let state = "deleteInput"; + let title:string = this.$scope.sdcMenu.alertMessages[state].title; + let message:string = this.$scope.sdcMenu.alertMessages[state].message.format([inputToDelete.name]); + + // Open confirmation modal + this.ModalsHandler.openAlertModal(title, message).then(onDelete); + }; + + this.$scope.openEditValueModal = (input:InputModel) => { + this.ModalsHandler.openEditInputValueModal(input); + }; + + this.$scope.openSelectPropertyDataTypeViewModel = (instanceId:string, property:PropertyModel) => { + //to open the select data type modal + let selectedInstance = _.find(this.$scope.vfInstancesList, {uniqueId: instanceId}); + this.DataTypesService.selectedInstance = selectedInstance; //set the selected instance on the service for compering the input name on the service & the complex property + this.DataTypesService.selectedComponentInputs = this.$scope.component.inputs; // set all the service inputs on the data type service + let filteredPropertiesMap = _.filter(this.$scope.instancePropertiesMap[instanceId], (instanceProperty)=> { + return instanceProperty.name == property.name; + });//get all properties under the specific property + this.DataTypesService.selectedPropertiesName = property.propertiesName; + + this.ModalsHandler.openSelectDataTypeModal(property, this.$scope.component, this.$scope.component.properties, filteredPropertiesMap).then((selectedProperty:PropertyModel)=> { + if (selectedProperty && selectedProperty.propertiesName) { + let propertyToUpdate:PropertyModel = _.find(selectedInstance.properties, {uniqueId: selectedProperty.uniqueId}); + let existingProperty:PropertyModel = (<PropertyModel>_.find(this.$scope.instancePropertiesMap[instanceId], {uniqueId: propertyToUpdate.uniqueId})); + + if (existingProperty) { + existingProperty.propertiesName = selectedProperty.propertiesName; + existingProperty.input = selectedProperty.input; + existingProperty.isAlreadySelected = false; + } else { + propertyToUpdate.propertiesName = selectedProperty.propertiesName; + propertyToUpdate.input = selectedProperty.input; + this.$scope.instancePropertiesMap[instanceId].push(propertyToUpdate); + + } + this.$scope.checkArrowState(); + + } + }); + }; + + + this.$scope.openEditPropertyDataTypeViewModel = (property:PropertyModel)=> { + this.ModalsHandler.openEditPropertyModal(property, this.$scope.component, this.$scope.component.properties, false).then(() => { + }); + } + }; + + private addColorToItems = (inputsCreated:Array<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/src/app/view-models/workspace/tabs/inputs/service-input/service-inputs-view.html b/catalog-ui/src/app/view-models/workspace/tabs/inputs/service-input/service-inputs-view.html new file mode 100644 index 0000000000..cb4d853f58 --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/inputs/service-input/service-inputs-view.html @@ -0,0 +1,134 @@ +<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="instance.originType=='VF' ? loadInstanceInputs(instance):loadInstanceProperties(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}}"> + <div data-ng-if="instance.originType=='VF'"> + + <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"> + <input-row input="input" + is-view-only='isViewOnly' + instance-id='instance.uniqueId' + instance-name='instance.name' + instance-inputs-map="instanceInputsMap" + on-checkbox-clicked="checkArrowState()"></input-row> + </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"> + <property-row property="property" instance-name="instance.name"></property-row> + </div> + </div> + </div> + <div data-ng-if="instance.originType!='VF'"> + <div class="empty-row" data-tests-id="empty-row" ng-if="instance.properties.length===0"> No + properties to display + </div> + <div ng-repeat="property in instance.properties track by $index"> + <property-row instance-properties-map="instancePropertiesMap" property="property" + on-name-clicked="openSelectPropertyDataTypeViewModel(instance.uniqueId,property)" + on-checkbox-clicked="checkArrowState()" + instance-name="instance.name" + instance-id='instance.uniqueId'></property-row> + </div> + </div> + </div> + + </perfect-scrollbar> + </div> + </div> + </div> + + <div class="inputs-button-container pull-left"> + <div ng-click="onArrowPressed()" ng-class="{disabled: isArrowDisabled || isViewOnly}" data-ng-disabled="isArrowDisabled || isViewOnly" 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 "> + <input-row input="serviceInput" is-view-only='isViewOnly' instance-name='serviceInput.name' + delete-input='deleteInput(serviceInput)' + data-tests-id="service-input-{{$index}}" + class="service-input-row" + on-name-clicked="openEditValueModal(serviceInput)" + ng-class="serviceInput.isNew ? 'new-input': ''" + ></input-row> + </expand-collapse> + + <div data-ng-repeat-end="" class="service-inputs {{$index}}"> + <div ng-if="serviceInput.inputs.length > 0"> + <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"> + <input-row input="input" + is-view-only='isViewOnly' + instance-name='input.componentInstanceName'></input-row> + </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"> + <property-row property="property" instance-name="property.name" is-clickable="false"></property-row> + </div> + </div> + </div> + <div ng-if="serviceInput.properties.length > 0"> + <div class="empty-row" ng-if="serviceInput.properties.length===0">No properties to display</div> + <div ng-repeat="property in serviceInput.properties track by $index"> + <property-row property="property" instance-name="property.name" is-clickable="false"></property-row> + </div> + </div> + </div> + + + </perfect-scrollbar> + </div> + </div> + </div> +</div> diff --git a/catalog-ui/src/app/view-models/workspace/tabs/inputs/service-input/service-inputs.less b/catalog-ui/src/app/view-models/workspace/tabs/inputs/service-input/service-inputs.less new file mode 100644 index 0000000000..f783d0b6d6 --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/inputs/service-input/service-inputs.less @@ -0,0 +1,47 @@ +.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; + + &.service-input-row { + background: @tlv_color_u; + &.new-input { + background: @tlv_color_v; + } + } + } + + + } + } + +} diff --git a/catalog-ui/src/app/view-models/workspace/tabs/management-workflow/management-workflow-view-model.ts b/catalog-ui/src/app/view-models/workspace/tabs/management-workflow/management-workflow-view-model.ts new file mode 100644 index 0000000000..b7428e990b --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/management-workflow/management-workflow-view-model.ts @@ -0,0 +1,127 @@ + 'use strict'; + import {ArtifactType} from "app/utils"; + import {ArtifactGroupModel} from "app/models"; + import {participant} from "app/view-models/workspace/tabs/network-call-flow/network-call-flow-view-model"; + import {IWorkspaceViewModelScope} from "app/view-models/workspace/workspace-view-model"; + import {ComponentGenericResponse} from "../../../../ng2/services/responses/component-generic-response"; + import {ComponentServiceNg2} from "../../../../ng2/services/component-services/component.service"; + + export interface IManagementWorkflowViewModelScope extends IWorkspaceViewModelScope { + vendorModel:VendorModel; + } + + export class VendorModel { + artifacts: ArtifactGroupModel; + serviceID: string; + readonly: boolean; + sessionID: string; + requestID: string; + diagramType: string; + participants:Array<participant>; + + constructor(artifacts: 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', + 'ComponentServiceNg2' + ]; + + constructor(private $scope:IManagementWorkflowViewModelScope, + private uuid4:any, + private ComponentServiceNg2: ComponentServiceNg2) { + + this.initInformationalArtifacts(); + this.$scope.updateSelectedMenuItem(); + } + + + private initInformationalArtifacts = ():void => { + if(!this.$scope.component.artifacts) { + this.$scope.isLoading = true; + this.ComponentServiceNg2.getComponentInformationalArtifacts(this.$scope.component).subscribe((response:ComponentGenericResponse) => { + this.$scope.component.artifacts = response.artifacts; + this.initScope(); + this.$scope.isLoading = false; + }); + } else { + this.initScope(); + } + } + + 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(ArtifactType.THIRD_PARTY_RESERVED_TYPES.WORKFLOW), + this.$scope.component.uniqueId, + this.$scope.isViewMode(), + this.$scope.user.userId, + this.uuid4.generate(), + ArtifactType.THIRD_PARTY_RESERVED_TYPES.WORKFLOW, + ManagementWorkflowViewModel.getParticipants() + ); + + this.$scope.thirdParty = true; + this.$scope.setValidState(true); + } + } diff --git a/catalog-ui/src/app/view-models/workspace/tabs/management-workflow/management-workflow-view.html b/catalog-ui/src/app/view-models/workspace/tabs/management-workflow/management-workflow-view.html new file mode 100644 index 0000000000..bd196daec8 --- /dev/null +++ b/catalog-ui/src/app/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/src/app/view-models/workspace/tabs/network-call-flow/network-call-flow-view-model.ts b/catalog-ui/src/app/view-models/workspace/tabs/network-call-flow/network-call-flow-view-model.ts new file mode 100644 index 0000000000..511c17eb60 --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/network-call-flow/network-call-flow-view-model.ts @@ -0,0 +1,79 @@ +'use strict'; +import {IWorkspaceViewModelScope} from "app/view-models/workspace/workspace-view-model"; +import {VendorModel} from "app/view-models/workspace/tabs/management-workflow/management-workflow-view-model"; +import {ResourceType, ArtifactType} from "app/utils"; +import {ComponentInstance} from "app/models"; +import {ComponentGenericResponse} from "../../../../ng2/services/responses/component-generic-response"; +import {ComponentServiceNg2} from "../../../../ng2/services/component-services/component.service"; + +export interface INetworkCallFlowViewModelScope extends IWorkspaceViewModelScope { + vendorMessageModel:VendorModel; +} + +export class participant { + name:string; + id:string; + + constructor(instance:ComponentInstance) { + this.name = instance.name; + this.id = instance.uniqueId; + } +} + + +export class NetworkCallFlowViewModel { + + static '$inject' = [ + '$scope', + 'uuid4', + 'ComponentServiceNg2' + ]; + + constructor(private $scope:INetworkCallFlowViewModelScope, + private uuid4:any, + private ComponentServiceNg2: ComponentServiceNg2) { + + this.initComponentInstancesAndInformationalArtifacts(); + this.$scope.updateSelectedMenuItem(); + } + + private getVFParticipantsFromInstances(instances:Array<ComponentInstance>):Array<participant> { + let participants = []; + _.forEach(instances, (instance)=> { + if (ResourceType.VF == instance.originType) { + participants.push(new participant(instance)); + } + }); + return participants; + } + + private initComponentInstancesAndInformationalArtifacts = ():void => { + if(!this.$scope.component.artifacts || !this.$scope.component.componentInstances) { + this.$scope.isLoading = true; + this.ComponentServiceNg2.getComponentInformationalArtifactsAndInstances(this.$scope.component).subscribe((response:ComponentGenericResponse) => { + this.$scope.component.artifacts = response.artifacts; + this.$scope.component.componentInstances = response.componentInstances; + this.initScope(); + this.$scope.isLoading = false; + }); + } else { + this.initScope(); + } + } + + private initScope():void { + this.$scope.vendorMessageModel = new VendorModel( + this.$scope.component.artifacts.filteredByType(ArtifactType.THIRD_PARTY_RESERVED_TYPES.NETWORK_CALL_FLOW), + this.$scope.component.uniqueId, + this.$scope.isViewMode(), + this.$scope.user.userId, + this.uuid4.generate(), + 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/src/app/view-models/workspace/tabs/network-call-flow/network-call-flow-view.html b/catalog-ui/src/app/view-models/workspace/tabs/network-call-flow/network-call-flow-view.html new file mode 100644 index 0000000000..6ce3e8e2b7 --- /dev/null +++ b/catalog-ui/src/app/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/src/app/view-models/workspace/tabs/product-hierarchy/product-hierarchy-view-model.ts b/catalog-ui/src/app/view-models/workspace/tabs/product-hierarchy/product-hierarchy-view-model.ts new file mode 100644 index 0000000000..233b12952b --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/product-hierarchy/product-hierarchy-view-model.ts @@ -0,0 +1,109 @@ +'use strict'; +import {ComponentFactory} from "app/utils"; +import {Product, IGroup, ISubCategory, IMainCategory} from "app/models"; +import {IWorkspaceViewModelScope} from "app/view-models/workspace/workspace-view-model"; +import {CacheService} from "app/services"; + +export interface IProductHierarchyScope extends IWorkspaceViewModelScope { + + categoriesOptions:Array<IMainCategory>; + product:Product; + isLoading:boolean; + showDropDown:boolean; + + onInputTextClicked():void; + onGroupSelected(category:IMainCategory, subcategory:ISubCategory, group: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:CacheService, + private ComponentFactory:ComponentFactory, + private $state:ng.ui.IStateService) { + + + this.$scope.product = <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<IGroup> = []; + _.forEach(this.$scope.product.categories, (category:IMainCategory) => { + _.forEach(category.subcategories, (subcategory:ISubCategory) => { + selectedGroup = selectedGroup.concat(subcategory.groupings); + }); + }); + _.forEach(this.$scope.categoriesOptions, (category:IMainCategory) => { + _.forEach(category.subcategories, (subcategory:ISubCategory) => { + _.forEach(subcategory.groupings, (group:ISubCategory) => { + let componentGroup:IGroup = _.find(selectedGroup, (componentGroupObj) => { + return componentGroupObj.uniqueId == group.uniqueId; + }); + if (componentGroup) { + group.isDisabled = true; + } + }); + }); + }); + }; + + private setFormValidation = ():void => { + + this.$scope.setValidState(true); + + }; + + private initScope = ():void => { + this.$scope.isLoading = false; + this.$scope.showDropDown = false; + this.initCategories(); + this.setFormValidation(); + + this.$scope.onGroupSelected = (category:IMainCategory, subcategory:ISubCategory, group: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:IMainCategory) => { + _.forEach(category.subcategories, (subcategory:ISubCategory) => { + let groupObj:IGroup = _.find(subcategory.groupings, (group) => { + return group.uniqueId === uniqueId; + }); + if (groupObj) { + groupObj.isDisabled = false; + } + }); + }); + } + }; +} diff --git a/catalog-ui/src/app/view-models/workspace/tabs/product-hierarchy/product-hierarchy-view.html b/catalog-ui/src/app/view-models/workspace/tabs/product-hierarchy/product-hierarchy-view.html new file mode 100644 index 0000000000..2335ad7c74 --- /dev/null +++ b/catalog-ui/src/app/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/src/app/view-models/workspace/tabs/product-hierarchy/product-hierarchy.less b/catalog-ui/src/app/view-models/workspace/tabs/product-hierarchy/product-hierarchy.less new file mode 100644 index 0000000000..c992558ed2 --- /dev/null +++ b/catalog-ui/src/app/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/src/app/view-models/workspace/tabs/properties/properties-view-model.ts b/catalog-ui/src/app/view-models/workspace/tabs/properties/properties-view-model.ts new file mode 100644 index 0000000000..923cc58a7e --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/properties/properties-view-model.ts @@ -0,0 +1,92 @@ +'use strict'; +import {IWorkspaceViewModelScope} from "app/view-models/workspace/workspace-view-model"; +import {PropertyModel} from "app/models"; +import {ModalsHandler} from "app/utils"; +import {COMPONENT_FIELDS} from "../../../../utils/constants"; +import {ComponentGenericResponse} from "../../../../ng2/services/responses/component-generic-response"; +import {ComponentServiceNg2} from "../../../../ng2/services/component-services/component.service"; + +interface IPropertiesViewModelScope extends IWorkspaceViewModelScope { + tableHeadersList:Array<any>; + reverse:boolean; + sortBy:string; + filteredProperties:Array<PropertyModel>; + + addOrUpdateProperty(property?:PropertyModel):void; + delete(property:PropertyModel):void; + sort(sortBy:string):void; +} + +export class PropertiesViewModel { + + static '$inject' = [ + '$scope', + '$filter', + 'ModalsHandler', + 'ComponentServiceNg2' + ]; + + + constructor(private $scope:IPropertiesViewModelScope, + private $filter:ng.IFilterService, + private ModalsHandler:ModalsHandler, + private ComponentServiceNg2:ComponentServiceNg2) { + this.initComponentProperties(); + this.$scope.updateSelectedMenuItem(); + } + + private initComponentProperties = ():void => { + + if(!this.$scope.component.properties) { + this.$scope.isLoading = true; + this.ComponentServiceNg2.getComponentProperties(this.$scope.component).subscribe((response:ComponentGenericResponse) => { + this.$scope.component.properties = response.properties; + this.initScope(); + this.$scope.isLoading = false; + }, () => { + this.$scope.isLoading = false; + }); + } else { + this.initScope(); + } + } + + private openEditPropertyModal = (property:PropertyModel):void => { + this.ModalsHandler.openEditPropertyModal(property, this.$scope.component, this.$scope.filteredProperties, false).then(() => { + }); + }; + + private initScope = ():void => { + + //let self = this; + this.$scope.filteredProperties = this.$scope.component.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?:PropertyModel):void => { + this.openEditPropertyModal(property ? property : new PropertyModel()); + }; + + this.$scope.delete = (property: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/src/app/view-models/workspace/tabs/properties/properties-view.html b/catalog-ui/src/app/view-models/workspace/tabs/properties/properties-view.html new file mode 100644 index 0000000000..d1e0582386 --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/properties/properties-view.html @@ -0,0 +1,59 @@ +<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=(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" tooltips tooltip-content="{{property.name}}"> + <a data-tests-id="propertyName_{{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" + tooltips tooltip-content="{{property.type}}"> + <span data-tests-id="propertyType_{{property.name}}"> {{property.type.replace("org.openecomp.datatypes.heat.","")}}</span> + </div> + <div class="table-col-general flex-item text" + tooltips tooltip-content="{{property.schema.property.type}}"> + <span data-tests-id="propertySchema_{{property.name}}"> {{property.schema.property.type.replace("org.openecomp.datatypes.heat.","")}}</span> + </div> + <div class="table-col-general flex-item text" tooltips tooltip-content="{{property.description}}"> + <span data-tests-id="propertyDescription_{{property.name}}" 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/src/app/view-models/workspace/tabs/properties/properties.less b/catalog-ui/src/app/view-models/workspace/tabs/properties/properties.less new file mode 100644 index 0000000000..3e8d6c3fbd --- /dev/null +++ b/catalog-ui/src/app/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/src/app/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities-view-model.ts b/catalog-ui/src/app/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities-view-model.ts new file mode 100644 index 0000000000..4ac99edf8a --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities-view-model.ts @@ -0,0 +1,147 @@ +/** + * Created by rcohen on 9/22/2016. + */ +'use strict'; +import {IWorkspaceViewModelScope} from "app/view-models/workspace/workspace-view-model"; +import {ModalsHandler} from "app/utils"; +import {Capability, PropertyModel, Requirement} from "app/models"; +import {ComponentGenericResponse} from "../../../../ng2/services/responses/component-generic-response"; +import {ComponentServiceNg2} from "../../../../ng2/services/component-services/component.service"; + +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<Requirement>; + capabilities:Array<Capability>; + mode:string; + filteredProperties:Array<Array<PropertyModel>>; + searchText:string; + + sort(sortBy:string, sortByTableDefined:SortTableDefined):void; + updateProperty(property:PropertyModel, indexInFilteredProperties:number):void; + allCapabilitiesSelected(selected:boolean):void; +} + +export class ReqAndCapabilitiesViewModel { + + static '$inject' = [ + '$scope', + '$filter', + 'ModalsHandler', + 'ComponentServiceNg2' + ]; + + + constructor(private $scope:IReqAndCapabilitiesViewModelScope, + private $filter:ng.IFilterService, + private ModalsHandler:ModalsHandler, + private ComponentServiceNg2: ComponentServiceNg2) { + + this.initCapabilitiesAndRequirements(); + this.$scope.updateSelectedMenuItem(); + } + + private initCapabilitiesAndRequirements = (): void => { + + if(!this.$scope.component.capabilities || !this.$scope.component.requirements) { + this.$scope.isLoading = true; + this.ComponentServiceNg2.getCapabilitiesAndRequirements(this.$scope.component.componentType, this.$scope.component.uniqueId).subscribe((response:ComponentGenericResponse) => { + this.$scope.component.capabilities = response.capabilities; + this.$scope.component.requirements = response.requirements; + this.initScope(); + this.$scope.isLoading = false; + }, () => { + this.$scope.isLoading = false; + }); + } else { + this.initScope(); + } + + } + + private openEditPropertyModal = (property:PropertyModel, indexInFilteredProperties:number):void => { + //...because there is not be api + _.forEach(this.$scope.filteredProperties[indexInFilteredProperties], (prop:PropertyModel)=> { + prop.readonly = true; + }); + this.ModalsHandler.openEditPropertyModal(property, this.$scope.component, this.$scope.filteredProperties[indexInFilteredProperties], false).then(() => { + + }); + }; + + 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<Requirement>, capName)=> { + this.$scope.requirements = this.$scope.requirements.concat(req); + }); + + this.$scope.capabilities = []; + _.forEach(this.$scope.component.capabilities, (cap:Array<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:PropertyModel, indexInFilteredProperties:number):void => { + this.openEditPropertyModal(property, indexInFilteredProperties); + }; + + this.$scope.allCapabilitiesSelected = (selected:boolean):void => { + _.forEach(this.$scope.capabilities, (cap:Capability)=> { + cap.selected = selected; + }); + }; + } +} + diff --git a/catalog-ui/src/app/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities-view.html b/catalog-ui/src/app/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities-view.html new file mode 100644 index 0000000000..3d6ccb9d00 --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities-view.html @@ -0,0 +1,143 @@ +<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" tooltips tooltip-content="{{req.name}}"> + <span data-tests-id="{{req.name}}">{{req.name}}</span> + </div> + <div class="table-col-general flex-item text" tooltips tooltip-content="{{req.capability}}"> + <span data-tests-id="{{req.capability}}">{{req.capability.substring("tosca.capabilities.".length)}}</span> + </div> + <div class="table-col-general flex-item text" tooltips tooltip-content="{{req.node}}"> + <span data-tests-id="{{req.node}}">{{req.node.substring("tosca.nodes.".length)}}</span> + </div> + <div class="table-col-general flex-item text" tooltips tooltip-content="{{req.relationship}}"> + <span data-tests-id="{{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" tooltips tooltip-content="{{req.minOccurrences}},{{req.maxOccurrences}}"> + <span data-tests-id="{{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" tooltips tooltip-content="{{capability.name}}"> + <span class="sprite-new arrow-up-small" data-ng-class="{'opened': capability.selected}"></span> + <span data-tests-id="{{capability.name}}">{{capability.name}}</span> + </div> + <div class="table-col-general flex-item text" tooltips tooltip-content="{{capability.type}}"> + <span data-tests-id="{{capability.type}}">{{capability.type.replace("tosca.capabilities.","")}}</span> + </div> + + <div class="table-col-general flex-item text" tooltips tooltip-content="{{capability.description}}"> + <span data-tests-id="{{capability.description}}">{{capability.description}}</span> + </div> + + <div class="table-col-general flex-item text" tooltips tooltip-content="{{capability.validSourceTypes.join(',')}}"> + <span data-tests-id="{{capability.validSourceTypes.join(',')}}">{{capability.validSourceTypes.join(',')}}</span> + </div> + + <div class="table-col-general flex-item text" tooltips tooltip-content="{{capability.minOccurrences}},{{capability.maxOccurrences}}"> + <span data-tests-id="{{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" tooltips tooltip-content="{{property.name}}"> + <a data-tests-id="{{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" tooltips tooltip-content="{{property.description}}"> + <span 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/src/app/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities.less b/catalog-ui/src/app/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities.less new file mode 100644 index 0000000000..9b52fad411 --- /dev/null +++ b/catalog-ui/src/app/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/src/app/view-models/workspace/tabs/tosca-artifacts/tosca-artifacts-view-model.ts b/catalog-ui/src/app/view-models/workspace/tabs/tosca-artifacts/tosca-artifacts-view-model.ts new file mode 100644 index 0000000000..06022786ee --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/tosca-artifacts/tosca-artifacts-view-model.ts @@ -0,0 +1,84 @@ +'use strict'; +import {ArtifactModel, IFileDownload} from "app/models"; +import {IWorkspaceViewModelScope} from "app/view-models/workspace/workspace-view-model"; +import {ComponentGenericResponse} from "../../../../ng2/services/responses/component-generic-response"; +import {ComponentServiceNg2} from "../../../../ng2/services/component-services/component.service"; +export interface IToscaArtifactsScope extends IWorkspaceViewModelScope { + artifacts:Array<ArtifactModel>; + tableHeadersList:Array<any>; + artifactType:string; + downloadFile:IFileDownload; + isLoading:boolean; + sortBy:string; + reverse:boolean; + + getTitle():string; + download(artifact:ArtifactModel):void; + sort(sortBy:string):void; + showNoArtifactMessage():boolean; +} + +export class ToscaArtifactsViewModel { + + static '$inject' = [ + '$scope', + '$filter', + 'ComponentServiceNg2' + ]; + + constructor(private $scope:IToscaArtifactsScope, + private $filter:ng.IFilterService, + private ComponentServiceNg2:ComponentServiceNg2) { + this.initToscaArtifacts(); + this.$scope.updateSelectedMenuItem(); + } + + private initToscaArtifacts = (): void => { + + if(!this.$scope.component.toscaArtifacts) { + this.$scope.isLoading = true; + this.ComponentServiceNg2.getComponentToscaArtifacts(this.$scope.component).subscribe((response:ComponentGenericResponse) => { + this.$scope.component.toscaArtifacts = response.toscaArtifacts; + this.initScope(); + this.$scope.isLoading = false; + }, () => { + this.$scope.isLoading = false; + }); + } else { + this.initScope(); + } + } + + private initScope = ():void => { + this.$scope.isLoading = false; + this.$scope.sortBy = 'artifactDisplayName'; + this.$scope.reverse = false; + this.$scope.setValidState(true); + this.$scope.artifactType = 'informational'; + this.$scope.getTitle = ():string => { + return this.$filter("resourceName")(this.$scope.component.name) + ' Artifacts'; + + }; + + this.$scope.tableHeadersList = [ + {title: 'Name', property: 'artifactDisplayName'}, + {title: 'Type', property: 'artifactType'}, + {title: 'Version', property: 'artifactVersion'} + ]; + + 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/src/app/view-models/workspace/tabs/tosca-artifacts/tosca-artifacts-view.html b/catalog-ui/src/app/view-models/workspace/tabs/tosca-artifacts/tosca-artifacts-view.html new file mode 100644 index 0000000000..5df6b5c11f --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/tosca-artifacts/tosca-artifacts-view.html @@ -0,0 +1,49 @@ +<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-tests-id="{{artifact.artifactDisplayName}}" + data-ng-class="{'selected': artifact.selected}"> + + <div class="table-col-general flex-item" data-ng-click="artifact.selected = !artifact.selected" data-tests-id="name-{{$index}}"> + <span class="sprite table-arrow" data-ng-class="{'opened': artifact.selected}"></span> + {{artifact.artifactDisplayName}} + </div> + + <div class="table-col-general flex-item" data-ng-click="artifact.selected = !artifact.selected" data-tests-id="type-{{$index}}"> + {{artifact.artifactType}} + </div> + + <div class="table-col-general flex-item" data-ng-click="artifact.selected = !artifact.selected" data-tests-id="version-{{$index}}"> + {{artifact.artifactVersion}} + </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" data-tests-id="details-{{$index}}"> + <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/src/app/view-models/workspace/tabs/tosca-artifacts/tosca-artifacts.less b/catalog-ui/src/app/view-models/workspace/tabs/tosca-artifacts/tosca-artifacts.less new file mode 100644 index 0000000000..6dfec2980f --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/tabs/tosca-artifacts/tosca-artifacts.less @@ -0,0 +1,78 @@ +.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; + } + + .flex-item:nth-child(4) { + 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/src/app/view-models/workspace/workspace-view-model.ts b/catalog-ui/src/app/view-models/workspace/workspace-view-model.ts new file mode 100644 index 0000000000..ff4636e002 --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/workspace-view-model.ts @@ -0,0 +1,718 @@ +/** + * Created by obarda on 3/30/2016. + */ +'use strict'; +import {IUserProperties, IAppMenu, Resource, Component} from "app/models"; +import { + WorkspaceMode, ComponentFactory, ChangeLifecycleStateHandler, Role, ComponentState, MenuItemGroup, MenuHandler, + MenuItem, ModalsHandler, States, EVENTS, CHANGE_COMPONENT_CSAR_VERSION_FLAG, ResourceType +} from "app/utils"; +import { + EventListenerService, + EntityService, + ProgressService, + CacheService, + LeftPaletteLoaderService +} from "app/services"; +import {FileUploadModel} from "../../directives/file-upload/file-upload"; + + +export interface IWorkspaceViewModelScope extends ng.IScope { + + isLoading:boolean; + isCreateProgress:boolean; + component:Component; + originComponent:Component; + componentType:string; + importFile:any; + leftBarTabs:MenuItemGroup; + isNew:boolean; + isFromImport:boolean; + isValidForm:boolean; + mode:WorkspaceMode; + breadcrumbsModel:Array<MenuItemGroup>; + sdcMenu:IAppMenu; + changeLifecycleStateButtons:any; + version:string; + versionsList:Array<any>; + changeVersion:any; + isComposition:boolean; + isDeployment:boolean; + $state:ng.ui.IStateService; + user:IUserProperties; + thirdParty:boolean; + disabledButtons:boolean; + menuComponentTitle:string; + progressService: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():Component; + setComponent(component: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; + isProductManager():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:Component, + private ComponentFactory:ComponentFactory, + private $state:ng.ui.IStateService, + private sdcMenu:IAppMenu, + private $q:ng.IQService, + private MenuHandler:MenuHandler, + private cacheService:CacheService, + private ChangeLifecycleStateHandler:ChangeLifecycleStateHandler, + private ModalsHandler:ModalsHandler, + private LeftPaletteLoaderService:LeftPaletteLoaderService, + private $filter:ng.IFilterService, + private EventListenerService:EventListenerService, + private EntityService:EntityService, + private Notification:any, + private $stateParams:any, + private progressService:ProgressService) { + + this.initScope(); + this.initAfterScope(); + } + + private role:string; + private components:Array<Component>; + + private initViewMode = ():WorkspaceMode => { + let mode = WorkspaceMode.VIEW; + + if (!this.$state.params['id']) { //&& !this.$state.params['vspComponent'] + mode = WorkspaceMode.CREATE; + } else { + if (this.$scope.component.lifecycleState === ComponentState.NOT_CERTIFIED_CHECKOUT && + this.$scope.component.lastUpdaterUserId === this.cacheService.get("user").userId) { + if (this.$scope.component.isProduct() && this.role == Role.PRODUCT_MANAGER) { + mode = WorkspaceMode.EDIT; + } + if ((this.$scope.component.isService() || this.$scope.component.isResource()) && this.role == Role.DESIGNER) { + mode = WorkspaceMode.EDIT; + } + } + } + return mode; + }; + + private initChangeLifecycleStateButtons = ():void => { + let state = this.$scope.component.isService() && (Role.OPS == this.role || 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 => { + return this.$scope.isEditMode() && + this.$state.current.data && this.$state.current.data.unsavedChanges; + }; + + private initLeftPalette = ():void => { + this.LeftPaletteLoaderService.loadLeftPanel(this.$scope.component.componentType); + }; + + private initScope = ():void => { + + this.$scope.component = this.injectComponent; + this.initLeftPalette(); + 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(States.WORKSPACE_COMPOSITION) > -1); + this.$scope.isDeployment = (this.$state.current.name.indexOf(States.WORKSPACE_DEPLOYMENT) > -1); + this.$scope.progressService = this.progressService; + + this.$scope.getComponent = ():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: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(); + let goToState = ():void => { + this.$state.go(state, { + id: this.$scope.component.uniqueId, + type: this.$scope.component.componentType.toLowerCase(), + components: this.components + }); + deferred.resolve(true); + }; + if (this.isNeedSave()) { + if (this.$scope.isValidForm) { + this.$scope.save().then(goToState); + } else { + console.log('form is not valid'); + deferred.reject(false); + } + } else if (this.$scope.isEditMode() && //this is a workaround for amdocs - we need to get the artifact in order to avoid saving the vf when moving from their tabs + (this.$state.current.name === States.WORKSPACE_MANAGEMENT_WORKFLOW || this.$state.current.name === States.WORKSPACE_NETWORK_CALL_FLOW)) { + let onGetSuccess = (component:Component) => { + this.$scope.isLoading = false; + // Update the components + this.$scope.component = component; + goToState(); + }; + let onFailed = () => { + this.EventListenerService.notifyObservers(EVENTS.ON_WORKSPACE_SAVE_BUTTON_ERROR); + this.$scope.isLoading = false; // stop the progress. + deferred.reject(false); + }; + this.$scope.component.getComponent().then(onGetSuccess, onFailed); + } else { + goToState(); + } + return deferred.promise; + }; + + this.$scope.setValidState = (isValid:boolean):void => { + this.$scope.isValidForm = isValid; + }; + + this.$scope.onVersionChanged = (selectedId:string):void => { + if (this.$state.current.data && this.$state.current.data.unsavedChanges) { + this.$scope.changeVersion.selectedVersion = _.find(this.$scope.versionsList, {versionId: this.$scope.component.uniqueId}); + } + this.$scope.isLoading = true; + this.$state.go(this.$state.current.name, { + id: selectedId, + type: this.$scope.componentType.toLowerCase(), + mode: 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(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 = () => { + _.first(this.$scope.leftBarTabs.menuItems).isDisabled = false;//enabled click on general tab (DE246274) + this.EventListenerService.notifyObservers(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.component.tags = _.without(this.$scope.component.tags, this.$scope.component.name);// for fix DE246217 + 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.ComponentFactory.createComponent(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:Component) => { + + this.showSuccessNotificationMessage(); + this.progressService.deleteProgressValue(this.$scope.component.uniqueId); + //update components for breadcrumbs + this.components.unshift(component); + this.$state.go(States.WORKSPACE_GENERAL, { + id: component.uniqueId, + type: component.componentType.toLowerCase(), + components: this.components + }); + + deferred.resolve(true); + }; + + let onSuccessUpdate = (component:Component) => { + this.$scope.isCreateProgress = false; + this.$scope.disabledButtons = false; + this.showSuccessNotificationMessage(); + 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; + + _.first(this.$scope.leftBarTabs.menuItems).isDisabled = true;//disabled click on general tab (DE246274) + + // 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(CHANGE_COMPONENT_CSAR_VERSION_FLAG)) { + (<Resource>this.$scope.component).csarVersion = this.cacheService.get(CHANGE_COMPONENT_CSAR_VERSION_FLAG); + this.cacheService.remove(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: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:Component, url:string):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 (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(CHANGE_COMPONENT_CSAR_VERSION_FLAG)) { + (<Resource>this.$scope.component).csarVersion = this.cacheService.get(CHANGE_COMPONENT_CSAR_VERSION_FLAG); + } + + //when checking out a minor version uuid remains + let bcComponent: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:MenuItem) => { + item.isDisabled = false; + }); + }; + + this.$scope.isViewMode = ():boolean => { + return this.$scope.mode === WorkspaceMode.VIEW; + }; + + this.$scope.isDesigner = ():boolean => { + return this.role == Role.DESIGNER; + }; + + this.$scope.isProductManager = ():boolean => { + return this.role == Role.PRODUCT_MANAGER; + }; + + this.$scope.isDisableMode = ():boolean => { + return this.$scope.mode === WorkspaceMode.VIEW && this.$scope.component.lifecycleState === ComponentState.NOT_CERTIFIED_CHECKIN; + }; + + this.$scope.showFullIcons = ():boolean => { + //we show revert and save icons only in general\icon view + return this.$state.current.name === States.WORKSPACE_GENERAL || + this.$state.current.name === States.WORKSPACE_ICONS; + }; + + this.$scope.isCreateMode = ():boolean => { + return this.$scope.mode === WorkspaceMode.CREATE; + }; + + this.$scope.isEditMode = ():boolean => { + return this.$scope.mode === WorkspaceMode.EDIT; + }; + + this.$scope.goToBreadcrumbHome = ():void => { + let bcHome:MenuItemGroup = this.$scope.breadcrumbsModel[0]; + this.$state.go(bcHome.menuItems[bcHome.selectedIndex].state); + }; + + this.$scope.showLifecycleIcon = ():boolean => { + return this.role == Role.DESIGNER || + this.role == 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() && Role.OPS != this.role && Role.GOVERNOR != this.role) { + result = false; + } + if (this.role === Role.PRODUCT_MANAGER && !this.$scope.component.isProduct()) { + result = false; + } + if ((this.role === Role.DESIGNER || this.role === Role.TESTER) + && this.$scope.component.isProduct()) { + result = false; + } + if (ComponentState.NOT_CERTIFIED_CHECKOUT === this.$scope.component.lifecycleState && this.$scope.isViewMode()) { + result = false; + } + if (ComponentState.CERTIFIED != this.$scope.component.lifecycleState && + (Role.OPS == this.role || Role.GOVERNOR == this.role)) { + result = false; + } + return result; + }; + + this.$scope.updateSelectedMenuItem = ():void => { + let selectedItem:MenuItem = _.find(this.$scope.leftBarTabs.menuItems, (item: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(States.WORKSPACE_COMPOSITION) > -1); + this.$scope.isDeployment = (newVal.indexOf(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 = ():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 MenuItem(text, null, States.WORKSPACE_GENERAL, 'goToState', [this.$state.params]); + }; + + private updateMenuItemByRole = (menuItems:Array<MenuItem>, role:string) => { + let tempMenuItems:Array<MenuItem> = new Array<MenuItem>(); + menuItems.forEach((item: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 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:MenuItem) => { + item.params = [item.state]; + item.callback = this.$scope.onMenuItemPressed; + item.isDisabled = (inCreateMode && States.WORKSPACE_GENERAL != item.state) || + (States.WORKSPACE_DEPLOYMENT === item.state && this.$scope.component.groups && this.$scope.component.groups.length === 0 && this.$scope.component.isResource()); + }); + + if (this.cacheService.get('breadcrumbsComponents')) { + this.initBreadcrumbs(); + } else { + let onSuccess = (components:Array<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:MenuItem) => { + item.params = [item.state]; + item.callback = this.$scope.onMenuItemPressed; + item.isDisabled = (States.WORKSPACE_GENERAL != item.state); + }); + } + + private enableMenuItems() { + this.$scope.leftBarTabs.menuItems.forEach((item:MenuItem) => { + item.params = [item.state]; + item.callback = this.$scope.onMenuItemPressed; + item.isDisabled = false; + }); + } + + private showSuccessNotificationMessage = ():void => { + this.Notification.success({ + message: this.$filter('translate')("IMPORT_VF_MESSAGE_CREATE_FINISHED_DESCRIPTION"), + title: this.$filter('translate')("IMPORT_VF_MESSAGE_CREATE_FINISHED_TITLE") + }); + }; + +} + + diff --git a/catalog-ui/src/app/view-models/workspace/workspace-view.html b/catalog-ui/src/app/view-models/workspace/workspace-view.html new file mode 100644 index 0000000000..dbb7fa6d63 --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/workspace-view.html @@ -0,0 +1,78 @@ +<div class="sdc-workspace-container"> + <loader data-display="isLoading"></loader> + <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}"> + <loader data-display="isCreateProgress" data-ng-show="isCreateProgress" relative="false"></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() || isProductManager()) && !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> + <ecomp-footer></ecomp-footer> +</div> diff --git a/catalog-ui/src/app/view-models/workspace/workspace.less b/catalog-ui/src/app/view-models/workspace/workspace.less new file mode 100644 index 0000000000..6cf024e335 --- /dev/null +++ b/catalog-ui/src/app/view-models/workspace/workspace.less @@ -0,0 +1,150 @@ +.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; + min-width: 200px; + } + } + + .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:calc(~'100% - @{action_nav_height}'); + + 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; + } + } + } +} + + +.properties-assignment .sdc-workspace-container .w-sdc-main-right-container .w-sdc-main-container-body-content{ + padding: 80px 2% 40px 2%; +}
\ No newline at end of file |