summaryrefslogtreecommitdiffstats
path: root/catalog-ui/app/scripts/view-models/workspace/tabs/general
diff options
context:
space:
mode:
Diffstat (limited to 'catalog-ui/app/scripts/view-models/workspace/tabs/general')
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/general/general-view-model.ts379
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/general/general-view.html307
-rw-r--r--catalog-ui/app/scripts/view-models/workspace/tabs/general/general.less64
3 files changed, 750 insertions, 0 deletions
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/general/general-view-model.ts b/catalog-ui/app/scripts/view-models/workspace/tabs/general/general-view-model.ts
new file mode 100644
index 0000000000..f613648596
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/general/general-view-model.ts
@@ -0,0 +1,379 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * SDC
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+/// <reference path="../../../../references"/>
+module Sdc.ViewModels {
+
+ 'use strict';
+ import Resource = Sdc.Models.Components.Resource;
+ import ISubCategory = Sdc.Models.ISubCategory;
+ import IMainCategory = Sdc.Models.IMainCategory;
+ import ResourceType = Sdc.Utils.Constants.ResourceType;
+
+ export class Validation {
+ validationPattern:RegExp;
+ contactIdValidationPattern:RegExp;
+ tagValidationPattern:RegExp;
+ vendorValidationPattern:RegExp;
+ commentValidationPattern:RegExp;
+ projectCodeValidationPattern:RegExp;
+ }
+
+ export interface IGeneralScope extends IWorkspaceViewModelScope {
+ validation:Validation;
+ editForm:ng.IFormController;
+ categories: Array<IMainCategory>;
+ latestCategoryId: string;
+ latestVendorName: string;
+ importedFileExtension:any;
+ isCreate:boolean;
+ isShowFileBrowse:boolean;
+ isShowOnboardingSelectionBrowse:boolean;
+ importedToscaBrowseFileText:string;
+ importCsarProgressKey:string;
+ browseFileLabel:string;
+
+
+ onToscaFileChange():void
+ validateField(field:any):boolean;
+ validateName(isInit:boolean): void;
+ calculateUnique(mainCategory:string, subCategory:string):string; // Build unique string from main and sub category
+ onVendorNameChange(oldVendorName:string): void;
+ convertCategoryStringToOneArray(category:string, subcategory:string):Array<Models.IMainCategory>;
+ onCategoryChange():void;
+ openOnBoardingModal():void;
+ initCategoreis():void;
+ }
+
+ export class GeneralViewModel {
+
+ static '$inject' = [
+ '$scope',
+ 'Sdc.Services.CacheService',
+ 'ValidationPattern',
+ 'ContactIdValidationPattern',
+ 'TagValidationPattern',
+ 'VendorValidationPattern',
+ 'CommentValidationPattern',
+ 'ValidationUtils',
+ 'sdcConfig',
+ 'ProjectCodeValidationPattern',
+ '$state',
+ 'ModalsHandler',
+ 'EventListenerService',
+ 'Notification',
+ 'Sdc.Services.ProgressService',
+ '$interval',
+ '$filter',
+ '$timeout'
+ ];
+
+ constructor(private $scope:IGeneralScope,
+ private cacheService:Services.CacheService,
+ private ValidationPattern:RegExp,
+ private ContactIdValidationPattern:RegExp,
+ private TagValidationPattern:RegExp,
+ private VendorValidationPattern:RegExp,
+ private CommentValidationPattern:RegExp,
+ private ValidationUtils:Sdc.Utils.ValidationUtils,
+ private sdcConfig:Models.IAppConfigurtaion,
+ private ProjectCodeValidationPattern:RegExp,
+ private $state:ng.ui.IStateService,
+ private ModalsHandler: Sdc.Utils.ModalsHandler,
+ private EventListenerService:Services.EventListenerService,
+ private Notification:any,
+ private progressService:Sdc.Services.ProgressService,
+ protected $interval:any,
+ private $filter:ng.IFilterService,
+ private $timeout:ng.ITimeoutService
+ ){
+
+ this.registerToSuccessSaveEvent();
+ this.initScopeValidation();
+ this.initScopeMethods();
+ this.initScope();
+ this.$scope.updateSelectedMenuItem();
+ }
+
+ private registerToSuccessSaveEvent = ():void => {
+ // Register to save success to show notification to user.
+ this.EventListenerService.registerObserverCallback(Utils.Constants.EVENTS.ON_WORKSPACE_SAVE_BUTTON_SUCCESS, this.showSuccessNotificationMessage);
+
+ };
+
+ private showSuccessNotificationMessage = ():void => {
+ // In case we import CSAR. Notify user when import VF was finished.
+ this.Notification.success({
+ message: this.$filter('translate')("IMPORT_VF_MESSAGE_CREATE_FINISHED_DESCRIPTION"),
+ title: this.$filter('translate')("IMPORT_VF_MESSAGE_CREATE_FINISHED_TITLE")
+ });
+
+ //set the form Pristine after save to reset the unsaved changes (whit for dom reload)
+ this.$timeout(()=> {
+ if(this.$scope.editForm) {
+ this.$scope.editForm.$setPristine();
+ }
+ }, 500);
+
+ };
+
+
+
+ private initScopeValidation = ():void => {
+ this.$scope.validation = new Validation();
+ this.$scope.validation.validationPattern = this.ValidationPattern;
+ this.$scope.validation.contactIdValidationPattern = this.ContactIdValidationPattern;
+ this.$scope.validation.tagValidationPattern = this.TagValidationPattern;
+ this.$scope.validation.vendorValidationPattern = this.VendorValidationPattern;
+ this.$scope.validation.commentValidationPattern = this.CommentValidationPattern;
+ this.$scope.validation.projectCodeValidationPattern = this.ProjectCodeValidationPattern;
+ };
+
+ private initScope = ():void => {
+
+ // Work around to change the csar version
+ if (this.cacheService.get(Utils.Constants.CHANGE_COMPONENT_CSAR_VERSION_FLAG)) {
+ (<Resource>this.$scope.component).csarVersion = this.cacheService.get(Utils.Constants.CHANGE_COMPONENT_CSAR_VERSION_FLAG);
+ }
+
+ this.$scope.importedToscaBrowseFileText = this.$scope.component.name + " (" + (<Resource>this.$scope.component).csarVersion + ")";
+ this.$scope.importCsarProgressKey = "importCsarProgressKey";
+ this.$scope.browseFileLabel = this.$scope.component.isResource() && (<Resource>this.$scope.component).resourceType===ResourceType.VF? "Upload file" : "Upload VFC";
+ this.$scope.progressService = this.progressService;
+
+ // Workaround to short vendor name to 25 chars
+ // onboarding send 27 chars, and the validation pattern is 25 chars.
+ if (this.$scope.component.vendorName){
+ this.$scope.component.vendorName = this.$scope.component.vendorName.substr(0, 25);
+ }
+
+ // Init UIModel
+ this.$scope.component.tags = _.without(this.$scope.component.tags, this.$scope.component.name);
+
+ // Init categories
+ this.$scope.initCategoreis();
+
+ // Init the decision if to show file browse.
+ this.$scope.isShowFileBrowse = false;
+ if (this.$scope.component.isResource()){
+ let resource:Sdc.Models.Components.Resource = <Sdc.Models.Components.Resource>this.$scope.component;
+ console.log(resource.name + ": " + resource.csarUUID);
+ if (resource.importedFile){ // Component has imported file.
+ this.$scope.isShowFileBrowse = true;
+ }
+ if (this.$scope.isEditMode() && resource.resourceType== ResourceType.VF && !resource.csarUUID){
+ this.$scope.isShowFileBrowse = true;
+ }
+ };
+
+ // Init the decision if to show onboarding
+ this.$scope.isShowOnboardingSelectionBrowse = false;
+ if (this.$scope.component.isResource() &&
+ this.$scope.isEditMode() &&
+ (<Resource>this.$scope.component).resourceType== ResourceType.VF &&
+ (<Resource>this.$scope.component).csarUUID) {
+ this.$scope.isShowOnboardingSelectionBrowse = true;
+ }
+
+ //init file extensions based on the file that was imported.
+ if (this.$scope.component.isResource() && (<Resource>this.$scope.component).importedFile){
+ let fileName:string = (<Resource>this.$scope.component).importedFile.filename;
+ let fileExtension:string = fileName.split(".").pop();
+ if (this.sdcConfig.csarFileExtension.indexOf(fileExtension.toLowerCase()) !== -1){
+ this.$scope.importedFileExtension = this.sdcConfig.csarFileExtension;
+ (<Resource>this.$scope.component).importedFile.filetype="csar";
+ } else if (this.sdcConfig.toscaFileExtension.indexOf(fileExtension.toLowerCase()) !== -1){
+ (<Resource>this.$scope.component).importedFile.filetype="yaml";
+ this.$scope.importedFileExtension = this.sdcConfig.toscaFileExtension;
+ }
+ }else if(this.$scope.isEditMode()&& (<Resource>this.$scope.component).resourceType === ResourceType.VF){
+ this.$scope.importedFileExtension = this.sdcConfig.csarFileExtension;
+ //(<Resource>this.$scope.component).importedFile.filetype="csar";
+ }
+
+ this.$scope.setValidState(true);
+
+ this.$scope.calculateUnique = (mainCategory:string, subCategory:string):string => {
+ let uniqueId:string = mainCategory;
+ if (subCategory) {
+ uniqueId += "_#_" + subCategory; // Set the select category combobox to show the selected category.
+ }
+ return uniqueId;
+ };
+
+ //TODO remove this after handling contact in UI
+ if(this.$scope.component.isProduct() && this.$scope.isCreateMode()){
+ (<Models.Components.Product>this.$scope.component).contacts = [];
+ (<Models.Components.Product>this.$scope.component).contacts.push(this.cacheService.get("user").userId);
+ }else if(this.$scope.isCreateMode()){
+ this.$scope.component.contactId = this.cacheService.get("user").userId;
+ }
+
+ };
+
+ // Convert category string MainCategory_#_SubCategory to Array with one item (like the server except)
+ private convertCategoryStringToOneArray = ():Array<Models.IMainCategory> => {
+ let tmp = this.$scope.component.selectedCategory.split("_#_");
+ let mainCategory = tmp[0];
+ let subCategory = tmp[1];
+
+ // Find the selected category and add the relevant sub category.
+ let selectedMainCategory:IMainCategory = <Models.IMainCategory>_.find(this.$scope.categories, function (item) {
+ return item["name"] === mainCategory;
+
+ });
+
+ let mainCategoryClone = angular.copy(selectedMainCategory);
+ if (subCategory) {
+ let selectedSubcategory = <Models.ISubCategory>_.find(selectedMainCategory.subcategories, function (item) {
+ return item["name"] === subCategory;
+ });
+ mainCategoryClone['subcategories'] = [angular.copy(selectedSubcategory)];
+ }
+ let tmpSelected = <Models.IMainCategory> mainCategoryClone;
+
+ let result:Array<Models.IMainCategory> = [];
+ result.push(tmpSelected);
+
+ return result;
+ };
+
+ private updateComponentNameInBreadcrumbs = ():void => {
+ //update breadcrum after changing name
+ this.$scope.breadcrumbsModel[1].updateSelectedMenuItemText(this.$scope.component.getComponentSubType() + ': ' + this.$scope.component.name);
+ this.$scope.updateMenuComponentName(this.$scope.component.name);
+ };
+
+ private initScopeMethods = ():void => {
+
+ this.$scope.initCategoreis = ():void => {
+ if (this.$scope.componentType === Utils.Constants.ComponentType.RESOURCE) {
+ this.$scope.categories = this.cacheService.get('resourceCategories');
+
+ }
+ if (this.$scope.componentType === Utils.Constants.ComponentType.SERVICE) {
+ this.$scope.categories = this.cacheService.get('serviceCategories');
+ }
+ }
+
+ this.$scope.validateField = (field:any):boolean => {
+ if (field && field.$dirty && field.$invalid) {
+ return true;
+ }
+ return false;
+ };
+
+ this.$scope.openOnBoardingModal=():void => {
+ let csarUUID = (<Resource>this.$scope.component).csarUUID;
+ this.ModalsHandler.openOnboadrdingModal('Update', csarUUID).then(()=>{
+ // OK
+ this.$scope.uploadFileChangedInGeneralTab();
+ }, ()=>{
+ // ERROR
+ });
+ };
+
+ this.$scope.validateName = (isInit:boolean):void => {
+ if (isInit === undefined) {
+ isInit = false;
+ }
+
+ let name = this.$scope.component.name;
+ if (!name || name === "") {
+ if (this.$scope.editForm
+ && this.$scope.editForm["componentName"]
+ && this.$scope.editForm["componentName"].$error) {
+
+ // Clear the error name already exists
+ this.$scope.editForm["componentName"].$setValidity('nameExist', true);
+ }
+
+ return;
+ }
+ //?????????????????????????
+ let subtype:string = Utils.Constants.ComponentType.RESOURCE == this.$scope.componentType ? this.$scope.component.getComponentSubType() : undefined;
+
+ let onFailed = (response) => {
+ //console.info('onFaild', response);
+ //this.$scope.isLoading = false;
+ };
+
+ let onSuccess = (validation:Models.IValidate) => {
+ this.$scope.editForm["componentName"].$setValidity('nameExist', validation.isValid);
+ if(validation.isValid){
+ //update breadcrumb after changing name
+ this.updateComponentNameInBreadcrumbs();
+ }
+ };
+
+ if (isInit) {
+ // When page is init after update
+ if (this.$scope.component.name !== this.$scope.originComponent.name) {
+ if (!(this.$scope.componentType===Utils.Constants.ComponentType.RESOURCE && (<Resource>this.$scope.component).csarUUID!==undefined)
+ ){
+ this.$scope.component.validateName(name, subtype).then(onSuccess, onFailed);
+ }
+ }
+ } else {
+ // Validating on change (has debounce)
+ if (this.$scope.editForm
+ && this.$scope.editForm["componentName"]
+ && this.$scope.editForm["componentName"].$error
+ && !this.$scope.editForm["componentName"].$error.pattern
+ && this.$scope.component.name !== this.$scope.originComponent.name
+ ) {
+ if (!(this.$scope.componentType===Utils.Constants.ComponentType.RESOURCE && (<Resource>this.$scope.component).csarUUID!==undefined)
+ ){
+ this.$scope.component.validateName(name, subtype).then(onSuccess, onFailed);
+ }
+ } else if (this.$scope.component.name === this.$scope.originComponent.name) {
+ // Clear the error
+ this.$scope.editForm["componentName"].$setValidity('nameExist', true);
+ }
+ }
+ };
+
+ this.$scope.$watchCollection('component.name', (newData:any):void => {
+ this.$scope.validateName(false);
+ });
+
+ // Notify the parent if this step valid or not.
+ this.$scope.$watch("editForm.$valid", (newVal, oldVal) => {
+ this.$scope.setValidState(newVal);
+ });
+
+ this.$scope.$watch("editForm.$dirty", (newVal, oldVal) => {
+ if (newVal!==oldVal) {
+ this.$state.current.data.unsavedChanges = newVal && !this.$scope.isCreateMode();
+ }
+ });
+
+ this.$scope.onCategoryChange = ():void => {
+ this.$scope.component.categories = this.convertCategoryStringToOneArray();
+ this.$scope.component.icon = Utils.Constants.DEFAULT_ICON;
+ };
+
+ this.$scope.onVendorNameChange = (oldVendorName:string):void => {
+ if (this.$scope.component.icon === oldVendorName) {
+ this.$scope.component.icon = Utils.Constants.DEFAULT_ICON;
+ }
+ };
+ };
+ }
+}
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/general/general-view.html b/catalog-ui/app/scripts/view-models/workspace/tabs/general/general-view.html
new file mode 100644
index 0000000000..1c1d4fedad
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/general/general-view.html
@@ -0,0 +1,307 @@
+<div include-padding="true" class="sdc-workspace-general-step">
+
+ <form novalidate class="w-sdc-form" name="editForm">
+
+ <div class="w-sdc-form-section-container">
+
+ <!--------------------- IMPORT TOSCA FILE USING BROWSE (ALSO VFC) -------------------->
+ <div class="i-sdc-form-item" ng-if="isShowFileBrowse">
+ <label class="i-sdc-form-label required">{{browseFileLabel}}</label>
+ <file-upload id="fileUploadElement"
+ class="i-sdc-form-input"
+ element-name="fileElement"
+ element-disabled="{{!isCreateMode()&&!(isEditMode()&&component.resourceType=='VF')}} || {{isViewMode()}}"
+ form-element="editForm"
+ file-model="component.importedFile"
+ on-file-changed-in-directive="uploadFileChangedInGeneralTab"
+ extensions="{{importedFileExtension}}"
+ default-text="'Browse to select file'"
+ data-ng-class="{'error':!(isEditMode()&&component.resourceType=='VF') && (!editForm.fileElement.$valid || !component.importedFile.filename)}"></file-upload>
+ </div>
+
+ <!--------------------- IMPORT TOSCA FILE USING ONBOARDING -------------------->
+ <div class="i-sdc-form-item" ng-if="isShowOnboardingSelectionBrowse">
+ <label class="i-sdc-form-label required">Select VSP</label>
+ <div class="i-sdc-form-file-upload i-sdc-form-input">
+ <span class="i-sdc-form-file-name" data-tests-id="filename">{{(fileModel && fileModel.filename) || importedToscaBrowseFileText}}</span>
+ <div class="i-sdc-form-file-upload-x-btn" ng-click="cancel()" data-ng-show="fileModel.filename && fileModel.filename!=='' && elementDisabled!=='true'"></div>
+ <input type="button" name="fileElement"/>
+ <div class="file-upload-browse-btn" data-ng-click="openOnBoardingModal()" data-tests-id="browseButton">Browse</div>
+ </div>
+ </div>
+
+ <div class="input-error-file-upload" data-ng-show="component.importedFile && (!editForm.fileElement.$valid || !component.importedFile.filename)">
+ <!-- editForm.fileElement.$error.required <== Can not use this, because the browse is done from outside for the first time -->
+ <span ng-show="!(isEditMode()&&component.resourceType=='VF')&&!component.importedFile.filename" translate="NEW_SERVICE_RESOURCE_ERROR_TOSCA_FILE_REQUIRED"></span><!-- Required -->
+ <span ng-show="editForm.fileElement.$error.maxsize" translate="VALIDATION_ERROR_MAX_FILE_SIZE"></span>
+ <span ng-show="editForm.fileElement.$error.filetype" translate="NEW_SERVICE_RESOURCE_ERROR_VALID_TOSCA_EXTENSIONS" translate-values="{'extensions': '{{importedFileExtension}}' }"></span>
+ <span ng-show="editForm.fileElement.$error.emptyFile" translate="VALIDATION_ERROR_EMPTY_FILE"></span>
+ </div>
+ <!--------------------- IMPORT TOSCA FILE -------------------->
+
+ <div class="w-sdc-form-columns-wrapper">
+
+ <div class="w-sdc-form-column">
+
+ <!--------------------- NAME -------------------->
+ <div class="i-sdc-form-item" data-ng-class="{'error': validateField(editForm.componentName)}">
+ <label class="i-sdc-form-label required">Name</label>
+ <input class="i-sdc-form-input"
+ data-ng-class="{'view-mode': isViewMode()}"
+ name="componentName"
+ data-ng-init="isCreateMode() && validateName(true)"
+ data-ng-maxlength="{{component.isProduct()?'25':'50'}}"
+ maxlength="{{component.isProduct()?'25':'50'}}"
+ data-ng-minlength="{{component.isProduct()?'4':'0'}}"
+ minlength="{{component.isProduct()?'4':'0'}}"
+ data-ng-model="component.name"
+ type="text"
+ data-required
+ data-ng-model-options="{ debounce: 500 }"
+ data-ng-pattern="validation.validationPattern"
+ data-ng-disabled="component.isAlreadyCertified()"
+ data-tests-id="name"
+ autofocus
+ ng-readonly="isViewMode()"
+ />
+
+ <div class="input-error" data-ng-show="validateField(editForm.componentName)">
+ <span ng-show="editForm.componentName.$error.required" translate="NEW_SERVICE_RESOURCE_ERROR_RESOURCE_NAME_REQUIRED"></span>
+ <span ng-show="editForm.componentName.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '50' }"></span>
+ <span ng-show="editForm.componentName.$error.minlength" translate="VALIDATION_ERROR_MIN_LENGTH" translate-values="{'min': '4' }"></span>
+ <span ng-show="editForm.componentName.$error.nameExist" translate="NEW_SERVICE_RESOURCE_ERROR_NAME_EXISTS"></span>
+ <span ng-show="editForm.componentName.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span>
+ </div>
+ </div>
+ <!--------------------- NAME -------------------->
+
+ <!--------------------- FULL NAME -------------------->
+ <div ng-if="component.isProduct()" class="i-sdc-form-item" data-ng-class="{'error': validateField(editForm.fullName)}">
+ <label class="i-sdc-form-label required">Full Name</label>
+ <input class="i-sdc-form-input"
+ name="fullName"
+ data-ng-class="{'view-mode': isViewMode()}"
+ data-ng-change="validateName()"
+ data-ng-maxlength="100"
+ maxlength="100"
+ data-ng-minlength="4"
+ minlength="4"
+ data-ng-model="component.fullName"
+ type="text"
+ data-required
+ data-ng-model-options="{ debounce: 500 }"
+ data-ng-pattern="validation.commentValidationPattern"
+ data-tests-id="fullName"
+ autofocus
+ ng-readonly="isViewMode()"
+ />
+
+ <div class="input-error" data-ng-show="validateField(editForm.fullName)">
+ <span ng-show="editForm.fullName.$error.required" translate="NEW_SERVICE_RESOURCE_ERROR_RESOURCE_NAME_REQUIRED"></span>
+ <span ng-show="editForm.fullName.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '50' }"></span>
+ <span ng-show="editForm.fullName.$error.minlength" translate="VALIDATION_ERROR_MIN_LENGTH" translate-values="{'min': '4' }"></span>
+ <span ng-show="editForm.fullName.$error.nameExist" translate="NEW_SERVICE_RESOURCE_ERROR_NAME_EXISTS"></span>
+ <span ng-show="editForm.fullName.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span>
+ </div>
+ </div>
+ <!--------------------- NAME -------------------->
+
+ <!--------------------- DESCRIPTION -------------------->
+ <div class="i-sdc-form-item"
+ data-ng-class="{'error': validateField(editForm.description)}">
+ <label class="i-sdc-form-label required">Description</label>
+ <textarea class="description"
+ name="description"
+ data-ng-class="{'view-mode': isViewMode()}"
+ data-ng-maxlength="1024"
+ data-required
+ data-ng-model="component.description"
+ data-ng-model-options="{ debounce: 500 }"
+ data-ng-pattern="validation.commentValidationPattern"
+ maxlength="1024"
+ data-tests-id="description"></textarea>
+ <!-- placeholder="Description here..." -->
+
+ <div class="input-error" data-ng-show="validateField(editForm.description)">
+ <span ng-show="editForm.description.$error.required" translate="NEW_SERVICE_RESOURCE_ERROR_RESOURCE_DESCRIPTION_REQUIRED"></span>
+ <span ng-show="editForm.description.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '1024' }"></span>
+ <span ng-show="editForm.description.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span>
+ </div>
+ </div>
+ <!--------------------- DESCRIPTION -------------------->
+
+ <!--------------------- CATEGORIES -------------------->
+ <div class="i-sdc-form-item"
+ data-ng-class="{'error': validateField(editForm.category)}"
+ data-ng-if="!component.isProduct()">
+ <loader data-display="!categories && !initCategoreis()" relative="true"></loader>
+ <label class="i-sdc-form-label required">Category</label>
+ <select class="i-sdc-form-select"
+ data-required
+ name="category"
+ data-ng-class="{'view-mode': isViewMode()}"
+ data-ng-change="onCategoryChange()"
+ data-ng-disabled="component.isAlreadyCertified() || (component.isCsarComponent() && component.selectedCategory && component.selectedCategory!=='')"
+ data-ng-model="component.selectedCategory"
+ data-tests-id="selectGeneralCategory"
+ >
+ <option value="">Select category</option>
+ <optgroup ng-if="component.isResource()" data-ng-repeat="mainCategory in categories | orderBy:['name']" label="{{mainCategory.name}}" data-tests-id="{{mainCategory.name}}">
+ <option data-ng-repeat="subCategory in mainCategory.subcategories track by $index"
+ data-ng-selected="component.selectedCategory === calculateUnique(mainCategory.name,subCategory.name)"
+ data-tests-id="{{subCategory.name}}"
+ value="{{calculateUnique(mainCategory.name, subCategory.name)}}">{{subCategory.name}}
+
+ </option>
+ </optgroup>
+ <option ng-if="component.isService()" data-ng-repeat="mainCategory in categories | orderBy:['name']"
+ data-ng-selected="component.selectedCategory===mainCategory.name"
+ value="{{mainCategory.name}}"
+ data-tests-id="{{mainCategory.name}}">{{mainCategory.name}}</option>
+ </select>
+
+ <div class="input-error" data-ng-show="validateField(editForm.category)">
+ <span ng-show="editForm.category.$error.required" translate="NEW_SERVICE_RESOURCE_ERROR_CATEGORY_REQUIRED"></span>
+ </div>
+ </div>
+ <!--------------------- CATEGORIES -------------------->
+
+ <!--------------------- PROJECT CODE -------------------->
+ <div class="i-sdc-form-item" data-ng-if="!component.isResource()"
+ data-ng-class="{'error': validateField(editForm.projectCode)}">
+ <label class="i-sdc-form-label required" translate="GENERAL_LABEL_PROJECT_CODE"></label>
+ <input class="i-sdc-form-input" type="text"
+ data-ng-model="component.projectCode"
+ data-ng-class="{'view-mode': isViewMode()}"
+ data-ng-model-options="{ debounce: 500 }"
+ data-ng-maxlength="128"
+ data-required
+ name="projectCode"
+ data-ng-pattern="validation.projectCodeValidationPattern"
+ maxlength="50"
+ data-tests-id="projectCode"
+ />
+
+ <div class="input-error" data-ng-show="validateField(editForm.projectCode)">
+ <span ng-show="editForm.projectCode.$error.required" translate="NEW_SERVICE_RESOURCE_ERROR_PROJECT_CODE_REQUIRED"></span>
+ <span ng-show="editForm.projectCode.$error.pattern" translate="NEW_SERVICE_RESOURCE_ERROR_PROJECT_CODE_NOT_VALID"></span>
+ </div>
+ </div>
+
+ <!--------------------- VENDOR NAME -------------------->
+ <div ng-if="component.isResource()" class="i-sdc-form-item" data-ng-class="{'error': validateField(editForm.vendorName)}">
+ <label class="i-sdc-form-label required">Vendor</label>
+ <input class="i-sdc-form-input" type="text"
+ data-ng-class="{'view-mode': isViewMode()}"
+ data-ng-model="component.vendorName"
+ data-ng-model-options="{ debounce: 500 }"
+ data-ng-maxlength="25"
+ data-required
+ ng-click="oldValue = component.vendorName"
+ name="vendorName"
+ data-ng-change="onVendorNameChange(oldValue)"
+ data-ng-pattern="validation.vendorValidationPattern"
+ maxlength="25"
+ data-ng-disabled="component.isAlreadyCertified() || (component.isCsarComponent() && component.vendorName && component.vendorName!=='')"
+ data-tests-id="vendorName"
+ />
+
+ <div class="input-error" data-ng-show="validateField(editForm.vendorName)">
+ <span ng-show="editForm.vendorName.$error.required" translate="NEW_SERVICE_RESOURCE_ERROR_VENDOR_NAME_REQUIRED"></span>
+ <span ng-show="editForm.vendorName.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '25' }"></span>
+ <span ng-show="editForm.vendorName.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span>
+ </div>
+
+ </div>
+
+ <!--------------------- VENDOR NAME -------------------->
+
+ <!--------------------- VENDOR RELEASE -------------------->
+ <div ng-if="component.isResource()"
+ class="i-sdc-form-item"
+ data-ng-class="{'error': validateField(editForm.vendorRelease)}">
+ <label class="i-sdc-form-label required">Vendor Release</label>
+ <input class="i-sdc-form-input" type="text"
+ data-ng-class="{'view-mode': isViewMode()}"
+ data-ng-model="component.vendorRelease"
+ data-ng-model-options="{ debounce: 500 }"
+ data-ng-maxlength="25"
+ data-required
+ name="vendorRelease"
+ data-ng-pattern="validation.vendorValidationPattern"
+ maxlength="25"
+ data-ng-disabled="component.isCsarComponent() && component.vendorRelease && component.vendorRelease!==''"
+ data-tests-id="vendorRelease"
+ />
+
+ <div class="input-error" data-ng-show="validateField(editForm.vendorRelease)">
+ <span ng-show="editForm.vendorRelease.$error.required" translate="NEW_SERVICE_RESOURCE_ERROR_VENDOR_RELEASE_REQUIRED"></span>
+ <span ng-show="editForm.vendorRelease.$error.maxlength" translate="VALIDATION_ERROR_MAX_LENGTH" translate-values="{'max': '128' }"></span>
+ <span ng-show="editForm.vendorRelease.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span>
+ </div>
+ </div>
+ <!--------------------- VENDOR RELEASE -------------------->
+
+
+
+ </div><!-- Close w-sdc-form-column -->
+
+ <div class="w-sdc-form-column">
+
+ <!--------------------- RESOURCE TAGS -------------------->
+ <div class="i-sdc-form-item" data-ng-class="{'error': validateField(editForm.tags)}">
+ <label class="i-sdc-form-label">Tags</label>
+
+ <sdc-tags form-element="editForm" element-name="tags" max-tags="20" class="i-sdc-form-item-tags"
+ sdc-disabled="isViewMode()"
+ tags="component.tags"
+ pattern="validation.tagValidationPattern"
+ special-tag="component.name"></sdc-tags>
+
+ <div class="input-error" data-ng-show="validateField(editForm.tags)">
+ <span ng-show="editForm.tags.$error.pattern" translate="VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED"></span>
+ <span ng-show="editForm.tags.$error.nameExist" translate="NEW_SERVICE_RESOURCE_ERROR_TAG_NAME_EXIST"></span>
+ </div>
+ </div>
+ <!--------------------- RESOURCE TAGS -------------------->
+
+ <!--------------------- CONTACT ID -------------------->
+ <div class="i-sdc-form-item" data-ng-class="{'error': validateField(editForm.contactId)}">
+ <label class="i-sdc-form-label " data-ng-class="{'required':!component.isProduct()}" translate="GENERAL_LABEL_CONTACT_ID"></label>
+ <input class="i-sdc-form-input" type="text" data-ng-if="!component.isProduct()"
+ data-ng-model="component.contactId"
+ data-ng-class="{'view-mode': isViewMode()}"
+ data-ng-required="!component.isProduct()"
+ name="contactId"
+ data-ng-pattern="validation.contactIdValidationPattern"
+ data-ng-model-options="{ debounce: 500 }"
+ data-tests-id="userId"
+ maxlength="50"
+ />
+ <input class="i-sdc-form-input" type="text" data-ng-if="component.isProduct()"
+ data-ng-model="component.contacts[0]"
+ data-ng-class="{'view-mode': isViewMode()}"
+ data-ng-required="!component.isProduct()"
+ name="contactId"
+ data-ng-pattern="validation.contactIdValidationPattern"
+ data-ng-model-options="{ debounce: 500 }"
+ data-tests-id="userId"
+ maxlength="50"
+ />
+
+ <div class="input-error" data-ng-show="validateField(editForm.contactId)">
+ <span ng-show="editForm.contactId.$error.required" translate="NEW_SERVICE_RESOURCE_ERROR_CONTACT_REQUIRED"></span>
+ <span ng-show="editForm.contactId.$error.pattern" translate="NEW_SERVICE_RESOURCE_ERROR_CONTACT_NOT_VALID"></span>
+ </div>
+ </div>
+ <!--------------------- CONTACT ID -------------------->
+
+
+ </div><!-- Close w-sdc-form-column -->
+ </div>
+
+ </div><!-- Close w-sdc-form-section-container -->
+
+ </form>
+
+</div>
diff --git a/catalog-ui/app/scripts/view-models/workspace/tabs/general/general.less b/catalog-ui/app/scripts/view-models/workspace/tabs/general/general.less
new file mode 100644
index 0000000000..1861d02e98
--- /dev/null
+++ b/catalog-ui/app/scripts/view-models/workspace/tabs/general/general.less
@@ -0,0 +1,64 @@
+.sdc-workspace-general-step {
+
+ .w-sdc-form {
+ padding: 0;
+
+ .i-sdc-form-file-upload{
+ input[type="button"] {
+ cursor: pointer;
+ display: block;
+ filter: alpha(opacity=0);
+ width: 100px;
+ height: 30px;
+ opacity: 0;
+ position: absolute;
+ right: 0;
+ text-align: right;
+ top: 0;
+ }
+
+ .file-upload-browse-btn {
+ .noselect;
+ .bg_n;
+ padding: 4px 6px;
+ cursor: pointer;
+ z-index: 999;
+ width: 100px;
+ height: 28px;
+ text-align: center;
+
+ &.disabled {
+ cursor: default;
+ }
+ }
+ }
+
+ .w-sdc-form-section-container {
+ text-align: center;
+ }
+
+ .i-sdc-form-item {
+ &.upload {
+ margin-top: 0;
+ width: auto;
+ padding: 10px;
+ }
+ }
+
+ .template-desc {
+ border: 1px dashed @border_color_f;
+ height: 130px;
+ overflow: hidden;
+ padding: 10px 6px 6px 6px;
+ margin-top: 10px;
+ }
+
+ .sdc-tag .tag {
+ max-width: 225px;
+ }
+
+ }
+
+}
+
+