blob: fc1ac1a68ae1bc820611223dcd6120112ed321d7 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import {HIDE_INTERSTITIAL_SPINNER_EVENT} from 'brooklyn-ui-utils/interstitial-spinner/interstitial-spinner';
import template from './main.template.html';
import {EntityFamily} from '../../components/util/model/entity.model';
import {graphicalState} from './graphical/graphical.state';
import {yamlState} from './yaml/yaml.state';
import {graphicalEditEntityState} from './graphical/edit/entity/edit.entity.controller';
import {graphicalEditPolicyState} from './graphical/edit/policy/edit.policy.controller';
import {graphicalEditEnricherState} from './graphical/edit/enricher/edit.enricher.controller';
import {graphicalEditSpecState} from './graphical/edit/spec/edit.spec.controller';
import bottomSheetTemplate from './bottom-sheet.template.html';
import {ISSUE_LEVEL} from '../../components/util/model/issue.model';
const layers = [
{
id: 'locations',
label: 'Locations',
selector: '.node-location',
active: true
},
{
id: 'adjuncts',
label: 'Adjuncts',
selector: '.node-adjuncts',
active: true
},
{
id: 'memberspecs',
label: 'Member specs',
selector: '.spec-node-group',
active: true
},
{
id: 'relationships',
label: 'Relationships',
selector: '.relation-group',
active: true
}
];
const layerCacheKey = 'blueprint-composer.layers';
export function MainController($scope, $element, $log, $state, $stateParams, brBrandInfo, blueprintService, actionService, catalogApi, applicationApi, brSnackbar, brBottomSheet, edit, yaml, composerOverrides) {
$scope.$emit(HIDE_INTERSTITIAL_SPINNER_EVENT);
let vm = this;
if (edit && yaml) {
throw 'Cannot supply both YAML source and a catalog item to edit';
}
vm.mode = $state.current;
$scope.$on('$stateChangeSuccess', (event, toState)=>{
vm.mode = toState;
});
vm.layers = localStorage && localStorage.getItem(layerCacheKey) !== null ?
JSON.parse(localStorage.getItem(layerCacheKey)) :
layers;
$scope.$watch('vm.layers', ()=> {
vm.layers.forEach(layer => {
document.querySelectorAll(layer.selector).forEach(node => {
angular.element(node).css('display', layer.active ? 'block' : 'none');
});
});
if (localStorage) {
try {
localStorage.setItem(layerCacheKey, JSON.stringify(vm.layers));
} catch (ex) {
$log.error('Cannot save layers preferences: ' + ex.message);
}
}
}, true);
vm.parseError = false;
$scope.$on('yaml.lint', (event, error)=>{
$scope.$applyAsync(()=> {
vm.parseError = error;
});
});
$scope.$on('blueprint.reset', () => {
vm.saveToCatalogConfig = {};
blueprintService.reset();
$state.go($state.includes(yamlState) ? yamlState : graphicalState, {}, {inherit: false, reload: true});
});
$scope.$on('blueprint.deploy', () => {
vm.deployApplication();
});
vm.saveToCatalogConfig = {};
if (edit) {
vm.saveToCatalogConfig = Object.assign(vm.saveToCatalogConfig, {
version: edit.type.version,
template: edit.type.template || false,
itemType: edit.type.template || !edit.type.superTypes || edit.type.superTypes.contains("org.apache.brooklyn.api.entity.Application")
? 'application'
: 'entity',
description: edit.type.description,
iconUrl: edit.type.iconUrlSource || edit.type.iconUrl,
original: {
bundle: edit.bundle.symbolicName.replace(/^catalog-bom-/, ''),
symbolicName: edit.type.symbolicName,
name: edit.type.displayName,
versions: edit.versions,
itemType: edit.type.template ? 'template'
: !edit.type.superTypes || edit.type.superTypes.contains("org.apache.brooklyn.api.entity.Application") ? 'application'
: 'entity',
}
});
if (!edit.type.template) {
// if loading a templates, do NOT set name, bundle, symbolicName, versions
// or in other words, for other items, DO set these
vm.saveToCatalogConfig = Object.assign(vm.saveToCatalogConfig, vm.saveToCatalogConfig.original);
}
yaml = edit.type.plan.data;
}
vm.isGraphicalMode = () => {
return $state.includes(graphicalState.name);
};
vm.isYamlMode = () => {
return $state.includes(yamlState.name);
};
if (yaml) {
if (vm.isYamlMode()) {
// don't set blueprint; yaml mode will take from "initial yaml"
blueprintService.reset();
$scope.initialYaml = yaml;
} else {
try {
blueprintService.setFromYaml(yaml);
} catch (e) {
console.warn("YAML supplied for editing is not valid for a blueprint. It will be ignored unless opened in the YAML editor:", e);
blueprintService.reset();
$scope.initialYaml = yaml;
}
}
} else {
blueprintService.reset();
}
vm.deployApplication = function () {
if (blueprintService.hasIssues()) {
vm.displayIssues();
return;
}
deployApplication();
};
vm.getAllActions = () => {
return actionService.getActions();
}
vm.displayIssues = () => {
let isOnlyWarnings = blueprintService.getIssues().length === blueprintService.getIssues().filter(issue => issue.level === ISSUE_LEVEL.WARN).length;
let message = '';
let options = {};
if (isOnlyWarnings) {
message = 'Blueprint has some warnings, deployment might fail. Do you wish to proceed anyway?';
options = {
label: 'Deploy',
callback: deployApplication
}
} else {
message = 'Blueprint is not valid: there ' + (
blueprintService.getIssues().filter(issue => issue.level === ISSUE_LEVEL.ERROR).length > 1 ?
'are ' + blueprintService.getIssues().filter(issue => issue.level === ISSUE_LEVEL.ERROR).length + ' problems' :
'is 1 problem'
) + ' to fix';
}
brSnackbar.create(message, options);
let firstEntityWithIssue = blueprintService.getIssues().filter(issue => {
if (isOnlyWarnings) {
return issue.level === ISSUE_LEVEL.WARN;
} else {
return issue.level === ISSUE_LEVEL.ERROR;
}
})[0].entity;
switch (firstEntityWithIssue.family) {
case EntityFamily.ENTITY:
$state.go(graphicalEditEntityState, {entityId: firstEntityWithIssue._id});
return;
case EntityFamily.POLICY:
$state.go(graphicalEditPolicyState, {entityId: firstEntityWithIssue.parent._id, policyId: firstEntityWithIssue._id});
return;
case EntityFamily.ENRICHER:
$state.go(graphicalEditEnricherState, {entityId: firstEntityWithIssue.parent._id, enricherId: firstEntityWithIssue._id});
return;
case EntityFamily.SPEC:
$state.go(graphicalEditSpecState, {entityId: firstEntityWithIssue.parent._id, specId: firstEntityWithIssue._id});
return;
}
}
function deployApplication() {
let bottomSheetOpts = {
controller: ['$log', 'brBrandInfo', 'brBottomSheetInstance', 'blueprintService', 'applicationApi', bottomSheetController],
controllerAs: 'ctrl',
template: bottomSheetTemplate
};
brBottomSheet.open(bottomSheetOpts);
function bottomSheetController($log, brBrandInfo, brBottomSheetInstance, blueprintService, applicationApi) {
this.loading = true;
this.vendors = brBrandInfo.getVendorPackages();
this.close = ()=> {
brBottomSheetInstance.close('Closing deployment error reporting');
};
this.investigate = ()=> {
brBottomSheetInstance.updateMode('inset');
};
this.onClipboardSuccess = (e)=> {
angular.element(e.trigger).triggerHandler('copied');
e.clearSelection();
};
this.onClipboardError = (e)=> {
let message = '';
let actionKey = e.action === 'cut' ? 'X' : 'C';
if(/iPhone|iPad/i.test(navigator.userAgent)) {
message = 'No support :(';
}
else if(/Mac/i.test(navigator.userAgent)) {
message = 'Press ⌘-' + actionKey + ' to ' + e.action;
}
else {
message = 'Press Ctrl-' + actionKey + ' to ' + e.action;
}
brSnackbar.create(message);
};
let applicationToDeploy = blueprintService.getAsYaml();
applicationApi.createApplication(applicationToDeploy).then((success)=> {
$log.info('Application deployment ... success', success);
window.location.href = brBrandInfo.getAppDeployedUrl(success.entityId, success.entityId);
}).catch((error)=> {
$log.error('Application deployment ... failed', error);
this.error = {
title: 'Application deployment failed',
message: error.error.message,
details: error.error.details
};
}).finally(()=> {
this.loading = false;
});
}
}
// allow downstream to configure controller and scope here
(composerOverrides.configureMainController || function() {})(vm, $scope, $element);
}
export const mainState = {
name: 'main',
url: '/?bundleSymbolicName&bundleVersion&typeSymbolicName&typeVersion&yaml',
abstract: true,
template: template,
controller: ['$scope', '$element', '$log', '$state', '$stateParams', 'brBrandInfo', 'blueprintService', 'actionService', 'catalogApi', 'applicationApi', 'brSnackbar', 'brBottomSheet', 'edit', 'yaml', 'composerOverrides', MainController],
controllerAs: 'vm',
resolve: {
edit: ['$stateParams', 'blueprintLoaderApi', ($stateParams, blueprintLoaderApi) => blueprintLoaderApi.loadBlueprint($stateParams)],
yaml: ['$stateParams', 'blueprintLoaderApi', ($stateParams, blueprintLoaderApi) => blueprintLoaderApi.loadYaml($stateParams)]
}
};