Move an entire workbook to schema

This is a preparation for integrating with Barricade.js

Change-Id: Iba9424ddc8337497d5bb8431c114b1b07902f6b8
This commit is contained in:
Timur Sufiev 2015-01-15 20:53:04 +03:00
parent b408d89815
commit b922fad15b
14 changed files with 650 additions and 372 deletions

View File

@ -9,3 +9,6 @@ ADD_INSTALLED_APPS = ['merlin', 'mistral']
# Python panel class of the PANEL to be added. # Python panel class of the PANEL to be added.
ADD_PANEL = 'mistral.panel.MistralPanel' ADD_PANEL = 'mistral.panel.MistralPanel'
ADD_ANGULAR_MODULES = ['angular.filter']
ADD_JS_FILES = ['merlin/lib/angular-filter.js']

View File

@ -1,10 +1,16 @@
<div class="panel panel-default merlin-panel"> <div class="panel panel-default merlin-panel">
<div class="panel-heading"> <div ng-if="title">
<h4 class="panel-title"> <div class="panel-heading">
<a data-toggle="collapse" data-target="#elem-{$ $id $}" href="#">{$ title $}</a> <h4 class="panel-title">
<a href="#" ng-click="removable()"><i ng-show="removable" class="fa fa-times-circle pull-right"></i></a></h4> <a data-toggle="collapse" data-target="#elem-{$ $id $}" href="#">{$ title $}</a>
<a href="#" ng-click="onRemove()"><i ng-show="removable" class="fa fa-times-circle pull-right"></i></a></h4>
</div>
<div id="elem-{$ $id $}" class="panel-collapse collapse in">
<div class="panel-body" ng-transclude>
</div>
</div>
</div> </div>
<div id="elem-{$ $id $}" class="panel-collapse collapse in"> <div ng-if="!title">
<div class="panel-body" ng-transclude> <div class="panel-body" ng-transclude>
</div> </div>
</div> </div>

View File

@ -1,13 +1,15 @@
<div class="three-columns" ng-repeat="(key, value) in item[spec.name] track by key"> <collapsible-group title="{$ spec.title || makeTitle(spec.name) $}" on-add="add(item[spec.name], '')">
<div class="left-column"> <div class="three-columns" ng-repeat="(key, value) in item[spec.name] track by key">
<div class="form-group"> <div class="left-column">
<label for="elem-{$ $id $}.{$ key $}"> <div class="form-group">
<editable value="key" label="New Name"></editable> <label for="elem-{$ $id $}.{$ key $}">
</label> <editable value="key" label="New Name"></editable>
<div class="input-group"> </label>
<input id="elem-{$ $id $}.{$ key $}" type="text" class="form-control" ng-model="value"> <div class="input-group">
<i class="fa fa-minus-circle input-group-addon"></i> <input id="elem-{$ $id $}.{$ key $}" type="text" class="form-control" ng-model="value">
<i class="fa fa-minus-circle input-group-addon"></i>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </collapsible-group>

View File

@ -1,4 +1,4 @@
<collapsible-group title="{$ spec.title || makeTitle(spec.name) $}" additive="false"> <collapsible-group title="{$ spec.title || makeTitle(spec.name) $}">
<div class="three-columns"> <div class="three-columns">
<div class="left-column"> <div class="left-column">
<div class="form-group" ng-repeat="(key, item) in item[spec.name] track by key"> <div class="form-group" ng-repeat="(key, item) in item[spec.name] track by key">

View File

@ -0,0 +1,11 @@
<collapsible-group title="{$ spec.title || makeTitle(spec.name) $}" on-add="add(item[spec.name])">
<div ng-repeat="specs in spec.value | groupBy: 'row' | toArray: true | orderBy: '$key' track by specs.$key">
<div ng-class="{'three-columns': specs[0].row !== undefined }">
<div ng-repeat="spec in specs track by spec.name"
ng-class="{'right-column': $odd && isAtomic(spec.type), 'left-column': $even && isAtomic(spec.type)}">
<typed-field></typed-field>
<div class="clearfix" ng-show="$odd"></div>
</div>
</div>
</div>
</collapsible-group>

View File

@ -0,0 +1,4 @@
<div class="form-group">
<label for="elem-{$ $id $}">{$ spec.title || makeTitle(spec.name) $}</label>
<textarea class="form-control" id="elem-{$ $id $}" ng-model="item[spec.name]"></textarea>
</div>

View File

@ -1,4 +1,5 @@
<collapsible-group title="{$ spec.title || makeTitle(spec.name) $}" on-add="add(item[spec.name], {type: 'string', value: '', id: 'varlist'+item[spec.name].length})"> <collapsible-group title="{$ spec.title || makeTitle(spec.name) $}"
on-add="add(item[spec.name], {type: 'string', value: '', id: 'varlist'+item[spec.name].length})">
<div class="three-columns" ng-repeat="subItem in item[spec.name] track by subItem.id" <div class="three-columns" ng-repeat="subItem in item[spec.name] track by subItem.id"
ng-class="{dictionary: subItem.type == 'dictionary', list: subItem.type == 'list'}"> ng-class="{dictionary: subItem.type == 'dictionary', list: subItem.type == 'list'}">
<div class="left-column"> <div class="left-column">

View File

@ -0,0 +1,18 @@
<collapsible-group title="{$ spec.title || makeTitle(spec.name) $}" on-add="add(item[spec.name], '')">
<div class="three-columns">
<div class="left-column" style="display:none">
<div class="form-group">
<textarea class="form-control">{$ yaqlExpresion $}</textarea>
</div>
</div>
<div class="right-column">
<div class="form-group">
<div class="input-group">
<span class="input-group-addon yaql-condition fa fa-unlock" role="button"></span>
<input type="text" class="form-control" value="{$ value $}">
<span class="input-group-addon fa fa-minus-circle"></span>
</div>
</div>
</div>
</div>
</collapsible-group>

View File

@ -65,33 +65,219 @@
type: 'list', type: 'list',
value: ['', ''] value: ['', '']
}] }]
} }],
] workflows: [{
id: 'workflow1',
name: 'Workflow1',
base: '', // FIXME
input: [''],
output: [{
id: 'varlist1',
type: 'string',
value: ''
}],
taskDefaults: {
onError: {
type: 'list',
value: ['', '']
},
onSuccess: {
type: 'list',
value: ['']
},
onComplete: {
type: 'list',
value: ['', '']
}
}
}]
}; };
$scope.schema = { $scope.schema = {
action: [{ name: {
name: 'name',
type: 'string', type: 'string',
group: 'one' index: 0,
}, { panelIndex: 0,
name: 'base', row: 0
type: 'string', },
group: 'one' description: {
}, { type: 'text',
name: 'baseInput', index: 1,
type: 'frozendict', panelIndex: 0,
group: '' row: 0
}, { },
name: 'input', actions: {
type: 'list', index: 2,
group: '' type: 'panel',
}, { multiple: true,
name: 'output', value: {
type: 'varlist', name: {
group: '' type: 'string',
row: 0,
index: 0
},
base: {
type: 'string',
row: 0,
index: 1
},
baseInput: {
type: 'frozendict',
title: 'Base Input',
index: 2
},
input: {
type: 'list',
index: 3
},
output: {
type: 'varlist',
index: 4
}
}
},
workflows: {
index: 3,
type: 'panel',
multiple: true,
value: {
name: {
type: 'string',
index: 0,
row: 0
},
base: {
type: 'string',
index: 1,
row: 0
},
input: {
type: 'list',
index: 2
},
output: {
type: 'varlist',
index: 3
},
taskDefaults: {
type: 'group',
title: 'Task defaults',
additive: false,
index: 4,
value: {
onError: {
type: 'yaqllist',
title: 'On error',
index: 0
},
onSuccess: {
type: 'yaqllist',
title: 'On success',
index: 1
},
onComplete: {
type: 'yaqllist',
title: 'On complete',
index: 2
}
}
},
tasks: {
type: 'group',
index: 5,
value: {
task: {
type: 'group',
additive: false,
multiple: true,
index: 0,
value: {
name: {
type: 'string',
index: 0,
row: 0
},
type: {
type: 'string',
index: 1,
row: 0
},
action: {
type: 'string',
index: 2,
row: 1
},
input: {
type: 'dictionary',
index: 3
},
publish: {
type: 'dictionary',
index: 4
},
onError: {
type: 'yaqllist',
title: 'On error',
index: 5
},
onSuccess: {
type: 'yaqllist',
title: 'On success',
index: 6
},
onComplete: {
type: 'yaqllist',
title: 'On complete',
index: 7
},
policies: {
type: 'group',
additive: false,
index: 8,
value: {
waitBefore: {
type: 'string',
title: 'Wait before',
index: 0,
row: 0
},
waitAfter: {
type: 'string',
title: 'Wait after',
index: 1,
row: 0
},
timeout: {
type: 'string',
index: 2,
row: 1
},
retryCount: {
type: 'string',
title: 'Retry count',
index: 3,
row: 2
},
retryDelay: {
type: 'string',
title: 'Retry delay',
index: 4,
row: 2
},
retryBreakOn: {
type: 'string',
title: 'Retry break on',
index: 5,
row: 3
}
}
}
}
}
}
}
}
} }
]
}; };
$scope.makeTitle = function(str) { $scope.makeTitle = function(str) {
@ -107,7 +293,7 @@
}; };
$scope.isAtomic = function(type) { $scope.isAtomic = function(type) {
return ['string'].indexOf(type) > -1; return ['string', 'text'].indexOf(type) > -1;
}; };
$scope.remove = function(parent, item) { $scope.remove = function(parent, item) {

View File

@ -60,22 +60,25 @@
} }
}) })
.directive('collapsiblePanel', function($parse, defaultSetter) { .directive('panel', function() {
return { return {
restrict: 'E', restrict: 'E',
templateUrl: '/static/mistral/js/angular-templates/collapsible-panel.html', templateUrl: '/static/mistral/js/angular-templates/collapsible-panel.html',
transclude: true, transclude: true,
scope: { scope: {
title: '@', title: '@',
removable: '&' onRemove: '&'
}, },
link: function(scope, element, attrs) { link: function(scope, element, attrs) {
disableClickDefaultBehaviour(element); disableClickDefaultBehaviour(element);
if ( attrs.onRemove ) {
scope.removable = true;
}
} }
} }
}) })
.directive('collapsibleGroup', function($parse, defaultSetter) { .directive('collapsibleGroup', function() {
return { return {
restrict: 'E', restrict: 'E',
templateUrl: '/static/mistral/js/angular-templates/collapsible-group.html', templateUrl: '/static/mistral/js/angular-templates/collapsible-group.html',

View File

@ -1,324 +1,329 @@
/* Copyright (c) 2014 Mirantis, Inc. /* Copyright (c) 2014 Mirantis, Inc.
Licensed under the Apache License, Version 2.0 (the "License"); you may Licensed under the Apache License, Version 2.0 (the "License"); you may
not use this file except in compliance with the License. You may obtain not use this file except in compliance with the License. You may obtain
a copy of the License at a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations License for the specific language governing permissions and limitations
under the License. under the License.
*/ */
var types = { var types = {
Mistral: {}, Mistral: {},
base: {}, base: {},
OpenStack: { OpenStack: {
// TODO: obtain list of predefined OpenStack actions from Mistral server-side // TODO: obtain list of predefined OpenStack actions from Mistral server-side
// for now a stubbed list of predefined actions suffices // for now a stubbed list of predefined actions suffices
actions: ['createInstance', 'terminateInstance'] actions: ['createInstance', 'terminateInstance']
}, },
getOpenStackActions: function() { getOpenStackActions: function() {
return this.OpenStack.actions.slice(); return this.OpenStack.actions.slice();
} }
}; };
types.base.AcceptsMixin = Barricade.Blueprint.create(function (acceptsList) { types.base.AcceptsMixin = Barricade.Blueprint.create(function (acceptsList) {
acceptsList = acceptsList || []; acceptsList = acceptsList || [];
this.getLabels = function() { this.getLabels = function() {
return acceptsList.map(function(item) { return acceptsList.map(function(item) {
return item.label; return item.label;
}) })
}; };
this.getValue = function(label) { this.getValue = function(label) {
for ( var i = 0; i < acceptsList.length; i++ ) { for ( var i = 0; i < acceptsList.length; i++ ) {
if ( acceptsList[i].label === label ) { if ( acceptsList[i].label === label ) {
return acceptsList[i].value; return acceptsList[i].value;
} }
} }
return null; return null;
} }
}); });
types.Mistral.Action = Barricade.create({ types.Mistral.Action = Barricade.create({
'@type': Object, '@type': Object,
'@meta': {'groups': ['panel2']},
'name': {'@type': String}, 'name': {'@type': String},
'base': { 'base': {
'@type': String, '@type': String,
'@enum': function() { '@enum': function() {
var predefinedActions = types.getOpenStackActions(), var predefinedActions = types.getOpenStackActions(),
actions = workbook.get('actions'), actions = workbook.get('actions'),
currentItemIndex = actions.length() - 1; currentItemIndex = actions.length() - 1;
actions.each(function(index, actionItem) { actions.each(function(index, actionItem) {
var name = actionItem.get('name'); var name = actionItem.get('name');
if ( index < currentItemIndex && !name.isEmpty() ) { if ( index < currentItemIndex && !name.isEmpty() ) {
predefinedActions = predefinedActions.concat(name.get()) predefinedActions = predefinedActions.concat(name.get())
} }
}); });
return predefinedActions; return predefinedActions;
}, },
'@default': types.getOpenStackActions()[0] '@default': types.getOpenStackActions()[0]
}, },
'base-input': { 'base-input': {
'@type': Object, '@type': Object,
'@required': false, '@required': false,
'?': {'@type': String} '?': {'@type': String}
} }
}); });
types.Mistral.Policy = Barricade.create({ types.Mistral.Policy = Barricade.create({
'@type': Object, '@type': Object,
'wait-before': { 'wait-before': {
'@type': Number, '@type': Number,
'@required': false '@required': false
}, },
'wait-after': { 'wait-after': {
'@type': Number, '@type': Number,
'@required': false '@required': false
}, },
'retry': { 'retry': {
'@type': Object, '@type': Object,
'@required': false, '@required': false,
'count': {'@type': Number}, 'count': {'@type': Number},
'delay': {'@type': Number}, 'delay': {'@type': Number},
'break-on': { 'break-on': {
'@type': String, '@type': String,
'@required': false '@required': false
} }
}, },
'timeout': { 'timeout': {
'@type': Number, '@type': Number,
'@required': false '@required': false
} }
}); });
types.Mistral.Task = Barricade.create({ types.Mistral.Task = Barricade.create({
'@type': Object, '@type': Object,
'name': {'@type': String}, 'name': {'@type': String},
'input': { 'input': {
'@type': Array, '@type': Array,
'*': { '*': {
'@class': Barricade.Primitive.extend({ '@class': Barricade.Primitive.extend({
'name': 'Parameter' 'name': 'Parameter'
}, { }, {
'@type': String '@type': String
}) })
} }
}, },
'publish': { 'publish': {
'@type': String, '@type': String,
'@required': false '@required': false
}, },
'policies': { 'policies': {
'@class': types.Mistral.Policy, '@class': types.Mistral.Policy,
'@required': false '@required': false
} }
}); });
types.Mistral.Tasks = Barricade.MutableObject.extend({ types.Mistral.Tasks = Barricade.MutableObject.extend({
create: function(json, parameters) { create: function(json, parameters) {
var self = Barricade.MutableObject.create.call(this); var self = Barricade.MutableObject.create.call(this);
function getParentWorkflowType() { function getParentWorkflowType() {
var container = self._container, var container = self._container,
workflow; workflow;
while ( container ) { while ( container ) {
if ( container.instanceof(types.Mistral.Workflow) ) { if ( container.instanceof(types.Mistral.Workflow) ) {
workflow = container; workflow = container;
break; break;
} }
container = container._container; container = container._container;
} }
return workflow && workflow.get('type').get(); return workflow && workflow.get('type').get();
} }
var directSpecificData = { var directSpecificData = {
'on-complete': { 'on-complete': {
'@type': String, '@type': String,
'@required': false '@required': false
}, },
'on-success': { 'on-success': {
'@type': String, '@type': String,
'@required': false '@required': false
}, },
'on-error': { 'on-error': {
'@type': String, '@type': String,
'@required': false '@required': false
} }
}, },
reverseSpecificData = { reverseSpecificData = {
'requires': { 'requires': {
'@type': Array, '@type': Array,
'*': { '*': {
'@class': Barricade.Primitive.extend({ '@class': Barricade.Primitive.extend({
'name': 'Action' 'name': 'Action'
}, { }, {
'@type': String, '@type': String,
'@enum': function() { '@enum': function() {
var container = this._container, var container = this._container,
workflow, task; workflow, task;
while ( container ) { while ( container ) {
if ( container.instanceof(types.Mistral.Task) ) { if ( container.instanceof(types.Mistral.Task) ) {
task = container; task = container;
} }
if ( container.instanceof(types.Mistral.Workflow) ) { if ( container.instanceof(types.Mistral.Workflow) ) {
workflow = container; workflow = container;
break; break;
} }
container = container._container; container = container._container;
} }
if ( workflow && task ) { if ( workflow && task ) {
return workflow.get('tasks').toArray().filter(function(taskItem) { return workflow.get('tasks').toArray().filter(function(taskItem) {
return !(taskItem === task) && taskItem.get('name').get(); return !(taskItem === task) && taskItem.get('name').get();
}).map(function(taskItem) { }).map(function(taskItem) {
return taskItem.get('name').get(); return taskItem.get('name').get();
}); });
} else { } else {
return []; return [];
} }
} }
}) })
} }
} }
}; };
types.base.AcceptsMixin.call(self, [ types.base.AcceptsMixin.call(self, [
{ {
label: 'Action-based', label: 'Action-based',
value: function() { value: function() {
var workflowType = getParentWorkflowType(); var workflowType = getParentWorkflowType();
if ( workflowType === 'direct' ) { if ( workflowType === 'direct' ) {
return types.Mistral.ActionTask.extend({}, directSpecificData); return types.Mistral.ActionTask.extend({}, directSpecificData);
} else if ( workflowType === 'reverse' ) { } else if ( workflowType === 'reverse' ) {
return types.Mistral.ActionTask.extend({}, reverseSpecificData); return types.Mistral.ActionTask.extend({}, reverseSpecificData);
} else { } else {
return types.Mistral.ActionTask; return types.Mistral.ActionTask;
} }
} }
}, { }, {
label: 'Workflow-based', label: 'Workflow-based',
value: function() { value: function() {
var workflowType = getParentWorkflowType(); var workflowType = getParentWorkflowType();
if ( workflowType === 'direct' ) { if ( workflowType === 'direct' ) {
return types.Mistral.WorkflowTask.extend({}, directSpecificData); return types.Mistral.WorkflowTask.extend({}, directSpecificData);
} else if ( workflowType === 'reverse' ) { } else if ( workflowType === 'reverse' ) {
return types.Mistral.WorkflowTask.extend({}, reverseSpecificData); return types.Mistral.WorkflowTask.extend({}, reverseSpecificData);
} else { } else {
return types.Mistral.WorkflowTask; return types.Mistral.WorkflowTask;
} }
} }
} }
]); ]);
return self; return self;
} }
}, { }, {
'@type': Object, '@type': Object,
'?': {'@class': types.Mistral.Task} '?': {'@class': types.Mistral.Task}
}); });
types.Mistral.WorkflowTask = types.Mistral.Task.extend({}, types.Mistral.WorkflowTask = types.Mistral.Task.extend({},
{ {
'workflow': { 'workflow': {
'@type': String, '@type': String,
'@enum': function() { '@enum': function() {
var workflows = workbook.get('workflows').toArray(); var workflows = workbook.get('workflows').toArray();
return workflows.map(function(workflowItem) { return workflows.map(function(workflowItem) {
return workflowItem.get('name').get(); return workflowItem.get('name').get();
}).filter(function (name) { }).filter(function (name) {
return name; return name;
}); });
} }
} }
}); });
types.Mistral.ActionTask = types.Mistral.Task.extend({}, types.Mistral.ActionTask = types.Mistral.Task.extend({},
{ {
'action': { 'action': {
'@type': String, '@type': String,
'@enum': function() { '@enum': function() {
var predefinedActions = types.getOpenStackActions(), var predefinedActions = types.getOpenStackActions(),
actions = workbook.get('actions').toArray(); actions = workbook.get('actions').toArray();
return predefinedActions.concat(actions.map(function(actionItem) { return predefinedActions.concat(actions.map(function(actionItem) {
return actionItem.get('name').get(); return actionItem.get('name').get();
}).filter(function(name) { }).filter(function(name) {
return name; } return name; }
)); ));
} }
} }
}); });
types.Mistral.Workflow = Barricade.create({ types.Mistral.Workflow = Barricade.create({
'@type': Object, '@type': Object,
'@meta': {'groups': 'panel3'},
'name': {'@type': String}, 'name': {'@type': String},
'type': { 'type': {
'@type': String, '@type': String,
'@enum': ['reverse', 'direct'], '@enum': ['reverse', 'direct'],
'@default': 'direct' '@default': 'direct'
}, },
'input': { 'input': {
'@type': Array, '@type': Array,
'@required': false, '@required': false,
'*': { '*': {
'@class': Barricade.Primitive.extend({ '@class': Barricade.Primitive.extend({
'name': 'Primitive' 'name': 'Primitive'
}, { }, {
'@type': String '@type': String
}) })
} }
}, },
'output': { 'output': {
'@type': String, '@type': String,
'@required': false '@required': false
}, },
'task-defaults': { 'task-defaults': {
'@type': Object, '@type': Object,
'@required': false, '@required': false,
'on-error': {'@type': String}, 'on-error': {'@type': String},
'on-success': {'@type': String}, 'on-success': {'@type': String},
'on-complete': {'@type': String}, 'on-complete': {'@type': String},
'policies': {'@class': types.Mistral.Policy} 'policies': {'@class': types.Mistral.Policy}
}, },
'tasks': { 'tasks': {
'@class': types.Mistral.Tasks '@class': types.Mistral.Tasks
} }
}); });
types.Mistral.Workbook = Barricade.create({ types.Mistral.Workbook = Barricade.create({
'@type': Object, '@type': Object,
'version': { 'version': {
'@type': Number, '@type': Number,
'@default': 2 '@meta': {'groups': ['panel1']},
}, '@default': 2
'name': { },
'@type': String 'name': {
}, '@type': String,
'description': { '@meta': {'groups': ['panel1']}
'@type': String, },
'@required': false 'description': {
}, '@type': String,
'actions': { '@meta': {'groups': ['panel1']},
'@type': Object, '@required': false
'@required': false, },
'?': { 'actions': {
'@class': types.Mistral.Action '@type': Object,
} '@required': false,
}, '?': {
'workflows': { '@class': types.Mistral.Action
'@type': Object, }
'?': { },
'@class': types.Mistral.Workflow 'workflows': {
} '@type': Object,
} '?': {
}); '@class': types.Mistral.Workflow
}
}
});

View File

@ -16,7 +16,8 @@
}) })
.run(function($http, $templateCache) { .run(function($http, $templateCache) {
var fields = ['dictionary', 'frozendict', 'list', 'string', 'varlist']; var fields = ['dictionary', 'frozendict', 'list', 'string',
'varlist', 'text', 'group', 'yaqllist'];
fields.forEach(function(field) { fields.forEach(function(field) {
var base = '/static/mistral/js/angular-templates/fields/'; var base = '/static/mistral/js/angular-templates/fields/';
$http.get(base + field + '.html').success(function(templateContent) { $http.get(base + field + '.html').success(function(templateContent) {
@ -25,4 +26,42 @@
}) })
}) })
.filter('prepareSchema', function($filter) {
var toArray = $filter('toArray'),
orderBy = $filter('orderBy');
function schemaToArray(schema) {
return angular.isArray(schema) ? schema : orderBy(
toArray(schema, true), 'index').map(function(item) {
item.name = item.$key;
if ( item.type === 'panel' ) {
item.panelIndex = item.index;
}
if ( item.type === 'panel' || item.type === 'group' ) {
item.value = schemaToArray(item.value);
}
return item;
});
}
return schemaToArray;
})
.filter('normalizePanels', function() {
return function(collection) {
return collection.map(function(panelSpec) {
if ( panelSpec[0].type === 'panel' ) {
var data = panelSpec[0];
panelSpec.length = data.value.length;
for ( var i = 0; i < panelSpec.length; i++ ) {
panelSpec[i] = data.value[i];
}
panelSpec.multiple = data.multiple;
panelSpec.name = data.name;
}
return panelSpec;
});
}
})
})(); })();

View File

@ -51,40 +51,34 @@
<!-- Data panel start --> <!-- Data panel start -->
<div class="two-panels"> <div class="two-panels">
<div class="left-panel"> <div class="left-panel">
<!-- root-level parameters --> <div ng-repeat="panelSpec in schema | prepareSchema | groupBy: 'panelIndex' | toArray:true | orderBy: '$key' | normalizePanels track by panelSpec.$key">
<div class="panel panel-default merlin-panel" id="panel0"> <panel ng-if="panelSpec.multiple" ng-repeat="item in data[panelSpec.name] track by item.id" title="{$ item.name $}" on-remove="remove('{$ panelSpec.name $}', item)">
<div class="panel-body"> <div ng-repeat="specs in panelSpec | groupBy: 'row' | toArray: true | orderBy: '$key' track by specs.$key">
<div class="two-columns"> <div ng-class="{'three-columns': specs[0].row !== undefined }">
<div class="left-column"> <div ng-repeat="spec in specs track by spec.name"
<div class="form-group"> ng-class="{'right-column': $even && isAtomic(spec.type), 'left-column': $odd && isAtomic(spec.type)}">
<label for="workbookName">Name</label> <typed-field></typed-field>
<input type="text" class="form-control" id="workbookName" placeholder="Workbook1"> <div class="clearfix" ng-show="$even"></div>
</div>
</div>
<div class="right-column">
<div class="form-group">
<label for="workbookDesc">Description</label>
<textarea class="form-control" id="workbookDesc" placeholder="Type a description here"></textarea>
</div> </div>
</div> </div>
</div> </div>
</div> </panel>
<panel ng-if="!panelSpec.multiple">
<!-- ugly duplication here -->
<div ng-repeat="specs in panelSpec | groupBy: 'row' | toArray: true | orderBy: '$key' track by specs.$key">
<div ng-class="{'two-columns': specs[0].row !== undefined }">
<div ng-repeat="spec in specs track by spec.name"
ng-class="{'right-column': $even && isAtomic(spec.type), 'left-column': $odd && isAtomic(spec.type)}">
<typed-field></typed-field>
<div class="clearfix" ng-show="$even"></div>
</div>
</div>
</div>
</panel>
</div> </div>
<!-- Action added -->
<collapsible-panel ng-controller="actionCtrl" ng-repeat="item in data.actions track by item.id" title="{$ item.name $}" removable="remove('actions', item)">
<div ng-repeat="specs in schema.action | groupBy: 'group' | toArray:true | orderBy: '-$key' track by specs.$key">
<div ng-class="{'three-columns': specs[0].group}">
<div ng-repeat="spec in specs track by spec.name"
ng-class="{'right-column': $even && isAtomic(spec.type), 'left-column': $odd && isAtomic(spec.type)}">
<typed-field></typed-field>
<div class="clearfix" ng-show="$even"></div>
</div>
</div>
</div>
</collapsible-panel>
<!-- panel with workflow --> <!-- panel with workflow -->
<div ng-controller="workflowsCtrl"> <div ng-controller="workflowsCtrl">
<collapsible-panel title="Workflow1" removable="true"> <panel title="Workflow1" removable="true">
<!-- 2 simple inputs in a single row --> <!-- 2 simple inputs in a single row -->
<div class="three-columns"> <div class="three-columns">
<div class="left-column"> <div class="left-column">
@ -267,7 +261,7 @@
</collapsible-group> </collapsible-group>
</collapsible-group> </collapsible-group>
</collapsible-group> </collapsible-group>
</collapsible-panel> </panel>
</div> </div>
</div> </div>
<!-- YAML Panel --> <!-- YAML Panel -->

File diff suppressed because one or more lines are too long