mirror of
https://github.com/ansible/awx.git
synced 2026-05-08 01:47:35 -02:30
Add initial support for workflow pause approve
This commit is contained in:
@@ -117,7 +117,7 @@ function TemplatesStrings (BaseString) {
|
|||||||
TOTAL_NODES: t.s('TOTAL NODES'),
|
TOTAL_NODES: t.s('TOTAL NODES'),
|
||||||
ADD_A_NODE: t.s('ADD A NODE'),
|
ADD_A_NODE: t.s('ADD A NODE'),
|
||||||
EDIT_TEMPLATE: t.s('EDIT TEMPLATE'),
|
EDIT_TEMPLATE: t.s('EDIT TEMPLATE'),
|
||||||
JOBS: t.s('JOBS'),
|
JOBS: t.s('Jobs'),
|
||||||
PLEASE_CLICK_THE_START_BUTTON: t.s('Please click the start button to build your workflow.'),
|
PLEASE_CLICK_THE_START_BUTTON: t.s('Please click the start button to build your workflow.'),
|
||||||
PLEASE_HOVER_OVER_A_TEMPLATE: t.s('Please hover over a template for additional options.'),
|
PLEASE_HOVER_OVER_A_TEMPLATE: t.s('Please hover over a template for additional options.'),
|
||||||
EDIT_LINK_TOOLTIP: t.s('Click to edit link'),
|
EDIT_LINK_TOOLTIP: t.s('Click to edit link'),
|
||||||
@@ -144,7 +144,8 @@ function TemplatesStrings (BaseString) {
|
|||||||
UNSAVED_CHANGES_PROMPT_TEXT: t.s('Are you sure you want to exit the Workflow Creator without saving your changes?'),
|
UNSAVED_CHANGES_PROMPT_TEXT: t.s('Are you sure you want to exit the Workflow Creator without saving your changes?'),
|
||||||
EXIT: t.s('EXIT'),
|
EXIT: t.s('EXIT'),
|
||||||
CANCEL: t.s('CANCEL'),
|
CANCEL: t.s('CANCEL'),
|
||||||
SAVE_AND_EXIT: t.s('SAVE & EXIT')
|
SAVE_AND_EXIT: t.s('SAVE & EXIT'),
|
||||||
|
PAUSE_NODE: t.s('Pause Node')
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1981,11 +1981,6 @@ tr td button i {
|
|||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.select2-container {
|
|
||||||
margin-left: 2px;
|
|
||||||
margin-top: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-control + .select2-container--disabled .select2-selection {
|
.form-control + .select2-container--disabled .select2-selection {
|
||||||
background-color: @ebgrey !important;
|
background-color: @ebgrey !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
@import 'action/_index';
|
@import 'action/_index';
|
||||||
|
@import 'approvalsDrawer/_index';
|
||||||
@import 'dialog/_index';
|
@import 'dialog/_index';
|
||||||
@import 'input/_index';
|
@import 'input/_index';
|
||||||
@import 'launchTemplateButton/_index';
|
@import 'launchTemplateButton/_index';
|
||||||
|
|||||||
56
awx/ui/client/lib/components/approvalsDrawer/_index.less
Normal file
56
awx/ui/client/lib/components/approvalsDrawer/_index.less
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
.at-ApprovalsDrawer {
|
||||||
|
position: absolute;
|
||||||
|
right: 0px;
|
||||||
|
top: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 540px;
|
||||||
|
background-color: white;
|
||||||
|
z-index: 1000000;
|
||||||
|
animation-duration: 0.5s;
|
||||||
|
// TODO: fix animation?
|
||||||
|
// animation-name: slidein;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: -3px 0px 8px -2px #aaaaaa;
|
||||||
|
|
||||||
|
&-header {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-title {
|
||||||
|
flex: 1 0 auto;
|
||||||
|
color: #606060;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
width: calc(82%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-exit {
|
||||||
|
justify-content: flex-end;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
button {
|
||||||
|
height: 20px;
|
||||||
|
font-size: 20px;
|
||||||
|
color: #D7D7D7;
|
||||||
|
line-height: 1;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover{
|
||||||
|
color: @default-icon;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slidein {
|
||||||
|
from {
|
||||||
|
width: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
width: 540px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
const templateUrl = require('~components/approvalsDrawer/approvalsDrawer.partial.html');
|
||||||
|
|
||||||
|
function AtApprovalsDrawerController (strings, Rest, GetBasePath, $rootScope) {
|
||||||
|
const vm = this || {};
|
||||||
|
|
||||||
|
const toolbarSortDefault = {
|
||||||
|
label: `${strings.get('sort.CREATED_ASCENDING')}`,
|
||||||
|
value: 'created'
|
||||||
|
};
|
||||||
|
|
||||||
|
vm.toolbarSortValue = toolbarSortDefault;
|
||||||
|
|
||||||
|
// This will probably need to be expanded
|
||||||
|
vm.toolbarSortOptions = [
|
||||||
|
toolbarSortDefault,
|
||||||
|
{ label: `${strings.get('sort.CREATED_DESCENDING')}`, value: '-created' }
|
||||||
|
];
|
||||||
|
|
||||||
|
vm.queryset = {
|
||||||
|
page_size: 5
|
||||||
|
};
|
||||||
|
|
||||||
|
vm.emptyListReason = strings.get('approvals.NONE');
|
||||||
|
|
||||||
|
const loadTheList = () => {
|
||||||
|
Rest.setUrl(`${GetBasePath('workflow_approval')}?page_size=5&order_by=created&status=pending`);
|
||||||
|
Rest.get()
|
||||||
|
.then(({ data }) => {
|
||||||
|
vm.dataset = data;
|
||||||
|
vm.approvals = data.results;
|
||||||
|
vm.count = data.count;
|
||||||
|
$rootScope.pendingApprovalCount = data.count;
|
||||||
|
vm.listLoaded = true;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
loadTheList();
|
||||||
|
|
||||||
|
vm.onToolbarSort = (sort) => {
|
||||||
|
vm.toolbarSortValue = sort;
|
||||||
|
|
||||||
|
// TODO: this...
|
||||||
|
// const queryParams = Object.assign(
|
||||||
|
// {},
|
||||||
|
// $state.params.user_search,
|
||||||
|
// paginateQuerySet,
|
||||||
|
// { order_by: sort.value }
|
||||||
|
// );
|
||||||
|
|
||||||
|
// // Update URL with params
|
||||||
|
// $state.go('.', {
|
||||||
|
// user_search: queryParams
|
||||||
|
// }, { notify: false, location: 'replace' });
|
||||||
|
|
||||||
|
// rather than ^^ we want to just re-load the data based on new params
|
||||||
|
};
|
||||||
|
|
||||||
|
vm.approve = (approval) => {
|
||||||
|
Rest.setUrl(`${GetBasePath('workflow_approval')}${approval.id}/approve`);
|
||||||
|
Rest.post()
|
||||||
|
.then(() => loadTheList());
|
||||||
|
};
|
||||||
|
|
||||||
|
vm.deny = (approval) => {
|
||||||
|
Rest.setUrl(`${GetBasePath('workflow_approval')}${approval.id}/deny`);
|
||||||
|
Rest.post()
|
||||||
|
.then(() => loadTheList());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
AtApprovalsDrawerController.$inject = ['ComponentsStrings', 'Rest', 'GetBasePath', '$rootScope'];
|
||||||
|
|
||||||
|
function atApprovalsDrawer () {
|
||||||
|
return {
|
||||||
|
restrict: 'E',
|
||||||
|
transclude: true,
|
||||||
|
templateUrl,
|
||||||
|
controller: AtApprovalsDrawerController,
|
||||||
|
controllerAs: 'vm',
|
||||||
|
scope: {
|
||||||
|
closeApprovals: '&'
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default atApprovalsDrawer;
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
<div style="width: 100%;height: 100%;position: fixed;top: 0;right: 0;opacity: 0.5;background-color: black;z-index: 10000;"></div>
|
||||||
|
<div class="at-ApprovalsDrawer" ng-if="vm.listLoaded">
|
||||||
|
<div class="at-ApprovalsDrawer-header">
|
||||||
|
<div class="at-ApprovalsDrawer-title">
|
||||||
|
<span>
|
||||||
|
NOTIFICATIONS
|
||||||
|
</span>
|
||||||
|
<span class="at-Panel-headingTitleBadge">
|
||||||
|
{{vm.count}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="at-ApprovalsDrawer-exit">
|
||||||
|
<button class="close" ng-click="closeApprovals()">
|
||||||
|
<i class="fa fa-times-circle"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<at-list-toolbar
|
||||||
|
ng-if="vm.approvals.length > 0"
|
||||||
|
sort-only="true"
|
||||||
|
sort-value="vm.toolbarSortValue"
|
||||||
|
sort-options="vm.toolbarSortOptions"
|
||||||
|
on-sort="vm.onToolbarSort">
|
||||||
|
</at-list-toolbar>
|
||||||
|
<at-list results="vm.approvals" id="approvals_list" empty-list-reason="{{ vm.emptyListReason }}">
|
||||||
|
<at-row ng-repeat="approval in vm.approvals"
|
||||||
|
id="approval-row-{{ approval.id }}">
|
||||||
|
<div class="at-Row-items">
|
||||||
|
<div class="at-Row-container">
|
||||||
|
<div class="at-Row-container">
|
||||||
|
<!-- TODO: translate header tag -->
|
||||||
|
<at-row-item
|
||||||
|
header-value="{{ approval.summary_fields.source_workflow_job.name }}"
|
||||||
|
header-state="workflowResults({id: {{approval.summary_fields.source_workflow_job.id}}})"
|
||||||
|
header-tag="Workflow Template">
|
||||||
|
</at-row-item>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="at-Row-container--wrapped">
|
||||||
|
<at-row-item
|
||||||
|
value-bind-html="{{ approval.created | longDate }}">
|
||||||
|
</at-row-item>
|
||||||
|
<at-row-item
|
||||||
|
style="color: red;"
|
||||||
|
value-bind-html="Expires: Never">
|
||||||
|
</at-row-item>
|
||||||
|
<!-- <at-row-item
|
||||||
|
style="color: red;"
|
||||||
|
value-bind-html="Expires {{ approval.created | longDate }}">
|
||||||
|
</at-row-item> -->
|
||||||
|
</div>
|
||||||
|
<div class="at-Row-container--wrapped">
|
||||||
|
<div style="display: flex; justify-content: flex-end; width: 100%; margin-top: 10px;">
|
||||||
|
<div>Continue workflow job?</div>
|
||||||
|
<button class="btn at-Button--success"
|
||||||
|
style="margin-left: 15px;"
|
||||||
|
ng-click="vm.approve(approval)"
|
||||||
|
type="button">
|
||||||
|
APPROVE
|
||||||
|
</button>
|
||||||
|
<button class="btn at-Button--error"
|
||||||
|
style="margin-left: 15px;"
|
||||||
|
ng-click="vm.deny(approval)"
|
||||||
|
type="button">
|
||||||
|
DENY
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</at-row>
|
||||||
|
</at-list>
|
||||||
|
<paginate
|
||||||
|
collection="vm.approvals"
|
||||||
|
dataset="vm.dataset"
|
||||||
|
iterator="template"
|
||||||
|
base-path="unified_job_templates"
|
||||||
|
query-set="vm.queryset">
|
||||||
|
</paginate>
|
||||||
|
</div>
|
||||||
@@ -119,6 +119,10 @@ function ComponentsStrings (BaseString) {
|
|||||||
EXPANDED: t.s('Expanded'),
|
EXPANDED: t.s('Expanded'),
|
||||||
SORT_BY: t.s('SORT BY')
|
SORT_BY: t.s('SORT BY')
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ns.approvals = {
|
||||||
|
NONE: t.s('There are no jobs awaiting approval')
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
ComponentsStrings.$inject = ['BaseStringService'];
|
ComponentsStrings.$inject = ['BaseStringService'];
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import atLibServices from '~services';
|
|||||||
|
|
||||||
import actionGroup from '~components/action/action-group.directive';
|
import actionGroup from '~components/action/action-group.directive';
|
||||||
import actionButton from '~components/action/action-button.directive';
|
import actionButton from '~components/action/action-button.directive';
|
||||||
|
import approvalsDrawer from '~components/approvalsDrawer/approvalsDrawer.directive';
|
||||||
import dialog from '~components/dialog/dialog.component';
|
import dialog from '~components/dialog/dialog.component';
|
||||||
import divider from '~components/utility/divider.directive';
|
import divider from '~components/utility/divider.directive';
|
||||||
import dynamicSelect from '~components/input/dynamic-select.directive';
|
import dynamicSelect from '~components/input/dynamic-select.directive';
|
||||||
@@ -60,6 +61,7 @@ angular
|
|||||||
])
|
])
|
||||||
.directive('atActionGroup', actionGroup)
|
.directive('atActionGroup', actionGroup)
|
||||||
.directive('atActionButton', actionButton)
|
.directive('atActionButton', actionButton)
|
||||||
|
.directive('atApprovalsDrawer', approvalsDrawer)
|
||||||
.component('atDialog', dialog)
|
.component('atDialog', dialog)
|
||||||
.directive('atDivider', divider)
|
.directive('atDivider', divider)
|
||||||
.directive('atDynamicSelect', dynamicSelect)
|
.directive('atDynamicSelect', dynamicSelect)
|
||||||
|
|||||||
@@ -81,6 +81,23 @@
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.at-Layout-topNavApprovals {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
div {
|
||||||
|
margin-left: 10px;
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 3px;
|
||||||
|
background-color: red;
|
||||||
|
color: white;
|
||||||
|
height: 15px;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-sideContainer {
|
&-sideContainer {
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ function AtLayoutController ($scope, $http, strings, ProcessErrors, $transitions
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$scope.$watch('$root.pendingApprovalCount', () => {
|
||||||
|
vm.approvalsCount = _.get($scope, '$root.pendingApprovalCount') || 0;
|
||||||
|
});
|
||||||
|
|
||||||
$scope.$watch('$root.socketStatus', (newStatus) => {
|
$scope.$watch('$root.socketStatus', (newStatus) => {
|
||||||
vm.socketState = newStatus;
|
vm.socketState = newStatus;
|
||||||
vm.socketIconClass = `icon-socket-${vm.socketState}`;
|
vm.socketIconClass = `icon-socket-${vm.socketState}`;
|
||||||
@@ -42,6 +46,14 @@ function AtLayoutController ($scope, $http, strings, ProcessErrors, $transitions
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
vm.openApprovals = () => {
|
||||||
|
vm.showApprovals = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
vm.closeApprovals = () => {
|
||||||
|
vm.showApprovals = false;
|
||||||
|
};
|
||||||
|
|
||||||
function checkOrgAdmin () {
|
function checkOrgAdmin () {
|
||||||
const usersPath = `/api/v2/users/${vm.currentUserId}/admin_of_organizations/`;
|
const usersPath = `/api/v2/users/${vm.currentUserId}/admin_of_organizations/`;
|
||||||
$http.get(usersPath)
|
$http.get(usersPath)
|
||||||
|
|||||||
@@ -14,6 +14,12 @@
|
|||||||
<span>{{ $parent.layoutVm.currentUsername }}</span>
|
<span>{{ $parent.layoutVm.currentUsername }}</span>
|
||||||
</a>
|
</a>
|
||||||
</at-top-nav-item>
|
</at-top-nav-item>
|
||||||
|
<at-top-nav-item ng-click="vm.openApprovals()">
|
||||||
|
<div class="at-Layout-topNavApprovals">
|
||||||
|
<i class="fa fa-bell" alt="{{ vm.getString('NOTIFICATIONS') }}"></i>
|
||||||
|
<div>{{vm.approvalsCount}}</div>
|
||||||
|
</div>
|
||||||
|
</at-top-nav-item>
|
||||||
<at-top-nav-item>
|
<at-top-nav-item>
|
||||||
<a ui-sref="about">
|
<a ui-sref="about">
|
||||||
<i class="fa fa-info-circle" alt="{{ vm.getString('ABOUT') }}"></i>
|
<i class="fa fa-info-circle" alt="{{ vm.getString('ABOUT') }}"></i>
|
||||||
@@ -104,4 +110,5 @@
|
|||||||
<ng-transclude></ng-transclude>
|
<ng-transclude></ng-transclude>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<at-approvals-drawer ng-if="vm.showApprovals" close-approvals="vm.closeApprovals()"></at-approvals-drawer>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import atLibServices from '~services';
|
import atLibServices from '~services';
|
||||||
|
|
||||||
import Application from '~models/Application';
|
|
||||||
import AdHocCommand from '~models/AdHocCommand';
|
import AdHocCommand from '~models/AdHocCommand';
|
||||||
|
import Application from '~models/Application';
|
||||||
import Base from '~models/Base';
|
import Base from '~models/Base';
|
||||||
import Config from '~models/Config';
|
import Config from '~models/Config';
|
||||||
import Credential from '~models/Credential';
|
import Credential from '~models/Credential';
|
||||||
@@ -19,16 +19,16 @@ import Me from '~models/Me';
|
|||||||
import NotificationTemplate from '~models/NotificationTemplate';
|
import NotificationTemplate from '~models/NotificationTemplate';
|
||||||
import Organization from '~models/Organization';
|
import Organization from '~models/Organization';
|
||||||
import Project from '~models/Project';
|
import Project from '~models/Project';
|
||||||
import Schedule from '~models/Schedule';
|
|
||||||
import ProjectUpdate from '~models/ProjectUpdate';
|
import ProjectUpdate from '~models/ProjectUpdate';
|
||||||
|
import Schedule from '~models/Schedule';
|
||||||
import SystemJob from '~models/SystemJob';
|
import SystemJob from '~models/SystemJob';
|
||||||
import Token from '~models/Token';
|
import Token from '~models/Token';
|
||||||
|
import UnifiedJob from '~models/UnifiedJob';
|
||||||
import UnifiedJobTemplate from '~models/UnifiedJobTemplate';
|
import UnifiedJobTemplate from '~models/UnifiedJobTemplate';
|
||||||
|
import User from '~models/User';
|
||||||
import WorkflowJob from '~models/WorkflowJob';
|
import WorkflowJob from '~models/WorkflowJob';
|
||||||
import WorkflowJobTemplate from '~models/WorkflowJobTemplate';
|
import WorkflowJobTemplate from '~models/WorkflowJobTemplate';
|
||||||
import WorkflowJobTemplateNode from '~models/WorkflowJobTemplateNode';
|
import WorkflowJobTemplateNode from '~models/WorkflowJobTemplateNode';
|
||||||
import UnifiedJob from '~models/UnifiedJob';
|
|
||||||
import User from '~models/User';
|
|
||||||
|
|
||||||
import ModelsStrings from '~models/models.strings';
|
import ModelsStrings from '~models/models.strings';
|
||||||
|
|
||||||
@@ -38,8 +38,8 @@ angular
|
|||||||
.module(MODULE_NAME, [
|
.module(MODULE_NAME, [
|
||||||
atLibServices
|
atLibServices
|
||||||
])
|
])
|
||||||
.service('ApplicationModel', Application)
|
|
||||||
.service('AdHocCommandModel', AdHocCommand)
|
.service('AdHocCommandModel', AdHocCommand)
|
||||||
|
.service('ApplicationModel', Application)
|
||||||
.service('BaseModel', Base)
|
.service('BaseModel', Base)
|
||||||
.service('ConfigModel', Config)
|
.service('ConfigModel', Config)
|
||||||
.service('CredentialModel', Credential)
|
.service('CredentialModel', Credential)
|
||||||
@@ -54,19 +54,19 @@ angular
|
|||||||
.service('JobModel', Job)
|
.service('JobModel', Job)
|
||||||
.service('JobTemplateModel', JobTemplate)
|
.service('JobTemplateModel', JobTemplate)
|
||||||
.service('MeModel', Me)
|
.service('MeModel', Me)
|
||||||
|
.service('ModelsStrings', ModelsStrings)
|
||||||
.service('NotificationTemplate', NotificationTemplate)
|
.service('NotificationTemplate', NotificationTemplate)
|
||||||
.service('OrganizationModel', Organization)
|
.service('OrganizationModel', Organization)
|
||||||
.service('ProjectModel', Project)
|
.service('ProjectModel', Project)
|
||||||
.service('ScheduleModel', Schedule)
|
|
||||||
.service('UnifiedJobModel', UnifiedJob)
|
|
||||||
.service('ProjectUpdateModel', ProjectUpdate)
|
.service('ProjectUpdateModel', ProjectUpdate)
|
||||||
|
.service('ScheduleModel', Schedule)
|
||||||
.service('SystemJobModel', SystemJob)
|
.service('SystemJobModel', SystemJob)
|
||||||
.service('TokenModel', Token)
|
.service('TokenModel', Token)
|
||||||
|
.service('UnifiedJobModel', UnifiedJob)
|
||||||
.service('UnifiedJobTemplateModel', UnifiedJobTemplate)
|
.service('UnifiedJobTemplateModel', UnifiedJobTemplate)
|
||||||
|
.service('UserModel', User)
|
||||||
.service('WorkflowJobModel', WorkflowJob)
|
.service('WorkflowJobModel', WorkflowJob)
|
||||||
.service('WorkflowJobTemplateModel', WorkflowJobTemplate)
|
.service('WorkflowJobTemplateModel', WorkflowJobTemplate)
|
||||||
.service('WorkflowJobTemplateNodeModel', WorkflowJobTemplateNode)
|
.service('WorkflowJobTemplateNodeModel', WorkflowJobTemplateNode);
|
||||||
.service('UserModel', User)
|
|
||||||
.service('ModelsStrings', ModelsStrings);
|
|
||||||
|
|
||||||
export default MODULE_NAME;
|
export default MODULE_NAME;
|
||||||
|
|||||||
@@ -161,16 +161,16 @@ angular
|
|||||||
// })
|
// })
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
.run(['$stateExtender', '$q', '$compile', '$cookies', '$rootScope', '$log', '$stateParams',
|
.run(['$q', '$cookies', '$rootScope', '$log', '$stateParams',
|
||||||
'CheckLicense', '$location', 'Authorization', 'LoadBasePaths', 'Timer',
|
'CheckLicense', '$location', 'Authorization', 'LoadBasePaths', 'Timer',
|
||||||
'LoadConfig', 'Store', 'pendoService', 'Prompt', 'Rest',
|
'LoadConfig', 'Store', 'pendoService', 'Rest',
|
||||||
'Wait', 'ProcessErrors', '$state', 'GetBasePath', 'ConfigService',
|
'$state', 'GetBasePath', 'ConfigService',
|
||||||
'$filter', 'SocketService', 'AppStrings', '$transitions',
|
'SocketService', 'AppStrings', '$transitions',
|
||||||
function($stateExtender, $q, $compile, $cookies, $rootScope, $log, $stateParams,
|
function($q, $cookies, $rootScope, $log, $stateParams,
|
||||||
CheckLicense, $location, Authorization, LoadBasePaths, Timer,
|
CheckLicense, $location, Authorization, LoadBasePaths, Timer,
|
||||||
LoadConfig, Store, pendoService, Prompt, Rest, Wait,
|
LoadConfig, Store, pendoService, Rest,
|
||||||
ProcessErrors, $state, GetBasePath, ConfigService,
|
$state, GetBasePath, ConfigService,
|
||||||
$filter, SocketService, AppStrings, $transitions) {
|
SocketService, AppStrings, $transitions) {
|
||||||
|
|
||||||
$rootScope.$state = $state;
|
$rootScope.$state = $state;
|
||||||
$rootScope.$state.matches = function(stateName) {
|
$rootScope.$state.matches = function(stateName) {
|
||||||
@@ -387,6 +387,15 @@ angular
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Rest.setUrl(`${GetBasePath('workflow_approval')}?status=pending&page_size=1`);
|
||||||
|
Rest.get()
|
||||||
|
.then(({data}) => {
|
||||||
|
$rootScope.pendingApprovalCount = data.count;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// TODO: handle this
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,14 +39,14 @@
|
|||||||
* This is usage information.
|
* This is usage information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default ['$log', '$cookies', '$compile', '$rootScope',
|
export default ['$log', '$cookies', '$rootScope',
|
||||||
'$location', 'Authorization', 'Alert', 'Wait', 'Timer',
|
'$location', 'Authorization', 'Alert', 'Wait', 'Timer',
|
||||||
'Empty', '$scope', 'pendoService', 'ConfigService',
|
'Empty', '$scope', 'pendoService', 'ConfigService',
|
||||||
'CheckLicense', 'SocketService',
|
'CheckLicense', 'SocketService', 'Rest', 'GetBasePath',
|
||||||
function ($log, $cookies, $compile, $rootScope, $location,
|
function ($log, $cookies, $rootScope,
|
||||||
Authorization, Alert, Wait, Timer, Empty,
|
$location, Authorization, Alert, Wait, Timer,
|
||||||
scope, pendoService, ConfigService, CheckLicense,
|
Empty, scope, pendoService, ConfigService,
|
||||||
SocketService) {
|
CheckLicense, SocketService, Rest, GetBasePath) {
|
||||||
var lastPath, lastUser, sessionExpired, loginAgain, preAuthUrl;
|
var lastPath, lastUser, sessionExpired, loginAgain, preAuthUrl;
|
||||||
|
|
||||||
loginAgain = function() {
|
loginAgain = function() {
|
||||||
@@ -139,6 +139,15 @@ export default ['$log', '$cookies', '$compile', '$rootScope',
|
|||||||
Alert('Error', 'Failed to access user information. GET returned status: ' + status, 'alert-danger', loginAgain);
|
Alert('Error', 'Failed to access user information. GET returned status: ' + status, 'alert-danger', loginAgain);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Rest.setUrl(`${GetBasePath('workflow_approval')}?status=pending&page_size=1`);
|
||||||
|
Rest.get()
|
||||||
|
.then(({data}) => {
|
||||||
|
$rootScope.pendingApprovalCount = data.count;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// TODO: handle this
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Call the API to get an auth token
|
// Call the API to get an auth token
|
||||||
|
|||||||
@@ -75,221 +75,230 @@ export default ['Rest', 'GetBasePath', '$q', 'NextPage', function(Rest, GetBaseP
|
|||||||
}).catch(function(response){
|
}).catch(function(response){
|
||||||
return $q.reject( response );
|
return $q.reject( response );
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
getAllWorkflowJobTemplateLabels: function(id) {
|
getAllWorkflowJobTemplateLabels: function(id) {
|
||||||
Rest.setUrl(GetBasePath('workflow_job_templates') + id + "/labels?page_size=200");
|
Rest.setUrl(GetBasePath('workflow_job_templates') + id + "/labels?page_size=200");
|
||||||
return Rest.get()
|
return Rest.get()
|
||||||
.then(function(res) {
|
.then(function(res) {
|
||||||
if (res.data.next) {
|
if (res.data.next) {
|
||||||
return NextPage({
|
return NextPage({
|
||||||
url: res.data.next,
|
url: res.data.next,
|
||||||
arrayOfValues: res.data.results
|
arrayOfValues: res.data.results
|
||||||
}).then(function(labels) {
|
}).then(function(labels) {
|
||||||
return labels;
|
return labels;
|
||||||
}).catch(function(response){
|
}).catch(function(response){
|
||||||
return $q.reject( response );
|
return $q.reject( response );
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return $q.resolve( res.data.results );
|
return $q.resolve( res.data.results );
|
||||||
}
|
}
|
||||||
}).catch(function(response){
|
}).catch(function(response){
|
||||||
return $q.reject( response );
|
return $q.reject( response );
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getJobTemplate: function(id) {
|
getJobTemplate: function(id) {
|
||||||
var url = GetBasePath('job_templates');
|
var url = GetBasePath('job_templates');
|
||||||
|
|
||||||
url = url + id;
|
url = url + id;
|
||||||
|
|
||||||
Rest.setUrl(url);
|
Rest.setUrl(url);
|
||||||
return Rest.get();
|
return Rest.get();
|
||||||
},
|
},
|
||||||
addWorkflowNode: function(params) {
|
addWorkflowNode: function(params) {
|
||||||
// params.url
|
// params.url
|
||||||
// params.data
|
// params.data
|
||||||
|
|
||||||
Rest.setUrl(params.url);
|
Rest.setUrl(params.url);
|
||||||
return Rest.post(params.data);
|
return Rest.post(params.data);
|
||||||
},
|
},
|
||||||
editWorkflowNode: function(params) {
|
editWorkflowNode: function(params) {
|
||||||
// params.id
|
// params.id
|
||||||
// params.data
|
// params.data
|
||||||
|
|
||||||
var url = GetBasePath('workflow_job_template_nodes') + params.id;
|
var url = GetBasePath('workflow_job_template_nodes') + params.id;
|
||||||
|
|
||||||
Rest.setUrl(url);
|
Rest.setUrl(url);
|
||||||
return Rest.put(params.data);
|
return Rest.put(params.data);
|
||||||
},
|
},
|
||||||
getJobTemplateLaunchInfo: function(id) {
|
getJobTemplateLaunchInfo: function(id) {
|
||||||
var url = GetBasePath('job_templates');
|
var url = GetBasePath('job_templates');
|
||||||
|
|
||||||
url = url + id + '/launch';
|
url = url + id + '/launch';
|
||||||
|
|
||||||
Rest.setUrl(url);
|
Rest.setUrl(url);
|
||||||
return Rest.get();
|
return Rest.get();
|
||||||
},
|
},
|
||||||
getWorkflowJobTemplateNodes: function(id, page) {
|
getWorkflowJobTemplateNodes: function(id, page) {
|
||||||
var url = GetBasePath('workflow_job_templates');
|
var url = GetBasePath('workflow_job_templates');
|
||||||
|
|
||||||
url = url + id + '/workflow_nodes?page_size=200';
|
url = url + id + '/workflow_nodes?page_size=200';
|
||||||
|
|
||||||
if(page) {
|
if(page) {
|
||||||
url += '&page=' + page;
|
url += '&page=' + page;
|
||||||
}
|
}
|
||||||
|
|
||||||
Rest.setUrl(url);
|
Rest.setUrl(url);
|
||||||
return Rest.get();
|
return Rest.get();
|
||||||
},
|
},
|
||||||
updateWorkflowJobTemplate: function(params) {
|
updateWorkflowJobTemplate: function(params) {
|
||||||
// params.id
|
// params.id
|
||||||
// params.data
|
// params.data
|
||||||
|
|
||||||
var url = GetBasePath('workflow_job_templates');
|
var url = GetBasePath('workflow_job_templates');
|
||||||
|
|
||||||
url = url + params.id;
|
url = url + params.id;
|
||||||
|
|
||||||
Rest.setUrl(url);
|
Rest.setUrl(url);
|
||||||
return Rest.patch(params.data);
|
return Rest.patch(params.data);
|
||||||
},
|
},
|
||||||
getWorkflowJobTemplate: function(id) {
|
getWorkflowJobTemplate: function(id) {
|
||||||
var url = GetBasePath('workflow_job_templates');
|
var url = GetBasePath('workflow_job_templates');
|
||||||
|
|
||||||
url = url + id;
|
url = url + id;
|
||||||
|
|
||||||
Rest.setUrl(url);
|
Rest.setUrl(url);
|
||||||
return Rest.get();
|
return Rest.get();
|
||||||
},
|
},
|
||||||
deleteWorkflowJobTemplateNode: function(id) {
|
deleteWorkflowJobTemplateNode: function(id) {
|
||||||
var url = GetBasePath('workflow_job_template_nodes') + id;
|
var url = GetBasePath('workflow_job_template_nodes') + id;
|
||||||
|
|
||||||
Rest.setUrl(url);
|
Rest.setUrl(url);
|
||||||
return Rest.destroy();
|
return Rest.destroy();
|
||||||
},
|
},
|
||||||
disassociateWorkflowNode: function(params) {
|
disassociateWorkflowNode: function(params) {
|
||||||
//params.parentId
|
//params.parentId
|
||||||
//params.nodeId
|
//params.nodeId
|
||||||
//params.edge
|
//params.edge
|
||||||
|
|
||||||
var url = GetBasePath('workflow_job_template_nodes') + params.parentId;
|
var url = GetBasePath('workflow_job_template_nodes') + params.parentId;
|
||||||
|
|
||||||
if(params.edge === 'success') {
|
if(params.edge === 'success') {
|
||||||
url = url + '/success_nodes';
|
url = url + '/success_nodes';
|
||||||
}
|
}
|
||||||
else if(params.edge === 'failure') {
|
else if(params.edge === 'failure') {
|
||||||
url = url + '/failure_nodes';
|
url = url + '/failure_nodes';
|
||||||
}
|
}
|
||||||
else if(params.edge === 'always') {
|
else if(params.edge === 'always') {
|
||||||
url = url + '/always_nodes';
|
url = url + '/always_nodes';
|
||||||
}
|
}
|
||||||
|
|
||||||
Rest.setUrl(url);
|
Rest.setUrl(url);
|
||||||
return Rest.post({
|
return Rest.post({
|
||||||
"id": params.nodeId,
|
"id": params.nodeId,
|
||||||
"disassociate": true
|
"disassociate": true
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
associateWorkflowNode: function(params) {
|
associateWorkflowNode: function(params) {
|
||||||
//params.parentId
|
//params.parentId
|
||||||
//params.nodeId
|
//params.nodeId
|
||||||
//params.edge
|
//params.edge
|
||||||
|
|
||||||
var url = GetBasePath('workflow_job_template_nodes') + params.parentId;
|
var url = GetBasePath('workflow_job_template_nodes') + params.parentId;
|
||||||
|
|
||||||
if(params.edge === 'success') {
|
if(params.edge === 'success') {
|
||||||
url = url + '/success_nodes';
|
url = url + '/success_nodes';
|
||||||
}
|
}
|
||||||
else if(params.edge === 'failure') {
|
else if(params.edge === 'failure') {
|
||||||
url = url + '/failure_nodes';
|
url = url + '/failure_nodes';
|
||||||
}
|
}
|
||||||
else if(params.edge === 'always') {
|
else if(params.edge === 'always') {
|
||||||
url = url + '/always_nodes';
|
url = url + '/always_nodes';
|
||||||
}
|
}
|
||||||
|
|
||||||
Rest.setUrl(url);
|
Rest.setUrl(url);
|
||||||
return Rest.post({
|
return Rest.post({
|
||||||
id: params.nodeId
|
id: params.nodeId
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getUnifiedJobTemplate: function(id) {
|
getUnifiedJobTemplate: function(id) {
|
||||||
var url = GetBasePath('unified_job_templates');
|
var url = GetBasePath('unified_job_templates');
|
||||||
|
|
||||||
url = url + "?id=" + id;
|
url = url + "?id=" + id;
|
||||||
|
|
||||||
Rest.setUrl(url);
|
Rest.setUrl(url);
|
||||||
return Rest.get();
|
return Rest.get();
|
||||||
},
|
},
|
||||||
getCredential: function(id) {
|
getCredential: function(id) {
|
||||||
var url = GetBasePath('credentials');
|
var url = GetBasePath('credentials');
|
||||||
|
|
||||||
url = url + id;
|
url = url + id;
|
||||||
|
|
||||||
Rest.setUrl(url);
|
Rest.setUrl(url);
|
||||||
return Rest.get();
|
return Rest.get();
|
||||||
},
|
},
|
||||||
getInventory: function(id) {
|
getInventory: function(id) {
|
||||||
var url = GetBasePath('inventory');
|
var url = GetBasePath('inventory');
|
||||||
|
|
||||||
url = url + id;
|
url = url + id;
|
||||||
|
|
||||||
Rest.setUrl(url);
|
Rest.setUrl(url);
|
||||||
return Rest.get();
|
return Rest.get();
|
||||||
},
|
},
|
||||||
getWorkflowCopy: function(id) {
|
getWorkflowCopy: function(id) {
|
||||||
let url = GetBasePath('workflow_job_templates');
|
let url = GetBasePath('workflow_job_templates');
|
||||||
|
|
||||||
url = url + id + '/copy';
|
url = url + id + '/copy';
|
||||||
|
|
||||||
Rest.setUrl(url);
|
Rest.setUrl(url);
|
||||||
return Rest.get();
|
return Rest.get();
|
||||||
},
|
},
|
||||||
copyWorkflow: function(id) {
|
copyWorkflow: function(id) {
|
||||||
let url = GetBasePath('workflow_job_templates');
|
let url = GetBasePath('workflow_job_templates');
|
||||||
|
|
||||||
url = url + id + '/copy';
|
url = url + id + '/copy';
|
||||||
|
|
||||||
Rest.setUrl(url);
|
Rest.setUrl(url);
|
||||||
return Rest.post();
|
return Rest.post();
|
||||||
},
|
},
|
||||||
getWorkflowJobTemplateOptions: function() {
|
getWorkflowJobTemplateOptions: function() {
|
||||||
var deferred = $q.defer();
|
var deferred = $q.defer();
|
||||||
|
|
||||||
let url = GetBasePath('workflow_job_templates');
|
let url = GetBasePath('workflow_job_templates');
|
||||||
|
|
||||||
Rest.setUrl(url);
|
Rest.setUrl(url);
|
||||||
Rest.options()
|
Rest.options()
|
||||||
.then(({data}) => {
|
.then(({data}) => {
|
||||||
deferred.resolve(data);
|
deferred.resolve(data);
|
||||||
}).catch(({msg, code}) => {
|
}).catch(({msg, code}) => {
|
||||||
deferred.reject(msg, code);
|
deferred.reject(msg, code);
|
||||||
});
|
});
|
||||||
|
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
},
|
},
|
||||||
getJobTemplateOptions: function() {
|
getJobTemplateOptions: function() {
|
||||||
var deferred = $q.defer();
|
var deferred = $q.defer();
|
||||||
|
|
||||||
let url = GetBasePath('job_templates');
|
let url = GetBasePath('job_templates');
|
||||||
|
|
||||||
Rest.setUrl(url);
|
Rest.setUrl(url);
|
||||||
Rest.options()
|
Rest.options()
|
||||||
.then(({data}) => {
|
.then(({data}) => {
|
||||||
deferred.resolve(data);
|
deferred.resolve(data);
|
||||||
}).catch(({msg, code}) => {
|
}).catch(({msg, code}) => {
|
||||||
deferred.reject(msg, code);
|
deferred.reject(msg, code);
|
||||||
});
|
});
|
||||||
|
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
},
|
},
|
||||||
postWorkflowNodeCredential: function(params) {
|
postWorkflowNodeCredential: function(params) {
|
||||||
// params.id
|
// params.id
|
||||||
// params.data
|
// params.data
|
||||||
|
|
||||||
var url = GetBasePath('workflow_job_template_nodes') + params.id + '/credentials';
|
var url = GetBasePath('workflow_job_template_nodes') + params.id + '/credentials';
|
||||||
|
|
||||||
Rest.setUrl(url);
|
Rest.setUrl(url);
|
||||||
return Rest.post(params.data);
|
return Rest.post(params.data);
|
||||||
}
|
},
|
||||||
|
createApprovalTemplate: (params) => {
|
||||||
|
params = params || {};
|
||||||
|
Rest.setUrl(GetBasePath('workflow_approval_templates'));
|
||||||
|
return Rest.post(params);
|
||||||
|
},
|
||||||
|
patchApprovalTemplate: ({id, data}) => {
|
||||||
|
Rest.setUrl(`${GetBasePath('workflow_approval_templates')}/${id}`);
|
||||||
|
return Rest.patch(data);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}];
|
}];
|
||||||
|
|||||||
@@ -142,8 +142,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.WorkflowChart-deletedText {
|
.WorkflowChart-deletedText {
|
||||||
width: 90px;
|
width: 180px;
|
||||||
|
height: 14px;
|
||||||
color: @default-interface-txt;
|
color: @default-interface-txt;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
.WorkflowChart-activeNode {
|
.WorkflowChart-activeNode {
|
||||||
fill: @default-link;
|
fill: @default-link;
|
||||||
@@ -159,7 +161,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.WorkflowChart-nameText {
|
.WorkflowChart-nameText {
|
||||||
|
width: 180px;
|
||||||
|
height: 20px;
|
||||||
|
line-height: 18px;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.WorkflowChart-tooltip {
|
.WorkflowChart-tooltip {
|
||||||
|
|||||||
@@ -770,12 +770,23 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
|
|||||||
});
|
});
|
||||||
|
|
||||||
baseSvg.selectAll(".WorkflowChart-nameText")
|
baseSvg.selectAll(".WorkflowChart-nameText")
|
||||||
.attr("x", function(d){ return (scope.mode === 'details' && d.job && d.job.status) ? 20 : nodeW / 2; })
|
.attr("x", 0)
|
||||||
.attr("y", function(d){ return (scope.mode === 'details' && d.job && d.job.status) ? 10 : nodeH / 2; })
|
.attr("y", function(d){ return (scope.mode === 'details' && d.job && d.job.status) ? 10 : (nodeH / 2) - 10; })
|
||||||
.attr("text-anchor", function(d){ return (scope.mode === 'details' && d.job && d.job.status) ? "inherit" : "middle"; })
|
.attr("text-anchor", function(d){ return (scope.mode === 'details' && d.job && d.job.status) ? "inherit" : "middle"; })
|
||||||
.text(function (d) {
|
.html(function (d) {
|
||||||
const name = _.get(d, 'unifiedJobTemplate.name');
|
const name = _.get(d, 'unifiedJobTemplate.name');
|
||||||
return name ? wrap(name) : "";
|
const wrappedName = name ? wrap(name) : "";
|
||||||
|
// TODO: clean this up
|
||||||
|
if (d.unifiedJobTemplate && d.unifiedJobTemplate.unified_job_type === 'workflow_approval') {
|
||||||
|
return `<span>
|
||||||
|
<div style="background-color: #EBEBEB;height: 20px;width: 20px;border-radius: 50%;display: inline-block;margin-right:5px;">
|
||||||
|
<span style="color:#707070" class="fa fa-pause"></span>
|
||||||
|
</div>
|
||||||
|
<span>${wrappedName}</span>
|
||||||
|
</span>`;
|
||||||
|
} else {
|
||||||
|
return `<span>${wrappedName}</span>`;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
baseSvg.selectAll(".WorkflowChart-detailsLink")
|
baseSvg.selectAll(".WorkflowChart-detailsLink")
|
||||||
@@ -884,19 +895,31 @@ export default ['moment', '$timeout', '$window', '$filter', 'TemplatesStrings',
|
|||||||
.attr("class", "WorkflowChart-activeNode")
|
.attr("class", "WorkflowChart-activeNode")
|
||||||
.style("display", function(d) { return d.id === scope.graphState.nodeBeingEdited ? null : "none"; });
|
.style("display", function(d) { return d.id === scope.graphState.nodeBeingEdited ? null : "none"; });
|
||||||
|
|
||||||
thisNode.append("text")
|
thisNode.append("foreignObject")
|
||||||
.attr("x", function(d){ return (scope.mode === 'details' && d.job && d.job.status) ? 20 : nodeW / 2; })
|
// .attr("x", function(d){ return (scope.mode === 'details' && d.job && d.job.status) ? 20 : nodeW / 2; })
|
||||||
.attr("y", function(d){ return (scope.mode === 'details' && d.job && d.job.status) ? 10 : nodeH / 2; })
|
.attr("x", 0)
|
||||||
|
.attr("y", function(d){ return (scope.mode === 'details' && d.job && d.job.status) ? 10 : (nodeH / 2) - 10; })
|
||||||
.attr("dy", ".35em")
|
.attr("dy", ".35em")
|
||||||
.attr("text-anchor", function(d){ return (scope.mode === 'details' && d.job && d.job.status) ? "inherit" : "middle"; })
|
.attr("text-anchor", function(d){ return (scope.mode === 'details' && d.job && d.job.status) ? "inherit" : "middle"; })
|
||||||
.attr("class", "WorkflowChart-defaultText WorkflowChart-nameText")
|
.attr("class", "WorkflowChart-defaultText WorkflowChart-nameText")
|
||||||
.text(function (d) {
|
.html(function (d) {
|
||||||
const name = _.get(d, 'unifiedJobTemplate.name');
|
const name = _.get(d, 'unifiedJobTemplate.name');
|
||||||
return name ? wrap(name) : "";
|
const wrappedName = name ? wrap(name) : "";
|
||||||
|
// TODO: clean this up
|
||||||
|
if (d.unifiedJobTemplate && d.unifiedJobTemplate.unified_job_type === 'workflow_approval') {
|
||||||
|
return `<span>
|
||||||
|
<div style="background-color: #EBEBEB;height: 20px;width: 20px;border-radius: 50%;display: inline-block;margin-right:5px;">
|
||||||
|
<span style="color:#707070" class="fa fa-pause"></span>
|
||||||
|
</div>
|
||||||
|
<span>${wrappedName}</span>
|
||||||
|
</span>`;
|
||||||
|
} else {
|
||||||
|
return `<span>${wrappedName}</span>`;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
thisNode.append("foreignObject")
|
thisNode.append("foreignObject")
|
||||||
.attr("x", 62)
|
.attr("x", 0)
|
||||||
.attr("y", 22)
|
.attr("y", 22)
|
||||||
.attr("dy", ".35em")
|
.attr("dy", ".35em")
|
||||||
.attr("text-anchor", "middle")
|
.attr("text-anchor", "middle")
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export default ['$scope', 'TemplatesService', 'JobTemplateModel', 'PromptService
|
|||||||
|
|
||||||
$scope.strings = TemplatesStrings;
|
$scope.strings = TemplatesStrings;
|
||||||
$scope.editNodeHelpMessage = null;
|
$scope.editNodeHelpMessage = null;
|
||||||
|
$scope.pauseNode = {};
|
||||||
|
|
||||||
let templateList = _.cloneDeep(TemplateList);
|
let templateList = _.cloneDeep(TemplateList);
|
||||||
delete templateList.actions;
|
delete templateList.actions;
|
||||||
@@ -463,6 +464,7 @@ export default ['$scope', 'TemplatesService', 'JobTemplateModel', 'PromptService
|
|||||||
}
|
}
|
||||||
|
|
||||||
$scope.promptData = null;
|
$scope.promptData = null;
|
||||||
|
$scope.pauseNode = {};
|
||||||
$scope.editNodeHelpMessage = getEditNodeHelpMessage(selectedTemplate, $scope.workflowJobTemplateObj);
|
$scope.editNodeHelpMessage = getEditNodeHelpMessage(selectedTemplate, $scope.workflowJobTemplateObj);
|
||||||
|
|
||||||
if (selectedTemplate.type === "job_template" || selectedTemplate.type === "workflow_job_template") {
|
if (selectedTemplate.type === "job_template" || selectedTemplate.type === "workflow_job_template") {
|
||||||
@@ -616,26 +618,48 @@ export default ['$scope', 'TemplatesService', 'JobTemplateModel', 'PromptService
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CreateSelect2({
|
||||||
|
element: '#workflow-node-types',
|
||||||
|
multiple: false
|
||||||
|
});
|
||||||
|
|
||||||
$q.all(listPromises)
|
$q.all(listPromises)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
if ($scope.nodeConfig.mode === "edit") {
|
if ($scope.nodeConfig.mode === "edit") {
|
||||||
// Make sure that we have the full unified job template object
|
if ($scope.nodeConfig.node.unifiedJobTemplate && $scope.nodeConfig.node.unifiedJobTemplate.unified_job_type === "workflow_approval") {
|
||||||
if (!$scope.nodeConfig.node.fullUnifiedJobTemplateObject) {
|
$scope.selectedTemplate = null;
|
||||||
// This is a node that we got back from the api with an incomplete
|
$scope.activeTab = "pause";
|
||||||
// unified job template so we're going to pull down the whole object
|
CreateSelect2({
|
||||||
TemplatesService.getUnifiedJobTemplate($scope.nodeConfig.node.originalNodeObject.summary_fields.unified_job_template.id)
|
element: '#workflow_node_edge',
|
||||||
.then(({data}) => {
|
multiple: false
|
||||||
$scope.nodeConfig.node.fullUnifiedJobTemplateObject = data.results[0];
|
});
|
||||||
finishConfiguringEdit();
|
|
||||||
}, (error) => {
|
$scope.pauseNode = {
|
||||||
ProcessErrors($scope, error.data, error.status, null, {
|
isPauseNode: true,
|
||||||
hdr: 'Error!',
|
name: $scope.nodeConfig.node.unifiedJobTemplate.name,
|
||||||
msg: 'Failed to get unified job template. GET returned ' +
|
description: $scope.nodeConfig.node.unifiedJobTemplate.description,
|
||||||
'status: ' + error.status
|
};
|
||||||
});
|
|
||||||
});
|
$scope.nodeFormDataLoaded = true;
|
||||||
} else {
|
} else {
|
||||||
finishConfiguringEdit();
|
// Make sure that we have the full unified job template object
|
||||||
|
if (!$scope.nodeConfig.node.fullUnifiedJobTemplateObject) {
|
||||||
|
// This is a node that we got back from the api with an incomplete
|
||||||
|
// unified job template so we're going to pull down the whole object
|
||||||
|
TemplatesService.getUnifiedJobTemplate($scope.nodeConfig.node.originalNodeObject.summary_fields.unified_job_template.id)
|
||||||
|
.then(({data}) => {
|
||||||
|
$scope.nodeConfig.node.fullUnifiedJobTemplateObject = data.results[0];
|
||||||
|
finishConfiguringEdit();
|
||||||
|
}, (error) => {
|
||||||
|
ProcessErrors($scope, error.data, error.status, null, {
|
||||||
|
hdr: 'Error!',
|
||||||
|
msg: 'Failed to get unified job template. GET returned ' +
|
||||||
|
'status: ' + error.status
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
finishConfiguringEdit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
finishConfiguringAdd();
|
finishConfiguringAdd();
|
||||||
|
|||||||
@@ -1,9 +1,18 @@
|
|||||||
<div ng-show="nodeFormDataLoaded">
|
<div ng-show="nodeFormDataLoaded">
|
||||||
<div class="WorkflowMaker-formTitle">{{nodeConfig.mode === 'edit' ? nodeConfig.node.fullUnifiedJobTemplateObject.name || nodeConfig.node.unifiedJobTemplate.name : strings.get('workflow_maker.ADD_A_NODE')}}</div>
|
<div class="WorkflowMaker-formTitle">{{nodeConfig.mode === 'edit' ? nodeConfig.node.fullUnifiedJobTemplateObject.name || nodeConfig.node.unifiedJobTemplate.name : strings.get('workflow_maker.ADD_A_NODE')}}</div>
|
||||||
<div class="Form-tabHolder" ng-show="!readOnly">
|
<div class="WorkflowMaker-formTypeDropdown" ng-show="!readOnly">
|
||||||
<div class="Form-tab WorkflowMaker-formTab" ng-class="{'is-selected': activeTab === 'jobs'}" ng-click="activeTab = 'jobs'">{{strings.get('workflow_maker.JOBS')}}</div>
|
<select
|
||||||
<div class="Form-tab WorkflowMaker-formTab" ng-class="{'is-selected': activeTab === 'project_syncs'}" ng-click="activeTab = 'project_syncs'">{{strings.get('workflow_maker.PROJECT_SYNC')}}</div>
|
id="workflow-node-types"
|
||||||
<div class="Form-tab WorkflowMaker-formTab" ng-class="{'is-selected': activeTab === 'inventory_syncs'}" ng-click="activeTab = 'inventory_syncs'">{{strings.get('workflow_maker.INVENTORY_SYNC')}}</div>
|
ng-model="activeTab"
|
||||||
|
class="form-control Form-dropDown"
|
||||||
|
name="activeTab"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<option value="jobs" selected="selected">{{strings.get('workflow_maker.JOBS')}}</option>
|
||||||
|
<option value="project_syncs" selected="selected">{{strings.get('workflow_maker.PROJECT_SYNC')}}</option>
|
||||||
|
<option value="inventory_syncs" selected="selected">{{strings.get('workflow_maker.INVENTORY_SYNC')}}</option>
|
||||||
|
<option value="pause" selected="selected">{{strings.get('workflow_maker.PAUSE_NODE')}}</option>
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="WorkflowMaker-formLists" ng-show="!readOnly">
|
<div class="WorkflowMaker-formLists" ng-show="!readOnly">
|
||||||
<div id="workflow-jobs-list" ng-show="activeTab === 'jobs'">
|
<div id="workflow-jobs-list" ng-show="activeTab === 'jobs'">
|
||||||
@@ -103,6 +112,36 @@
|
|||||||
</div>
|
</div>
|
||||||
<paginate base-path="inventory_sources" collection="wf_maker_inventory_sources" dataset="wf_maker_inventory_source_dataset" iterator="wf_maker_inventory_source" query-set="wf_maker_inventory_source_queryset" hide-view-per-page="true" max-visible-pages="5"></paginate>
|
<paginate base-path="inventory_sources" collection="wf_maker_inventory_sources" dataset="wf_maker_inventory_source_dataset" iterator="wf_maker_inventory_source" query-set="wf_maker_inventory_source_queryset" hide-view-per-page="true" max-visible-pages="5"></paginate>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="workflow-pause" ng-show="activeTab === 'pause'">
|
||||||
|
<label class="WorkflowMaker-pauseCheckbox">
|
||||||
|
<input type="checkbox" ng-model="pauseNode.isPauseNode" />
|
||||||
|
<!-- translate -->
|
||||||
|
<span class="Form-inputLabel">Make this a pause node</span>
|
||||||
|
</label>
|
||||||
|
<div ng-hide="!pauseNode.isPauseNode">
|
||||||
|
<div class="form-group Form-formGroup Form-formGroup--singleColumn">
|
||||||
|
<label for="pauseName" class="Form-inputLabelContainer">
|
||||||
|
<span class="Form-requiredAsterisk">*</span>
|
||||||
|
<!-- translate -->
|
||||||
|
<span class="Form-inputLabel">Name</span>
|
||||||
|
</label>
|
||||||
|
<div>
|
||||||
|
<input type="text" ng-model="pauseNode.name" name="pauseName" id="workflow_job_template_pauseName" class="form-control Form-textInput" />
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group Form-formGroup Form-formGroup--singleColumn"></div>
|
||||||
|
<label for="pauseDesc" class="Form-inputLabelContainer">
|
||||||
|
<!-- translate -->
|
||||||
|
<span class="Form-inputLabel">Description</span>
|
||||||
|
</label>
|
||||||
|
<div>
|
||||||
|
<input type="text" ng-model="pauseNode.description" name="pauseDesc" id="workflow_job_template_pauseDesc" class="form-control Form-textInput" />
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div ng-if="selectedTemplate && selectedTemplateInvalid">
|
<div ng-if="selectedTemplate && selectedTemplateInvalid">
|
||||||
<div class="WorkflowMaker-invalidJobTemplateWarning">
|
<div class="WorkflowMaker-invalidJobTemplateWarning">
|
||||||
@@ -238,7 +277,8 @@
|
|||||||
<button type="button" class="btn btn-sm Form-primaryButton Form-primaryButton--noMargin" id="workflow_maker_prompt_btn" ng-show="showPromptButton && activeTab == 'jobs' " ng-click="openPromptModal()"> {{:: strings.get('prompt.PROMPT') }}</button>
|
<button type="button" class="btn btn-sm Form-primaryButton Form-primaryButton--noMargin" id="workflow_maker_prompt_btn" ng-show="showPromptButton && activeTab == 'jobs' " ng-click="openPromptModal()"> {{:: strings.get('prompt.PROMPT') }}</button>
|
||||||
<button type="button" class="btn btn-sm Form-cancelButton" id="workflow_maker_cancel_node_btn" ng-show="!readOnly" ng-click="cancel()"> {{:: strings.get('CANCEL') }}</button>
|
<button type="button" class="btn btn-sm Form-cancelButton" id="workflow_maker_cancel_node_btn" ng-show="!readOnly" ng-click="cancel()"> {{:: strings.get('CANCEL') }}</button>
|
||||||
<button type="button" class="btn btn-sm Form-cancelButton" id="workflow_maker_close_node_btn" ng-show="readOnly" ng-click="cancel()"> {{:: strings.get('CLOSE') }}</button>
|
<button type="button" class="btn btn-sm Form-cancelButton" id="workflow_maker_close_node_btn" ng-show="readOnly" ng-click="cancel()"> {{:: strings.get('CLOSE') }}</button>
|
||||||
<button type="button" class="btn btn-sm Form-saveButton" id="workflow_maker_select_node_btn" ng-show="!readOnly" ng-click="select({selectedTemplate, promptData, edgeType})" ng-disabled="!selectedTemplate || promptModalMissingReqFields || credentialRequiresPassword || selectedTemplateInvalid"> {{:: strings.get('workflow_maker.SELECT') }}</button>
|
<!-- need to figure out how to disable this button when pause node is incomplete -->
|
||||||
|
<button type="button" class="btn btn-sm Form-saveButton" id="workflow_maker_select_node_btn" ng-show="!readOnly" ng-click="select({selectedTemplate, promptData, edgeType, pauseNode})" ng-disabled="!(selectedTemplate || pauseNode.isPauseNode) || promptModalMissingReqFields || credentialRequiresPassword || selectedTemplateInvalid || (pauseNode.isPauseNode && !pauseNode.name)"> {{:: strings.get('workflow_maker.SELECT') }}</button>
|
||||||
</div>
|
</div>
|
||||||
<prompt prompt-data="promptData" action-text="{{:: strings.get('prompt.CONFIRM')}}" prevent-creds-with-passwords="preventCredsWithPasswords" read-only-prompts="readOnly"></prompt>
|
<prompt prompt-data="promptData" action-text="{{:: strings.get('prompt.CONFIRM')}}" prevent-creds-with-passwords="preventCredsWithPasswords" read-only-prompts="readOnly"></prompt>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -296,8 +296,8 @@
|
|||||||
border-bottom-left-radius: 5px;
|
border-bottom-left-radius: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.WorkflowMaker-formTab {
|
.WorkflowMaker-formTypeDropdown {
|
||||||
margin-right: 10px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.WorkflowMaker-preventBodyScrolling {
|
.WorkflowMaker-preventBodyScrolling {
|
||||||
@@ -314,6 +314,13 @@
|
|||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.WorkflowMaker-pauseCheckbox {
|
||||||
|
input {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.Key-list {
|
.Key-list {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
|||||||
@@ -140,59 +140,126 @@ export default ['$scope', 'TemplatesService',
|
|||||||
};
|
};
|
||||||
|
|
||||||
if ($scope.graphState.arrayOfNodesForChart.length > 1) {
|
if ($scope.graphState.arrayOfNodesForChart.length > 1) {
|
||||||
|
let approvalTemplatePromises = [];
|
||||||
let addPromises = [];
|
let addPromises = [];
|
||||||
let editPromises = [];
|
let editPromises = [];
|
||||||
let credentialRequests = [];
|
let credentialRequests = [];
|
||||||
|
|
||||||
Object.keys(nodeRef).map((workflowMakerNodeId) => {
|
Object.keys(nodeRef).map((workflowMakerNodeId) => {
|
||||||
if (nodeRef[workflowMakerNodeId].isNew) {
|
const node = nodeRef[workflowMakerNodeId];
|
||||||
addPromises.push(TemplatesService.addWorkflowNode({
|
if (node.isNew) {
|
||||||
url: $scope.workflowJobTemplateObj.related.workflow_nodes,
|
if (node.unifiedJobTemplate && node.unifiedJobTemplate.unified_job_type === "workflow_approval") {
|
||||||
data: buildSendableNodeData(nodeRef[workflowMakerNodeId])
|
approvalTemplatePromises.push(TemplatesService.createApprovalTemplate({
|
||||||
}).then(({data}) => {
|
name: node.unifiedJobTemplate.name
|
||||||
nodeRef[workflowMakerNodeId].originalNodeObject = data;
|
}).then(({data: approvalTemplateData}) => {
|
||||||
nodeIdToChartNodeIdMapping[data.id] = parseInt(workflowMakerNodeId);
|
addPromises.push(TemplatesService.addWorkflowNode({
|
||||||
if (_.get(nodeRef[workflowMakerNodeId], 'promptData.launchConf.ask_credential_on_launch')) {
|
url: $scope.workflowJobTemplateObj.related.workflow_nodes,
|
||||||
// This finds the credentials that were selected in the prompt but don't occur
|
data: {
|
||||||
// in the template defaults
|
unified_job_template: approvalTemplateData.id
|
||||||
let credentialIdsToPost = nodeRef[workflowMakerNodeId].promptData.prompts.credentials.value.filter((credFromPrompt) => {
|
}
|
||||||
let defaultCreds = _.get(nodeRef[workflowMakerNodeId], 'promptData.launchConf.defaults.credentials', []);
|
}).then(({data: nodeData}) => {
|
||||||
return !defaultCreds.some((defaultCred) => {
|
node.originalNodeObject = nodeData;
|
||||||
return credFromPrompt.id === defaultCred.id;
|
nodeIdToChartNodeIdMapping[nodeData.id] = parseInt(workflowMakerNodeId);
|
||||||
|
}).catch(({ data, status }) => {
|
||||||
|
Wait('stop');
|
||||||
|
ProcessErrors($scope, data, status, null, {
|
||||||
|
hdr: $scope.strings.get('error.HEADER')
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
|
}).catch(({ data, status }) => {
|
||||||
|
Wait('stop');
|
||||||
|
ProcessErrors($scope, data, status, null, {
|
||||||
|
hdr: $scope.strings.get('error.HEADER')
|
||||||
});
|
});
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
addPromises.push(TemplatesService.addWorkflowNode({
|
||||||
|
url: $scope.workflowJobTemplateObj.related.workflow_nodes,
|
||||||
|
data: buildSendableNodeData(node)
|
||||||
|
}).then(({data}) => {
|
||||||
|
node.originalNodeObject = data;
|
||||||
|
nodeIdToChartNodeIdMapping[data.id] = parseInt(workflowMakerNodeId);
|
||||||
|
if (_.get(node, 'promptData.launchConf.ask_credential_on_launch')) {
|
||||||
|
// This finds the credentials that were selected in the prompt but don't occur
|
||||||
|
// in the template defaults
|
||||||
|
let credentialIdsToPost = node.promptData.prompts.credentials.value.filter((credFromPrompt) => {
|
||||||
|
let defaultCreds = _.get(node, 'promptData.launchConf.defaults.credentials', []);
|
||||||
|
return !defaultCreds.some((defaultCred) => {
|
||||||
|
return credFromPrompt.id === defaultCred.id;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
credentialIdsToPost.forEach((credentialToPost) => {
|
credentialIdsToPost.forEach((credentialToPost) => {
|
||||||
credentialRequests.push({
|
credentialRequests.push({
|
||||||
id: data.id,
|
id: data.id,
|
||||||
|
data: {
|
||||||
|
id: credentialToPost.id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).catch(({ data, status }) => {
|
||||||
|
Wait('stop');
|
||||||
|
ProcessErrors($scope, data, status, null, {
|
||||||
|
hdr: $scope.strings.get('error.HEADER')
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} else if (node.isEdited) {
|
||||||
|
if (node.unifiedJobTemplate && node.unifiedJobTemplate.unified_job_type === "workflow_approval") {
|
||||||
|
if (node.originalNodeObject.summary_fields.unified_job_template.unified_job_type === "workflow_approval") {
|
||||||
|
approvalTemplatePromises.push(TemplatesService.patchApprovalTemplate({
|
||||||
|
id: node.originalNodeObject.summary_fields.unified_job_template.id,
|
||||||
|
data: {
|
||||||
|
name: node.unifiedJobTemplate.name,
|
||||||
|
description: node.unifiedJobTemplate.description
|
||||||
|
}
|
||||||
|
}).catch(({ data, status }) => {
|
||||||
|
Wait('stop');
|
||||||
|
ProcessErrors($scope, data, status, null, {
|
||||||
|
hdr: $scope.strings.get('error.HEADER')
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
approvalTemplatePromises.push(TemplatesService.createApprovalTemplate({
|
||||||
|
name: node.unifiedJobTemplate.name
|
||||||
|
}).then(({data: approvalTemplateData}) => {
|
||||||
|
// Make sure that this isn't overwriting everything on the node...
|
||||||
|
editPromises.push(TemplatesService.editWorkflowNode({
|
||||||
|
url: $scope.workflowJobTemplateObj.related.workflow_nodes,
|
||||||
data: {
|
data: {
|
||||||
id: credentialToPost.id
|
unified_job_template: approvalTemplateData.id
|
||||||
}
|
}
|
||||||
|
}).catch(({ data, status }) => {
|
||||||
|
Wait('stop');
|
||||||
|
ProcessErrors($scope, data, status, null, {
|
||||||
|
hdr: $scope.strings.get('error.HEADER')
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
}).catch(({ data, status }) => {
|
||||||
|
Wait('stop');
|
||||||
|
ProcessErrors($scope, data, status, null, {
|
||||||
|
hdr: $scope.strings.get('error.HEADER')
|
||||||
});
|
});
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
}).catch(({ data, status }) => {
|
} else {
|
||||||
Wait('stop');
|
editPromises.push(TemplatesService.editWorkflowNode({
|
||||||
ProcessErrors($scope, data, status, null, {
|
id: node.originalNodeObject.id,
|
||||||
hdr: $scope.strings.get('error.HEADER')
|
data: buildSendableNodeData(node)
|
||||||
});
|
}));
|
||||||
}));
|
}
|
||||||
} else if (nodeRef[workflowMakerNodeId].isEdited) {
|
|
||||||
editPromises.push(TemplatesService.editWorkflowNode({
|
|
||||||
id: nodeRef[workflowMakerNodeId].originalNodeObject.id,
|
|
||||||
data: buildSendableNodeData(nodeRef[workflowMakerNodeId])
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (_.get(nodeRef[workflowMakerNodeId], 'promptData.launchConf.ask_credential_on_launch')) {
|
if (_.get(node, 'promptData.launchConf.ask_credential_on_launch')) {
|
||||||
let credentialsNotInPriorCredentials = nodeRef[workflowMakerNodeId].promptData.prompts.credentials.value.filter((credFromPrompt) => {
|
let credentialsNotInPriorCredentials = node.promptData.prompts.credentials.value.filter((credFromPrompt) => {
|
||||||
let defaultCreds = _.get(nodeRef[workflowMakerNodeId], 'promptData.launchConf.defaults.credentials', []);
|
let defaultCreds = _.get(node, 'promptData.launchConf.defaults.credentials', []);
|
||||||
return !defaultCreds.some((defaultCred) => {
|
return !defaultCreds.some((defaultCred) => {
|
||||||
return credFromPrompt.id === defaultCred.id;
|
return credFromPrompt.id === defaultCred.id;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
let credentialsToAdd = credentialsNotInPriorCredentials.filter((credNotInPrior) => {
|
let credentialsToAdd = credentialsNotInPriorCredentials.filter((credNotInPrior) => {
|
||||||
let previousOverrides = _.get(nodeRef[workflowMakerNodeId], 'promptData.prompts.credentials.previousOverrides', []);
|
let previousOverrides = _.get(node, 'promptData.prompts.credentials.previousOverrides', []);
|
||||||
return !previousOverrides.some((priorCred) => {
|
return !previousOverrides.some((priorCred) => {
|
||||||
return credNotInPrior.id === priorCred.id;
|
return credNotInPrior.id === priorCred.id;
|
||||||
});
|
});
|
||||||
@@ -200,8 +267,8 @@ export default ['$scope', 'TemplatesService',
|
|||||||
|
|
||||||
let credentialsToRemove = [];
|
let credentialsToRemove = [];
|
||||||
|
|
||||||
if (_.has(nodeRef[workflowMakerNodeId], 'promptData.prompts.credentials.previousOverrides')) {
|
if (_.has(node, 'promptData.prompts.credentials.previousOverrides')) {
|
||||||
credentialsToRemove = nodeRef[workflowMakerNodeId].promptData.prompts.credentials.previousOverrides.filter((priorCred) => {
|
credentialsToRemove = node.promptData.prompts.credentials.previousOverrides.filter((priorCred) => {
|
||||||
return !credentialsNotInPriorCredentials.some((credNotInPrior) => {
|
return !credentialsNotInPriorCredentials.some((credNotInPrior) => {
|
||||||
return priorCred.id === credNotInPrior.id;
|
return priorCred.id === credNotInPrior.id;
|
||||||
});
|
});
|
||||||
@@ -210,7 +277,7 @@ export default ['$scope', 'TemplatesService',
|
|||||||
|
|
||||||
credentialsToAdd.forEach((credentialToAdd) => {
|
credentialsToAdd.forEach((credentialToAdd) => {
|
||||||
credentialRequests.push({
|
credentialRequests.push({
|
||||||
id: nodeRef[workflowMakerNodeId].originalNodeObject.id,
|
id: node.originalNodeObject.id,
|
||||||
data: {
|
data: {
|
||||||
id: credentialToAdd.id
|
id: credentialToAdd.id
|
||||||
}
|
}
|
||||||
@@ -219,7 +286,7 @@ export default ['$scope', 'TemplatesService',
|
|||||||
|
|
||||||
credentialsToRemove.forEach((credentialToRemove) => {
|
credentialsToRemove.forEach((credentialToRemove) => {
|
||||||
credentialRequests.push({
|
credentialRequests.push({
|
||||||
id: nodeRef[workflowMakerNodeId].originalNodeObject.id,
|
id: node.originalNodeObject.id,
|
||||||
data: {
|
data: {
|
||||||
id: credentialToRemove.id,
|
id: credentialToRemove.id,
|
||||||
disassociate: true
|
disassociate: true
|
||||||
@@ -235,172 +302,177 @@ export default ['$scope', 'TemplatesService',
|
|||||||
return TemplatesService.deleteWorkflowJobTemplateNode(nodeId);
|
return TemplatesService.deleteWorkflowJobTemplateNode(nodeId);
|
||||||
});
|
});
|
||||||
|
|
||||||
$q.all(addPromises.concat(editPromises, deletePromises))
|
$q.all(approvalTemplatePromises)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
let disassociatePromises = [];
|
$q.all(addPromises.concat(editPromises, deletePromises))
|
||||||
let associatePromises = [];
|
.then(() => {
|
||||||
let linkMap = {};
|
let disassociatePromises = [];
|
||||||
|
let associatePromises = [];
|
||||||
|
let linkMap = {};
|
||||||
|
|
||||||
// Build a link map for easy access
|
// Build a link map for easy access
|
||||||
$scope.graphState.arrayOfLinksForChart.forEach(link => {
|
$scope.graphState.arrayOfLinksForChart.forEach(link => {
|
||||||
// link.source.id of 1 is our artificial start node
|
// link.source.id of 1 is our artificial start node
|
||||||
if (link.source.id !== 1) {
|
if (link.source.id !== 1) {
|
||||||
const sourceNodeId = nodeRef[link.source.id].originalNodeObject.id;
|
const sourceNodeId = nodeRef[link.source.id].originalNodeObject.id;
|
||||||
const targetNodeId = nodeRef[link.target.id].originalNodeObject.id;
|
const targetNodeId = nodeRef[link.target.id].originalNodeObject.id;
|
||||||
if (!linkMap[sourceNodeId]) {
|
if (!linkMap[sourceNodeId]) {
|
||||||
linkMap[sourceNodeId] = {};
|
linkMap[sourceNodeId] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
linkMap[sourceNodeId][targetNodeId] = link.edgeType;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
linkMap[sourceNodeId][targetNodeId] = link.edgeType;
|
Object.keys(nodeRef).map((workflowNodeId) => {
|
||||||
}
|
let nodeId = nodeRef[workflowNodeId].originalNodeObject.id;
|
||||||
});
|
if (nodeRef[workflowNodeId].originalNodeObject.success_nodes) {
|
||||||
|
nodeRef[workflowNodeId].originalNodeObject.success_nodes.forEach((successNodeId) => {
|
||||||
Object.keys(nodeRef).map((workflowNodeId) => {
|
|
||||||
let nodeId = nodeRef[workflowNodeId].originalNodeObject.id;
|
|
||||||
if (nodeRef[workflowNodeId].originalNodeObject.success_nodes) {
|
|
||||||
nodeRef[workflowNodeId].originalNodeObject.success_nodes.forEach((successNodeId) => {
|
|
||||||
if (
|
|
||||||
!deletedNodeIds.includes(successNodeId) &&
|
|
||||||
(!linkMap[nodeId] ||
|
|
||||||
!linkMap[nodeId][successNodeId] ||
|
|
||||||
linkMap[nodeId][successNodeId] !== "success")
|
|
||||||
) {
|
|
||||||
disassociatePromises.push(
|
|
||||||
TemplatesService.disassociateWorkflowNode({
|
|
||||||
parentId: nodeId,
|
|
||||||
nodeId: successNodeId,
|
|
||||||
edge: "success"
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (nodeRef[workflowNodeId].originalNodeObject.failure_nodes) {
|
|
||||||
nodeRef[workflowNodeId].originalNodeObject.failure_nodes.forEach((failureNodeId) => {
|
|
||||||
if (
|
|
||||||
!deletedNodeIds.includes(failureNodeId) &&
|
|
||||||
(!linkMap[nodeId] ||
|
|
||||||
!linkMap[nodeId][failureNodeId] ||
|
|
||||||
linkMap[nodeId][failureNodeId] !== "failure")
|
|
||||||
) {
|
|
||||||
disassociatePromises.push(
|
|
||||||
TemplatesService.disassociateWorkflowNode({
|
|
||||||
parentId: nodeId,
|
|
||||||
nodeId: failureNodeId,
|
|
||||||
edge: "failure"
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (nodeRef[workflowNodeId].originalNodeObject.always_nodes) {
|
|
||||||
nodeRef[workflowNodeId].originalNodeObject.always_nodes.forEach((alwaysNodeId) => {
|
|
||||||
if (
|
|
||||||
!deletedNodeIds.includes(alwaysNodeId) &&
|
|
||||||
(!linkMap[nodeId] ||
|
|
||||||
!linkMap[nodeId][alwaysNodeId] ||
|
|
||||||
linkMap[nodeId][alwaysNodeId] !== "always")
|
|
||||||
) {
|
|
||||||
disassociatePromises.push(
|
|
||||||
TemplatesService.disassociateWorkflowNode({
|
|
||||||
parentId: nodeId,
|
|
||||||
nodeId: alwaysNodeId,
|
|
||||||
edge: "always"
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Object.keys(linkMap).map((sourceNodeId) => {
|
|
||||||
Object.keys(linkMap[sourceNodeId]).map((targetNodeId) => {
|
|
||||||
const sourceChartNodeId = nodeIdToChartNodeIdMapping[sourceNodeId];
|
|
||||||
const targetChartNodeId = nodeIdToChartNodeIdMapping[targetNodeId];
|
|
||||||
switch(linkMap[sourceNodeId][targetNodeId]) {
|
|
||||||
case "success":
|
|
||||||
if (
|
if (
|
||||||
!nodeRef[sourceChartNodeId].originalNodeObject.success_nodes ||
|
!deletedNodeIds.includes(successNodeId) &&
|
||||||
!nodeRef[sourceChartNodeId].originalNodeObject.success_nodes.includes(nodeRef[targetChartNodeId].originalNodeObject.id)
|
(!linkMap[nodeId] ||
|
||||||
|
!linkMap[nodeId][successNodeId] ||
|
||||||
|
linkMap[nodeId][successNodeId] !== "success")
|
||||||
) {
|
) {
|
||||||
associatePromises.push(
|
disassociatePromises.push(
|
||||||
TemplatesService.associateWorkflowNode({
|
TemplatesService.disassociateWorkflowNode({
|
||||||
parentId: parseInt(sourceNodeId),
|
parentId: nodeId,
|
||||||
nodeId: parseInt(targetNodeId),
|
nodeId: successNodeId,
|
||||||
edge: "success"
|
edge: "success"
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
});
|
||||||
case "failure":
|
}
|
||||||
|
if (nodeRef[workflowNodeId].originalNodeObject.failure_nodes) {
|
||||||
|
nodeRef[workflowNodeId].originalNodeObject.failure_nodes.forEach((failureNodeId) => {
|
||||||
if (
|
if (
|
||||||
!nodeRef[sourceChartNodeId].originalNodeObject.failure_nodes ||
|
!deletedNodeIds.includes(failureNodeId) &&
|
||||||
!nodeRef[sourceChartNodeId].originalNodeObject.failure_nodes.includes(nodeRef[targetChartNodeId].originalNodeObject.id)
|
(!linkMap[nodeId] ||
|
||||||
|
!linkMap[nodeId][failureNodeId] ||
|
||||||
|
linkMap[nodeId][failureNodeId] !== "failure")
|
||||||
) {
|
) {
|
||||||
associatePromises.push(
|
disassociatePromises.push(
|
||||||
TemplatesService.associateWorkflowNode({
|
TemplatesService.disassociateWorkflowNode({
|
||||||
parentId: parseInt(sourceNodeId),
|
parentId: nodeId,
|
||||||
nodeId: parseInt(targetNodeId),
|
nodeId: failureNodeId,
|
||||||
edge: "failure"
|
edge: "failure"
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
});
|
||||||
case "always":
|
}
|
||||||
|
if (nodeRef[workflowNodeId].originalNodeObject.always_nodes) {
|
||||||
|
nodeRef[workflowNodeId].originalNodeObject.always_nodes.forEach((alwaysNodeId) => {
|
||||||
if (
|
if (
|
||||||
!nodeRef[sourceChartNodeId].originalNodeObject.always_nodes ||
|
!deletedNodeIds.includes(alwaysNodeId) &&
|
||||||
!nodeRef[sourceChartNodeId].originalNodeObject.always_nodes.includes(nodeRef[targetChartNodeId].originalNodeObject.id)
|
(!linkMap[nodeId] ||
|
||||||
|
!linkMap[nodeId][alwaysNodeId] ||
|
||||||
|
linkMap[nodeId][alwaysNodeId] !== "always")
|
||||||
) {
|
) {
|
||||||
associatePromises.push(
|
disassociatePromises.push(
|
||||||
TemplatesService.associateWorkflowNode({
|
TemplatesService.disassociateWorkflowNode({
|
||||||
parentId: parseInt(sourceNodeId),
|
parentId: nodeId,
|
||||||
nodeId: parseInt(targetNodeId),
|
nodeId: alwaysNodeId,
|
||||||
edge: "always"
|
edge: "always"
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
$q.all(disassociatePromises)
|
Object.keys(linkMap).map((sourceNodeId) => {
|
||||||
.then(() => {
|
Object.keys(linkMap[sourceNodeId]).map((targetNodeId) => {
|
||||||
let credentialPromises = credentialRequests.map((request) => {
|
const sourceChartNodeId = nodeIdToChartNodeIdMapping[sourceNodeId];
|
||||||
return TemplatesService.postWorkflowNodeCredential({
|
const targetChartNodeId = nodeIdToChartNodeIdMapping[targetNodeId];
|
||||||
id: request.id,
|
switch(linkMap[sourceNodeId][targetNodeId]) {
|
||||||
data: request.data
|
case "success":
|
||||||
});
|
if (
|
||||||
});
|
!nodeRef[sourceChartNodeId].originalNodeObject.success_nodes ||
|
||||||
|
!nodeRef[sourceChartNodeId].originalNodeObject.success_nodes.includes(nodeRef[targetChartNodeId].originalNodeObject.id)
|
||||||
return $q.all(associatePromises.concat(credentialPromises))
|
) {
|
||||||
.then(() => {
|
associatePromises.push(
|
||||||
Wait('stop');
|
TemplatesService.associateWorkflowNode({
|
||||||
$scope.workflowChangesUnsaved = false;
|
parentId: parseInt(sourceNodeId),
|
||||||
$scope.workflowChangesStarted = false;
|
nodeId: parseInt(targetNodeId),
|
||||||
$scope.closeDialog();
|
edge: "success"
|
||||||
}).catch(({ data, status }) => {
|
})
|
||||||
Wait('stop');
|
);
|
||||||
ProcessErrors($scope, data, status, null, {
|
}
|
||||||
hdr: $scope.strings.get('error.HEADER')
|
break;
|
||||||
});
|
case "failure":
|
||||||
});
|
if (
|
||||||
}).catch(({
|
!nodeRef[sourceChartNodeId].originalNodeObject.failure_nodes ||
|
||||||
data,
|
!nodeRef[sourceChartNodeId].originalNodeObject.failure_nodes.includes(nodeRef[targetChartNodeId].originalNodeObject.id)
|
||||||
status
|
) {
|
||||||
}) => {
|
associatePromises.push(
|
||||||
Wait('stop');
|
TemplatesService.associateWorkflowNode({
|
||||||
ProcessErrors($scope, data, status, null, {
|
parentId: parseInt(sourceNodeId),
|
||||||
hdr: $scope.strings.get('error.HEADER')
|
nodeId: parseInt(targetNodeId),
|
||||||
|
edge: "failure"
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "always":
|
||||||
|
if (
|
||||||
|
!nodeRef[sourceChartNodeId].originalNodeObject.always_nodes ||
|
||||||
|
!nodeRef[sourceChartNodeId].originalNodeObject.always_nodes.includes(nodeRef[targetChartNodeId].originalNodeObject.id)
|
||||||
|
) {
|
||||||
|
associatePromises.push(
|
||||||
|
TemplatesService.associateWorkflowNode({
|
||||||
|
parentId: parseInt(sourceNodeId),
|
||||||
|
nodeId: parseInt(targetNodeId),
|
||||||
|
edge: "always"
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}).catch(({ data, status }) => {
|
|
||||||
Wait('stop');
|
|
||||||
ProcessErrors($scope, data, status, null, {
|
|
||||||
hdr: $scope.strings.get('error.HEADER')
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
$q.all(disassociatePromises)
|
||||||
|
.then(() => {
|
||||||
|
let credentialPromises = credentialRequests.map((request) => {
|
||||||
|
return TemplatesService.postWorkflowNodeCredential({
|
||||||
|
id: request.id,
|
||||||
|
data: request.data
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return $q.all(associatePromises.concat(credentialPromises))
|
||||||
|
.then(() => {
|
||||||
|
Wait('stop');
|
||||||
|
$scope.workflowChangesUnsaved = false;
|
||||||
|
$scope.workflowChangesStarted = false;
|
||||||
|
$scope.closeDialog();
|
||||||
|
}).catch(({ data, status }) => {
|
||||||
|
Wait('stop');
|
||||||
|
ProcessErrors($scope, data, status, null, {
|
||||||
|
hdr: $scope.strings.get('error.HEADER')
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}).catch(({
|
||||||
|
data,
|
||||||
|
status
|
||||||
|
}) => {
|
||||||
|
Wait('stop');
|
||||||
|
ProcessErrors($scope, data, status, null, {
|
||||||
|
hdr: $scope.strings.get('error.HEADER')
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}).catch(({ data, status }) => {
|
||||||
|
Wait('stop');
|
||||||
|
ProcessErrors($scope, data, status, null, {
|
||||||
|
hdr: $scope.strings.get('error.HEADER')
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// TODO: handle
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
let deletePromises = deletedNodeIds.map((nodeId) => {
|
let deletePromises = deletedNodeIds.map((nodeId) => {
|
||||||
@@ -511,17 +583,27 @@ export default ['$scope', 'TemplatesService',
|
|||||||
$scope.formState.showNodeForm = true;
|
$scope.formState.showNodeForm = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.confirmNodeForm = (selectedTemplate, promptData, edgeType) => {
|
$scope.confirmNodeForm = (selectedTemplate, promptData, edgeType, pauseNode) => {
|
||||||
$scope.workflowChangesUnsaved = true;
|
$scope.workflowChangesUnsaved = true;
|
||||||
const nodeId = $scope.nodeConfig.nodeId;
|
const nodeId = $scope.nodeConfig.nodeId;
|
||||||
if ($scope.nodeConfig.mode === "add") {
|
if ($scope.nodeConfig.mode === "add") {
|
||||||
if (selectedTemplate && edgeType && edgeType.value) {
|
if (edgeType && edgeType.value) {
|
||||||
nodeRef[$scope.nodeConfig.nodeId] = {
|
if (selectedTemplate) {
|
||||||
fullUnifiedJobTemplateObject: selectedTemplate,
|
nodeRef[$scope.nodeConfig.nodeId] = {
|
||||||
promptData,
|
fullUnifiedJobTemplateObject: selectedTemplate,
|
||||||
isNew: true
|
promptData,
|
||||||
};
|
isNew: true
|
||||||
|
};
|
||||||
|
} else if (pauseNode && pauseNode.isPauseNode) {
|
||||||
|
nodeRef[$scope.nodeConfig.nodeId] = {
|
||||||
|
unifiedJobTemplate: {
|
||||||
|
name: pauseNode.name,
|
||||||
|
description: pauseNode.description,
|
||||||
|
unified_job_type: "workflow_approval"
|
||||||
|
},
|
||||||
|
isNew: true
|
||||||
|
};
|
||||||
|
}
|
||||||
$scope.graphState.nodeBeingAdded = null;
|
$scope.graphState.nodeBeingAdded = null;
|
||||||
|
|
||||||
$scope.graphState.arrayOfLinksForChart.map( (link) => {
|
$scope.graphState.arrayOfLinksForChart.map( (link) => {
|
||||||
@@ -534,6 +616,7 @@ export default ['$scope', 'TemplatesService',
|
|||||||
} else if ($scope.nodeConfig.mode === "edit") {
|
} else if ($scope.nodeConfig.mode === "edit") {
|
||||||
if (selectedTemplate) {
|
if (selectedTemplate) {
|
||||||
nodeRef[$scope.nodeConfig.nodeId].fullUnifiedJobTemplateObject = selectedTemplate;
|
nodeRef[$scope.nodeConfig.nodeId].fullUnifiedJobTemplateObject = selectedTemplate;
|
||||||
|
nodeRef[$scope.nodeConfig.nodeId].unifiedJobTemplate = selectedTemplate;
|
||||||
nodeRef[$scope.nodeConfig.nodeId].promptData = _.cloneDeep(promptData);
|
nodeRef[$scope.nodeConfig.nodeId].promptData = _.cloneDeep(promptData);
|
||||||
nodeRef[$scope.nodeConfig.nodeId].isEdited = true;
|
nodeRef[$scope.nodeConfig.nodeId].isEdited = true;
|
||||||
$scope.graphState.nodeBeingEdited = null;
|
$scope.graphState.nodeBeingEdited = null;
|
||||||
@@ -546,12 +629,30 @@ export default ['$scope', 'TemplatesService',
|
|||||||
link.source.unifiedJobTemplate = selectedTemplate;
|
link.source.unifiedJobTemplate = selectedTemplate;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else if (pauseNode && pauseNode.isPauseNode) {
|
||||||
|
// If it's a _new_ pause node then we'll want to create the new ujt
|
||||||
|
// If it's an existing pause node then we'll want to update the ujt
|
||||||
|
nodeRef[$scope.nodeConfig.nodeId].unifiedJobTemplate = {
|
||||||
|
name: pauseNode.name,
|
||||||
|
description: pauseNode.description,
|
||||||
|
unified_job_type: "workflow_approval"
|
||||||
|
},
|
||||||
|
nodeRef[$scope.nodeConfig.nodeId].isEdited = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.graphState.arrayOfNodesForChart.map( (node) => {
|
$scope.graphState.arrayOfNodesForChart.map( (node) => {
|
||||||
if (node.id === nodeId) {
|
if (node.id === nodeId) {
|
||||||
node.unifiedJobTemplate = selectedTemplate;
|
if (pauseNode && pauseNode.isPauseNode) {
|
||||||
|
node.unifiedJobTemplate = {
|
||||||
|
unified_job_type: 'workflow_approval',
|
||||||
|
name: pauseNode.name,
|
||||||
|
description: pauseNode.description
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
node.unifiedJobTemplate = selectedTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -128,7 +128,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="WorkflowMaker-contentRight">
|
<div class="WorkflowMaker-contentRight">
|
||||||
<span ng-if="formState.showNodeForm">
|
<span ng-if="formState.showNodeForm">
|
||||||
<workflow-node-form node-config="nodeConfig" workflow-job-template-obj="workflowJobTemplateObj" select="confirmNodeForm(selectedTemplate, promptData, edgeType)" cancel="cancelNodeForm()" read-only="!workflowJobTemplateObj.summary_fields.user_capabilities.edit"/>
|
<workflow-node-form node-config="nodeConfig" workflow-job-template-obj="workflowJobTemplateObj" select="confirmNodeForm(selectedTemplate, promptData, edgeType, pauseNode)" cancel="cancelNodeForm()" read-only="!workflowJobTemplateObj.summary_fields.user_capabilities.edit"/>
|
||||||
</span>
|
</span>
|
||||||
<span ng-if="formState.showLinkForm">
|
<span ng-if="formState.showLinkForm">
|
||||||
<workflow-link-form link-config="linkConfig" read-only="!workflowJobTemplateObj.summary_fields.user_capabilities.edit" select="confirmLinkForm(edgeType)" cancel="cancelLinkForm()" unlink="unlink()"/>
|
<workflow-link-form link-config="linkConfig" read-only="!workflowJobTemplateObj.summary_fields.user_capabilities.edit" select="confirmLinkForm(edgeType)" cancel="cancelLinkForm()" unlink="unlink()"/>
|
||||||
|
|||||||
Reference in New Issue
Block a user