mirror of
https://github.com/ansible/awx.git
synced 2026-01-14 19:30:39 -03:30
updated template list to using new components
This commit is contained in:
parent
815cd829e0
commit
aea37654e2
@ -3,6 +3,7 @@ import atLibComponents from '~components';
|
||||
import atLibModels from '~models';
|
||||
|
||||
import atFeaturesCredentials from '~features/credentials';
|
||||
import atFeaturesTemplates from '~features/templates';
|
||||
|
||||
const MODULE_NAME = 'at.features';
|
||||
|
||||
@ -10,7 +11,8 @@ angular.module(MODULE_NAME, [
|
||||
atLibServices,
|
||||
atLibComponents,
|
||||
atLibModels,
|
||||
atFeaturesCredentials
|
||||
atFeaturesCredentials,
|
||||
atFeaturesTemplates
|
||||
]);
|
||||
|
||||
export default MODULE_NAME;
|
||||
|
||||
11
awx/ui/client/features/templates/index.js
Normal file
11
awx/ui/client/features/templates/index.js
Normal file
@ -0,0 +1,11 @@
|
||||
import TemplatesStrings from './templates.strings';
|
||||
import ListController from './list-templates.controller';
|
||||
|
||||
const MODULE_NAME = 'at.features.templates';
|
||||
|
||||
angular
|
||||
.module(MODULE_NAME, [])
|
||||
.controller('ListController', ListController)
|
||||
.service('TemplatesStrings', TemplatesStrings);
|
||||
|
||||
export default MODULE_NAME;
|
||||
178
awx/ui/client/features/templates/list-templates.controller.js
Normal file
178
awx/ui/client/features/templates/list-templates.controller.js
Normal file
@ -0,0 +1,178 @@
|
||||
function ListTemplatesController (model, strings, $state, $scope, rbacUiControlService, Dataset, $filter, Alert, InitiatePlaybookRun, Prompt, Wait, ProcessErrors) {
|
||||
const vm = this || {}
|
||||
const unifiedJobTemplate = model;
|
||||
|
||||
init();
|
||||
|
||||
function init() {
|
||||
vm.strings = strings;
|
||||
|
||||
// TODO: add the permission based functionality to the base model
|
||||
$scope.canAdd = false;
|
||||
rbacUiControlService.canAdd("job_templates")
|
||||
.then(function(params) {
|
||||
$scope.canAddJobTemplate = params.canAdd;
|
||||
});
|
||||
rbacUiControlService.canAdd("workflow_job_templates")
|
||||
.then(function(params) {
|
||||
$scope.canAddWorkflowJobTemplate = params.canAdd;
|
||||
});
|
||||
$scope.$watchGroup(["canAddJobTemplate", "canAddWorkflowJobTemplate"], function() {
|
||||
if ($scope.canAddJobTemplate || $scope.canAddWorkflowJobTemplate) {
|
||||
$scope.canAdd = true;
|
||||
} else {
|
||||
$scope.canAdd = false;
|
||||
}
|
||||
});
|
||||
|
||||
$scope.list = {
|
||||
iterator: 'template',
|
||||
name: 'templates'
|
||||
};
|
||||
$scope.collection = {
|
||||
basePath: 'unified_job_templates',
|
||||
iterator: 'template'
|
||||
};
|
||||
$scope[`${$scope.list.iterator}_dataset`] = Dataset.data;
|
||||
$scope[$scope.list.name] = $scope[`${$scope.list.iterator}_dataset`].results;
|
||||
$scope.$on('updateDataset', function(e, dataset) {
|
||||
$scope[`${$scope.list.iterator}_dataset`] = dataset;
|
||||
$scope[$scope.list.name] = dataset.results;
|
||||
});
|
||||
}
|
||||
|
||||
// get modified date and user who modified it
|
||||
vm.getModified = function(template) {
|
||||
let val = "";
|
||||
if (template.modified) {
|
||||
val += $filter('longDate')(template.modified);
|
||||
}
|
||||
if (_.has(template, 'summary_fields.modified_by.username')) {
|
||||
val += ` by <a href="/#/users/${template.summary_fields.modified_by.id}">${template.summary_fields.modified_by.username}</a>`;
|
||||
}
|
||||
if (val === "") {
|
||||
val = undefined;
|
||||
}
|
||||
return val;
|
||||
};
|
||||
|
||||
// get last ran date and user who ran it
|
||||
vm.getRan = function(template) {
|
||||
let val = "";
|
||||
if (template.last_job_run) {
|
||||
val += $filter('longDate')(template.last_job_run);
|
||||
}
|
||||
|
||||
// TODO: when API gives back a user who last ran the job in summary fields, uncomment and
|
||||
// update this code
|
||||
// if (template && template.summary_fields && template.summary_fields.modified_by &&
|
||||
// template.summary_fields.modified_by.username) {
|
||||
// val += ` by <a href="/#/users/${template.summary_fields.modified_by.id}">${template.summary_fields.modified_by.username}</a>`;
|
||||
// }
|
||||
|
||||
if (val === "") {
|
||||
val = undefined;
|
||||
}
|
||||
return val;
|
||||
};
|
||||
|
||||
// get pretified template type names from options
|
||||
vm.templateTypes = unifiedJobTemplate.options('actions.GET.type.choices')
|
||||
.reduce((acc, i) => {
|
||||
acc[i[0]] = i[1];
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
// get if you should show the active indicator for the row or not
|
||||
// TODO: edit indicator doesn't update when you enter edit route after initial load right now
|
||||
vm.activeId = parseInt($state.params.job_template_id || $state.params.workflow_template_id);
|
||||
|
||||
// TODO: update to new way of launching job after mike opens his pr
|
||||
vm.submitJob = function(template) {
|
||||
if(template) {
|
||||
if(template.type && (template.type === 'Job Template' || template.type === 'job_template')) {
|
||||
InitiatePlaybookRun({ scope: $scope, id: template.id, job_type: 'job_template' });
|
||||
}
|
||||
else if(template.type && (template.type === 'Workflow Job Template' || template.type === 'workflow_job_template')) {
|
||||
InitiatePlaybookRun({ scope: $scope, id: template.id, job_type: 'workflow_job_template' });
|
||||
}
|
||||
else {
|
||||
var alertStrings = {
|
||||
header: 'Error: Unable to determine template type',
|
||||
body: 'We were unable to determine this template\'s type while launching.'
|
||||
}
|
||||
Alert(strings.get('ALERT', alertStrings));
|
||||
}
|
||||
} else {
|
||||
var alertStrings = {
|
||||
header: 'Error: Unable to launch template',
|
||||
body: 'Template parameter is missing'
|
||||
}
|
||||
Alert(strings.get('ALERT', alertStrings));
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: implement copy function
|
||||
vm.copyTemplate = function(template) {
|
||||
};
|
||||
|
||||
vm.deleteTemplate = function(template) {
|
||||
var action = function() {
|
||||
$('#prompt-modal').modal('hide');
|
||||
Wait('start');
|
||||
// TODO: The request url doesn't work here
|
||||
unifiedJobTemplate.request('delete', template.id)
|
||||
.then(() => {
|
||||
|
||||
let reloadListStateParams = null;
|
||||
|
||||
if($scope.templates.length === 1 && $state.params.template_search && !_.isEmpty($state.params.template_search.page) && $state.params.template_search.page !== '1') {
|
||||
reloadListStateParams = _.cloneDeep($state.params);
|
||||
reloadListStateParams.template_search.page = (parseInt(reloadListStateParams.template_search.page)-1).toString();
|
||||
}
|
||||
|
||||
if (parseInt($state.params.template_id) === template.id) {
|
||||
$state.go("^", reloadListStateParams, { reload: true });
|
||||
} else {
|
||||
$state.go('.', reloadListStateParams, {reload: true});
|
||||
}
|
||||
})
|
||||
.catch(({data, status}) => {
|
||||
ProcessErrors($scope, data, status, null, {
|
||||
hdr: string.get('error.HEADER'),
|
||||
msg: strings.get('error.CALL', {path: "" + unifiedJobTemplate.path + template.id, status})
|
||||
});
|
||||
})
|
||||
.finally(function() {
|
||||
Wait('stop');
|
||||
});
|
||||
};
|
||||
|
||||
let deleteModalBody = `<div class="Prompt-bodyQuery">${strings.get('deleteResource.CONFIRM', 'template')}</div>`;
|
||||
|
||||
Prompt({
|
||||
hdr: strings.get('deleteResource.HEADER'),
|
||||
resourceName: $filter('sanitize')(template.name),
|
||||
body: deleteModalBody,
|
||||
action: action,
|
||||
actionText: 'DELETE'
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
ListTemplatesController.$inject = [
|
||||
'resolvedModels',
|
||||
'TemplatesStrings',
|
||||
'$state',
|
||||
'$scope',
|
||||
'rbacUiControlService',
|
||||
'Dataset',
|
||||
'$filter',
|
||||
'Alert',
|
||||
'InitiatePlaybookRun',
|
||||
'Prompt',
|
||||
'Wait',
|
||||
'ProcessErrors'
|
||||
];
|
||||
|
||||
export default ListTemplatesController;
|
||||
@ -1,15 +1,24 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
import ListController from './list-templates.controller';
|
||||
const listTemplate = require('~features/templates/list.view.html');
|
||||
import { N_ } from '../../src/i18n';
|
||||
|
||||
import { N_ } from '../../i18n';
|
||||
function TemplatesResolve (UnifiedJobTemplate) {
|
||||
return new UnifiedJobTemplate(['get', 'options']);
|
||||
}
|
||||
|
||||
TemplatesResolve.$inject = [
|
||||
'UnifiedJobTemplateModel'
|
||||
];
|
||||
|
||||
export default {
|
||||
name: 'templates',
|
||||
route: '/templates',
|
||||
ncyBreadcrumb: {
|
||||
// TODO: this would be best done with our
|
||||
// strings file pattern, but it's not possible to
|
||||
// get a handle on this route within a DI based
|
||||
// on the state tree generation as present in
|
||||
// src/templates currently
|
||||
label: N_("TEMPLATES")
|
||||
},
|
||||
data: {
|
||||
@ -32,18 +41,13 @@ export default {
|
||||
searchPrefix: 'template',
|
||||
views: {
|
||||
'@': {
|
||||
controller: 'TemplatesListController',
|
||||
templateProvider: function(TemplateList, generateList) {
|
||||
let html = generateList.build({
|
||||
list: TemplateList,
|
||||
mode: 'edit'
|
||||
});
|
||||
html = generateList.wrapPanel(html);
|
||||
return generateList.insertFormView() + html;
|
||||
}
|
||||
controller: ListController,
|
||||
templateUrl: listTemplate,
|
||||
controllerAs: 'vm'
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
resolvedModels: TemplatesResolve,
|
||||
Dataset: ['TemplateList', 'QuerySet', '$stateParams', 'GetBasePath',
|
||||
function(list, qs, $stateParams, GetBasePath) {
|
||||
let path = GetBasePath(list.basePath) || GetBasePath(list.name);
|
||||
118
awx/ui/client/features/templates/list.view.html
Normal file
118
awx/ui/client/features/templates/list.view.html
Normal file
@ -0,0 +1,118 @@
|
||||
<div ui-view="form"></div>
|
||||
<at-panel>
|
||||
<at-panel-heading hide-dismiss="true">
|
||||
{{:: vm.strings.get('list.PANEL_TITLE') }}
|
||||
<div class="at-Panel-headingTitleBadge" ng-show="template_dataset.count">
|
||||
{{ template_dataset.count }}
|
||||
</div>
|
||||
</at-panel-heading>
|
||||
|
||||
<at-panel-body>
|
||||
<div class="at-List-toolbar">
|
||||
<smart-search
|
||||
class="at-List-search"
|
||||
django-model="templates"
|
||||
base-path="unified_job_templates"
|
||||
iterator="template"
|
||||
list="list"
|
||||
dataset="template_dataset"
|
||||
collection="collection"
|
||||
search-tags="searchTags"
|
||||
query-set="querySet">
|
||||
</smart-search>
|
||||
<div class="at-List-toolbarAction" ng-show="canAdd">
|
||||
<button
|
||||
type="button"
|
||||
class="at-List-toolbarActionButton at-Button--success"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false">
|
||||
+ {{:: vm.strings.get('list.ADD_BUTTON_LABEL') }}
|
||||
<span class="at-List-toolbarDropdownCarat"></span>
|
||||
<span class="sr-only">Toggle Dropdown</span>
|
||||
</button>
|
||||
<ul class="dropdown-menu at-List-toolbarActionDropdownMenu">
|
||||
<li>
|
||||
<a ui-sref="templates.addJobTemplate">
|
||||
{{:: vm.strings.get('list.ADD_DD_JT_LABEL') }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a ui-sref="templates.addWorkflowJobTemplate">
|
||||
{{:: vm.strings.get('list.ADD_DD_WF_LABEL') }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<at-list results="templates">
|
||||
<!-- TODO: implement resources are missing red indicator as present in mockup -->
|
||||
<at-row ng-repeat="template in templates"
|
||||
ng-class="{'at-Row--active': (template.id === vm.activeId)}"
|
||||
template-id="{{ template.id }}">
|
||||
<div class="at-Row-items">
|
||||
<at-row-item
|
||||
header-value="{{ template.name }}"
|
||||
header-link="/#/templates/job_template/{{ template.id }}"
|
||||
header-tag="{{ vm.templateTypes[template.type] }}"
|
||||
ng-if="template.type === 'job_template'">
|
||||
</at-row-item>
|
||||
<at-row-item
|
||||
header-value="{{ template.name }}"
|
||||
header-link="/#/templates/workflow_job_template/{{ template.id }}"
|
||||
header-tag="{{ vm.templateTypes[template.type] }}"
|
||||
ng-if="template.type === 'workflow_job_template'">
|
||||
</at-row-item>
|
||||
<at-row-item
|
||||
label-value="{{:: vm.strings.get('list.ROW_ITEM_LABEL_ACTIVITY') }}"
|
||||
smart-status="template">
|
||||
</at-row-item>
|
||||
<at-row-item
|
||||
label-value="{{:: vm.strings.get('list.ROW_ITEM_LABEL_INVENTORY') }}"
|
||||
value="{{ template.summary_fields.inventory.name }}"
|
||||
value-link="/#/inventories/inventory/{{ template.summary_fields.inventory.id }}">
|
||||
</at-row-item>
|
||||
<at-row-item
|
||||
label-value="{{:: vm.strings.get('list.ROW_ITEM_LABEL_PROJECT') }}"
|
||||
value="{{ template.summary_fields.project.name }}"
|
||||
value-link="/#/projects/{{ template.summary_fields.project.id }}">
|
||||
</at-row-item>
|
||||
<!-- TODO: add see more for creds -->
|
||||
<at-row-item
|
||||
label-value="{{:: vm.strings.get('list.ROW_ITEM_LABEL_CREDENTIALS') }}"
|
||||
tag-values="template.summary_fields.credentials"
|
||||
tags-are-creds="true">
|
||||
</at-row-item>
|
||||
<at-row-item
|
||||
label-value="{{:: vm.strings.get('list.ROW_ITEM_LABEL_MODIFIED') }}"
|
||||
value="{{ vm.getModified(template) }}">
|
||||
</at-row-item>
|
||||
<at-row-item
|
||||
label-value="{{:: vm.strings.get('list.ROW_ITEM_LABEL_RAN') }}"
|
||||
value="{{ vm.getRan(template) }}">
|
||||
</at-row-item>
|
||||
<labels-list class="LabelList" show-delete="false" is-row-item="true">
|
||||
</labels-list>
|
||||
</div>
|
||||
<div class="at-Row-actions">
|
||||
<at-row-action icon="icon-launch" ng-click="vm.submitJob(template)"
|
||||
ng-show="template.summary_fields.user_capabilities.start">
|
||||
</at-row-action>
|
||||
<at-row-action icon="fa-copy" ng-click="vm.copyTemplate(template)"
|
||||
ng-show="template.summary_fields.user_capabilities.copy">
|
||||
</at-row-action>
|
||||
<at-row-action icon="fa-trash" ng-click="vm.deleteTemplate(template)"
|
||||
ng-show="template.summary_fields.user_capabilities.delete">
|
||||
</at-row-action>
|
||||
</div>
|
||||
</at-row>
|
||||
</at-list>
|
||||
<paginate
|
||||
collection="collection"
|
||||
dataset="template_dataset"
|
||||
iterator="template"
|
||||
base-path="unified_job_templates"
|
||||
query-set="querySet">
|
||||
</paginate>
|
||||
</at-panel-body>
|
||||
</at-panel>
|
||||
27
awx/ui/client/features/templates/templates.strings.js
Normal file
27
awx/ui/client/features/templates/templates.strings.js
Normal file
@ -0,0 +1,27 @@
|
||||
function TemplatesStrings (BaseString) {
|
||||
BaseString.call(this, 'templates');
|
||||
|
||||
const { t } = this;
|
||||
const ns = this.templates;
|
||||
|
||||
ns.state = {
|
||||
LIST_BREADCRUMB_LABEL: t.s('TEMPLATES')
|
||||
}
|
||||
|
||||
ns.list = {
|
||||
PANEL_TITLE: t.s('TEMPLATES'),
|
||||
ADD_BUTTON_LABEL: t.s('ADD'),
|
||||
ADD_DD_JT_LABEL: t.s('Job Template'),
|
||||
ADD_DD_WF_LABEL: t.s('Workflow Template'),
|
||||
ROW_ITEM_LABEL_ACTIVITY: t.s('Activity'),
|
||||
ROW_ITEM_LABEL_INVENTORY: t.s('Inventory'),
|
||||
ROW_ITEM_LABEL_PROJECT: t.s('Project'),
|
||||
ROW_ITEM_LABEL_CREDENTIALS: t.s('Credentials'),
|
||||
ROW_ITEM_LABEL_MODIFIED: t.s('Last Modified'),
|
||||
ROW_ITEM_LABEL_RAN: t.s('Last Ran'),
|
||||
}
|
||||
}
|
||||
|
||||
TemplatesStrings.$inject = ['BaseStringService'];
|
||||
|
||||
export default TemplatesStrings;
|
||||
@ -1,6 +1,7 @@
|
||||
@import 'action/_index';
|
||||
@import 'input/_index';
|
||||
@import 'layout/_index';
|
||||
@import 'list/_index';
|
||||
@import 'modal/_index';
|
||||
@import 'panel/_index';
|
||||
@import 'popover/_index';
|
||||
|
||||
@ -88,6 +88,10 @@ function ComponentsStrings (BaseString) {
|
||||
ALL: t.s('All'),
|
||||
FAILED: t.s('Failed')
|
||||
};
|
||||
|
||||
ns.list = {
|
||||
DEFAULT_EMPTY_LIST: t.s('List is empty.')
|
||||
};
|
||||
}
|
||||
|
||||
ComponentsStrings.$inject = ['BaseStringService'];
|
||||
|
||||
@ -16,6 +16,10 @@ import inputText from '~components/input/text.directive';
|
||||
import inputTextarea from '~components/input/textarea.directive';
|
||||
import inputTextareaSecret from '~components/input/textarea-secret.directive';
|
||||
import layout from '~components/layout/layout.directive';
|
||||
import list from '~components/list/list.directive';
|
||||
import row from '~components/list/row.directive';
|
||||
import rowItem from '~components/list/row-item.directive';
|
||||
import rowAction from '~components/list/row-action.directive';
|
||||
import modal from '~components/modal/modal.directive';
|
||||
import panel from '~components/panel/panel.directive';
|
||||
import panelBody from '~components/panel/body.directive';
|
||||
@ -54,6 +58,10 @@ angular
|
||||
.directive('atInputTextarea', inputTextarea)
|
||||
.directive('atInputTextareaSecret', inputTextareaSecret)
|
||||
.directive('atLayout', layout)
|
||||
.directive('atList', list)
|
||||
.directive('atRow', row)
|
||||
.directive('atRowItem', rowItem)
|
||||
.directive('atRowAction', rowAction)
|
||||
.directive('atModal', modal)
|
||||
.directive('atPanel', panel)
|
||||
.directive('atPanelBody', panelBody)
|
||||
|
||||
@ -175,9 +175,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@breakpoint-sm: 700px;
|
||||
|
||||
@media screen and (max-width: @breakpoint-sm) {
|
||||
@media screen and (max-width: @at-breakpoint-mobile-layout) {
|
||||
.at-Layout {
|
||||
&-side {
|
||||
top: 60px;
|
||||
@ -215,4 +213,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
191
awx/ui/client/lib/components/list/_index.less
Normal file
191
awx/ui/client/lib/components/list/_index.less
Normal file
@ -0,0 +1,191 @@
|
||||
.at-List {
|
||||
margin-top: @at-margin-top-list;
|
||||
}
|
||||
|
||||
.at-List--empty {
|
||||
margin-top: @at-margin-top-list;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: @at-height-list-empty;
|
||||
border-radius: @at-border-radius;
|
||||
border: @at-border-default-width solid @at-color-list-empty-border;
|
||||
background-color: @at-color-list-empty-background;
|
||||
color: @at-color-list-empty;
|
||||
text-transform: uppercase;
|
||||
text-align: center;
|
||||
padding: @at-padding-list-empty;
|
||||
}
|
||||
|
||||
.at-List-toolbar {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: @at-margin-bottom-list-toolbar;
|
||||
}
|
||||
|
||||
.at-List-search {
|
||||
flex: auto;
|
||||
}
|
||||
|
||||
.at-List-toolbarAction {
|
||||
margin-left: @at-margin-left-toolbar-action;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: @at-height-toolbar-action;
|
||||
border-radius: @at-border-radius;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.at-List-toolbarActionButton {
|
||||
border-radius: @at-border-radius;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.at-List-toolbarDropdownCarat {
|
||||
margin-left: @at-margin-left-toolbar-carat;
|
||||
display: inline-block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
vertical-align: middle;
|
||||
border-top: 4px dashed;
|
||||
border-right: 4px solid transparent;
|
||||
border-left: 4px solid transparent;
|
||||
}
|
||||
|
||||
.at-List-toolbarActionDropdownMenu {
|
||||
float: right;
|
||||
right: 0;
|
||||
left: auto;
|
||||
}
|
||||
|
||||
.at-List-container {
|
||||
border: @at-border-default-width solid @at-color-list-border;
|
||||
border-radius: @at-border-radius;
|
||||
}
|
||||
|
||||
.at-Row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: @at-padding-list-row;
|
||||
}
|
||||
|
||||
.at-Row--active {
|
||||
border-left: @at-border-style-list-active-indicator;
|
||||
border-top-left-radius: @at-border-radius;
|
||||
border-top-right-radius: @at-border-radius;
|
||||
}
|
||||
|
||||
.at-Row ~ .at-Row {
|
||||
border-top-left-radius: 0px;
|
||||
border-top-right-radius: 0px;
|
||||
border-top: @at-border-default-width solid @at-color-list-border;
|
||||
}
|
||||
|
||||
.at-Row-actions {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.at-Row-items {
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.at-RowItem {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: @at-height-list-row-item;
|
||||
}
|
||||
|
||||
.at-RowItem--isHeader {
|
||||
margin-bottom: @at-margin-bottom-list-header;
|
||||
line-height: @at-line-height-list-row-item-header;
|
||||
}
|
||||
|
||||
.at-RowItem--labels {
|
||||
line-height: @at-line-height-list-row-item-labels;
|
||||
}
|
||||
|
||||
.at-RowItem-header {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.at-RowItem-tagContainer {
|
||||
margin-left: @at-margin-left-list-row-item-tag-container;
|
||||
}
|
||||
|
||||
.at-RowItem-tag {
|
||||
text-transform: uppercase;
|
||||
font-weight: 100;
|
||||
background-color: @at-color-list-row-item-tag-background;
|
||||
border-radius: @at-border-radius;
|
||||
color: @at-color-list-row-item-tag;
|
||||
font-size: @at-font-size-list-row-item-tag;
|
||||
margin-left: @at-margin-left-list-row-item-tag;
|
||||
margin-top: @at-margin-top-list-row-item-tag;
|
||||
padding: @at-padding-list-row-item-tag;
|
||||
line-height: @at-line-height-list-row-item-tag;
|
||||
}
|
||||
|
||||
.at-RowItem-tag--primary {
|
||||
background: @at-color-list-row-item-tag-primary-background;
|
||||
color: @at-color-list-row-item-tag-primary;
|
||||
}
|
||||
|
||||
.at-RowItem-tag--header {
|
||||
height: @at-height-list-row-item-tag;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
.at-RowItem-tagIcon {
|
||||
margin-right: @at-margin-right-list-row-item-tag-icon;
|
||||
}
|
||||
|
||||
.at-RowItem-label {
|
||||
text-transform: uppercase;
|
||||
width: @at-width-list-row-item-label;
|
||||
color: @at-color-list-row-item-label;
|
||||
}
|
||||
|
||||
.at-RowAction {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: @at-margin-left-list-row-action;
|
||||
padding: @at-padding-list-row-action;
|
||||
background: @at-color-list-row-action-background;
|
||||
border-radius: @at-border-radius;
|
||||
height: @at-height-list-row-action;
|
||||
width: @at-width-list-row-action;
|
||||
|
||||
i {
|
||||
font-size: @at-font-size-list-row-action-icon;
|
||||
color: @at-color-list-row-action-icon;
|
||||
}
|
||||
}
|
||||
|
||||
.at-RowAction:hover {
|
||||
background-color: @at-color-list-row-action-hover;
|
||||
|
||||
i {
|
||||
color: @at-color-list-row-action-icon-hover;
|
||||
}
|
||||
}
|
||||
|
||||
.at-RowAction--danger:hover {
|
||||
background-color: @at-color-list-row-action-hover-danger;
|
||||
}
|
||||
|
||||
@media screen and (max-width: @at-breakpoint-compact-list) {
|
||||
.at-Row-actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.at-RowAction {
|
||||
margin: @at-margin-list-row-action-mobile;
|
||||
}
|
||||
}
|
||||
25
awx/ui/client/lib/components/list/list.directive.js
Normal file
25
awx/ui/client/lib/components/list/list.directive.js
Normal file
@ -0,0 +1,25 @@
|
||||
const templateUrl = require('~components/list/list.partial.html');
|
||||
|
||||
// TODO: figure out emptyListReason scope property
|
||||
|
||||
function AtListController (strings) {
|
||||
this.strings = strings;
|
||||
}
|
||||
|
||||
AtListController.$inject = ['ComponentsStrings'];
|
||||
|
||||
function atList () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
transclude: true,
|
||||
templateUrl,
|
||||
scope: {
|
||||
results: '=',
|
||||
},
|
||||
controller: AtListController,
|
||||
controllerAs: 'vm',
|
||||
};
|
||||
}
|
||||
|
||||
export default atList;
|
||||
7
awx/ui/client/lib/components/list/list.partial.html
Normal file
7
awx/ui/client/lib/components/list/list.partial.html
Normal file
@ -0,0 +1,7 @@
|
||||
<div class="at-List">
|
||||
<div class="at-List-container" ng-hide="results.length === 0" ng-transclude>
|
||||
</div>
|
||||
<div class="at-List--empty" ng-show="results.length === 0">
|
||||
{{ emptyListReason || vm.strings.get("list.DEFAULT_EMPTY_LIST") }}
|
||||
</div>
|
||||
</div>
|
||||
15
awx/ui/client/lib/components/list/row-action.directive.js
Normal file
15
awx/ui/client/lib/components/list/row-action.directive.js
Normal file
@ -0,0 +1,15 @@
|
||||
const templateUrl = require('~components/list/row-action.partial.html');
|
||||
|
||||
function atRowAction () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
transclude: true,
|
||||
templateUrl,
|
||||
scope: {
|
||||
icon: '@'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default atRowAction;
|
||||
@ -0,0 +1,4 @@
|
||||
<div class="at-RowAction"
|
||||
ng-class="{'at-RowAction--danger': (icon === 'fa-trash' || icon === 'fa-times')}">
|
||||
<i class="fa" ng-class="icon"></i>
|
||||
</div>
|
||||
24
awx/ui/client/lib/components/list/row-item.directive.js
Normal file
24
awx/ui/client/lib/components/list/row-item.directive.js
Normal file
@ -0,0 +1,24 @@
|
||||
const templateUrl = require('~components/list/row-item.partial.html');
|
||||
|
||||
function atRowItem () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
transclude: true,
|
||||
templateUrl,
|
||||
scope: {
|
||||
headerValue: '@',
|
||||
headerLink: '@',
|
||||
headerTag: '@',
|
||||
labelValue: '@',
|
||||
value: '@',
|
||||
valueLink: '@',
|
||||
smartStatus: '=?',
|
||||
tagValues: '=?',
|
||||
// TODO: add see more for tags if applicable
|
||||
tagsAreCreds: '@'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default atRowItem;
|
||||
38
awx/ui/client/lib/components/list/row-item.partial.html
Normal file
38
awx/ui/client/lib/components/list/row-item.partial.html
Normal file
@ -0,0 +1,38 @@
|
||||
<div class="at-RowItem" ng-class="{'at-RowItem--isHeader': headerValue}"
|
||||
ng-show="headerValue || value || (smartStatus && smartStatus.summary_fields.recent_jobs.length) || (tagValues && tagValues.length)">
|
||||
<div class="at-RowItem-header" ng-if="headerValue && headerLink">
|
||||
<a ng-href="{{ headerLink }}">{{ headerValue }}</a>
|
||||
</div>
|
||||
<div class="at-RowItem-header" ng-if="headerValue && !headerLink">
|
||||
{{ headerValue }}
|
||||
</div>
|
||||
<div class="at-RowItem-tag at-RowItem-tag--header" ng-if="headerTag">
|
||||
{{ headerTag }}
|
||||
</div>
|
||||
<div class="at-RowItem-label" ng-if="labelValue">
|
||||
{{ labelValue }}
|
||||
</div>
|
||||
<div class="at-RowItem-value" ng-if="value && valueLink">
|
||||
<a ng-href="{{ valueLink }}">{{ value }}</a>
|
||||
</div>
|
||||
<div class="at-RowItem-value" ng-if="value && !valueLink"
|
||||
ng-bind-html="value">
|
||||
</div>
|
||||
<aw-smart-status jobs="smartStatus.summary_fields.recent_jobs"
|
||||
template-type="smartStatus.type" ng-if="smartStatus">
|
||||
</aw-smart-status>
|
||||
<div class="at-RowItem-tagContainer" ng-if="tagValues && tagValues.length">
|
||||
<div ng-repeat="tag in tagValues" class="at-RowItem-tag at-RowItem-tag--primary">
|
||||
<span ng-switch="tag.kind" class="at-RowItem-tagIcon"
|
||||
ng-if="tagsAreCreds">
|
||||
<span class="fa fa-cloud" ng-switch-when="cloud"></span>
|
||||
<span class="fa fa-info" ng-switch-when="insights"></span>
|
||||
<span class="fa fa-sitemap" ng-switch-when="net"></span>
|
||||
<span class="fa fa-code-fork" ng-switch-when="scm"></span>
|
||||
<span class="fa fa-key" ng-switch-when="ssh"></span>
|
||||
<span class="fa fa-archive" ng-switch-when="vault"></span>
|
||||
</span>
|
||||
{{ tag.name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
15
awx/ui/client/lib/components/list/row.directive.js
Normal file
15
awx/ui/client/lib/components/list/row.directive.js
Normal file
@ -0,0 +1,15 @@
|
||||
const templateUrl = require('~components/list/row.partial.html');
|
||||
|
||||
function atRow () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
transclude: true,
|
||||
templateUrl,
|
||||
scope: {
|
||||
templateId: '@'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default atRow;
|
||||
2
awx/ui/client/lib/components/list/row.partial.html
Normal file
2
awx/ui/client/lib/components/list/row.partial.html
Normal file
@ -0,0 +1,2 @@
|
||||
<div class="at-Row" id="row-{{ templateId }}" ng-transclude>
|
||||
</div>
|
||||
@ -9,6 +9,10 @@
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.at-Panel-headingRow {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.at-Panel-dismiss {
|
||||
.at-mixin-ButtonIcon();
|
||||
color: @at-color-icon-dismiss;
|
||||
@ -24,3 +28,19 @@
|
||||
.at-mixin-Heading(@at-font-size-panel-heading);
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
.at-Panel-headingTitleBadge {
|
||||
font-size: 11px;
|
||||
font-weight: normal;
|
||||
padding: 2px 10px;
|
||||
line-height: 10px;
|
||||
background-color: #848992;
|
||||
border-radius: 5px;
|
||||
display: inline-block;
|
||||
min-width: 10px;
|
||||
color: #fff;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
const templateUrl = require('~components/panel/heading.partial.html');
|
||||
|
||||
function link (scope, el, attrs, panel) {
|
||||
scope.hideDismiss = Boolean(attrs.hideDismiss);
|
||||
panel.use(scope);
|
||||
}
|
||||
|
||||
|
||||
@ -1,12 +1,20 @@
|
||||
<div class="row">
|
||||
<div class="col-xs-10">
|
||||
<div class="row at-Panel-headingRow">
|
||||
<div class="col-xs-10"
|
||||
ng-if="!hideDismiss">
|
||||
<h3 class="at-Panel-headingTitle">
|
||||
<ng-transclude></ng-transclude>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="col-xs-2">
|
||||
<div class="col-xs-2"
|
||||
ng-if="!hideDismiss">
|
||||
<div class="at-Panel-dismiss">
|
||||
<i class="fa fa-times-circle fa-lg" ng-click="dismiss()"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12"
|
||||
ng-if="hideDismiss">
|
||||
<h3 class="at-Panel-headingTitle">
|
||||
<ng-transclude></ng-transclude>
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
}
|
||||
|
||||
.at-Truncate-copy {
|
||||
color: @at-gray-dark-2x;
|
||||
color: @at-gray-b7;
|
||||
cursor: pointer;
|
||||
margin-left: 10px;
|
||||
|
||||
|
||||
110
awx/ui/client/lib/models/UnifiedJobTemplate.js
Normal file
110
awx/ui/client/lib/models/UnifiedJobTemplate.js
Normal file
@ -0,0 +1,110 @@
|
||||
let BaseModel;
|
||||
let WorkflowJobTemplateNode;
|
||||
let $http;
|
||||
|
||||
function optionsLaunch (id) {
|
||||
const req = {
|
||||
method: 'OPTIONS',
|
||||
url: `${this.path}${id}/launch/`
|
||||
};
|
||||
|
||||
return $http(req);
|
||||
}
|
||||
|
||||
function getLaunch (id) {
|
||||
const req = {
|
||||
method: 'GET',
|
||||
url: `${this.path}${id}/launch/`
|
||||
};
|
||||
|
||||
return $http(req)
|
||||
.then(res => {
|
||||
this.model.launch.GET = res.data;
|
||||
|
||||
return res;
|
||||
});
|
||||
}
|
||||
|
||||
function postLaunch (params) {
|
||||
const req = {
|
||||
method: 'POST',
|
||||
url: `${this.path}${params.id}/launch/`
|
||||
};
|
||||
|
||||
if (params.launchData) {
|
||||
req.data = params.launchData;
|
||||
}
|
||||
|
||||
return $http(req);
|
||||
}
|
||||
|
||||
function getSurveyQuestions (id) {
|
||||
const req = {
|
||||
method: 'GET',
|
||||
url: `${this.path}${id}/survey_spec/`
|
||||
};
|
||||
|
||||
return $http(req);
|
||||
}
|
||||
|
||||
function canLaunchWithoutPrompt () {
|
||||
const launchData = this.model.launch.GET;
|
||||
|
||||
return (
|
||||
launchData.can_start_without_user_input &&
|
||||
!launchData.ask_inventory_on_launch &&
|
||||
!launchData.ask_credential_on_launch &&
|
||||
!launchData.ask_verbosity_on_launch &&
|
||||
!launchData.ask_job_type_on_launch &&
|
||||
!launchData.ask_limit_on_launch &&
|
||||
!launchData.ask_tags_on_launch &&
|
||||
!launchData.ask_skip_tags_on_launch &&
|
||||
!launchData.ask_variables_on_launch &&
|
||||
!launchData.ask_diff_mode_on_launch &&
|
||||
!launchData.survey_enabled
|
||||
);
|
||||
}
|
||||
|
||||
function setDependentResources (id) {
|
||||
this.dependentResources = [
|
||||
{
|
||||
model: new WorkflowJobTemplateNode(),
|
||||
params: {
|
||||
unified_job_template: id
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
function UnifiedJobTemplateModel (method, resource, graft) {
|
||||
BaseModel.call(this, 'unified_job_templates');
|
||||
|
||||
this.Constructor = UnifiedJobTemplateModel;
|
||||
this.setDependentResources = setDependentResources.bind(this);
|
||||
this.optionsLaunch = optionsLaunch.bind(this);
|
||||
this.getLaunch = getLaunch.bind(this);
|
||||
this.postLaunch = postLaunch.bind(this);
|
||||
this.getSurveyQuestions = getSurveyQuestions.bind(this);
|
||||
this.canLaunchWithoutPrompt = canLaunchWithoutPrompt.bind(this);
|
||||
|
||||
this.model.launch = {};
|
||||
|
||||
return this.create(method, resource, graft);
|
||||
}
|
||||
|
||||
function UnifiedJobTemplateModelLoader (_BaseModel_, WorkflowJobTemplateNodeModel, _$http_) {
|
||||
BaseModel = _BaseModel_;
|
||||
WorkflowJobTemplateNode = WorkflowJobTemplateNodeModel;
|
||||
$http = _$http_;
|
||||
|
||||
return UnifiedJobTemplateModel;
|
||||
}
|
||||
|
||||
UnifiedJobTemplateModelLoader.$inject = [
|
||||
'BaseModel',
|
||||
'WorkflowJobTemplateNodeModel',
|
||||
'$http',
|
||||
'$state'
|
||||
];
|
||||
|
||||
export default UnifiedJobTemplateModelLoader;
|
||||
@ -14,6 +14,7 @@ import Inventory from '~models/Inventory';
|
||||
import InventoryScript from '~models/InventoryScript';
|
||||
|
||||
import ModelsStrings from '~models/models.strings';
|
||||
import UnifiedJobTemplate from '~models/UnifiedJobTemplate';
|
||||
|
||||
const MODULE_NAME = 'at.lib.models';
|
||||
|
||||
@ -33,6 +34,7 @@ angular
|
||||
.service('InventorySourceModel', InventorySource)
|
||||
.service('InventoryModel', Inventory)
|
||||
.service('InventoryScriptModel', InventoryScript)
|
||||
.service('ModelsStrings', ModelsStrings);
|
||||
.service('ModelsStrings', ModelsStrings)
|
||||
.service('UnifiedJobTemplateModel', UnifiedJobTemplate);
|
||||
|
||||
export default MODULE_NAME;
|
||||
|
||||
@ -61,9 +61,16 @@ function BaseStringService (namespace) {
|
||||
this.SAVE = t.s('SAVE');
|
||||
this.OK = t.s('OK');
|
||||
this.deleteResource = {
|
||||
HEADER: t.s('Delete'),
|
||||
USED_BY: resourceType => t.s('The {{ resourceType }} is currently being used by other resources.', { resourceType }),
|
||||
CONFIRM: resourceType => t.s('Are you sure you want to delete this {{ resourceType }}?', { resourceType })
|
||||
};
|
||||
this.error = {
|
||||
HEADER: t.s('Error!'),
|
||||
CALL: ({ path, status }) => t.s('Call to {{ path }} failed. DELETE returned status: {{ status }}.', { path, status })
|
||||
};
|
||||
|
||||
this.ALERT = ({ header, body }) => t.s('{{ header }} {{ body }}', { header, body });
|
||||
|
||||
/**
|
||||
* This getter searches the extending class' namespace first for a match then falls back to
|
||||
|
||||
@ -45,6 +45,7 @@
|
||||
|
||||
.at-mixin-ButtonColor (@background, @color, @hover: '@{background}-hover') {
|
||||
background-color: @@background;
|
||||
border-color: @@background;
|
||||
|
||||
&, &:hover, &:focus {
|
||||
color: @@color;
|
||||
@ -52,6 +53,7 @@
|
||||
|
||||
&:hover, &:focus {
|
||||
background-color: @@hover;
|
||||
border-color: @@hover;
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
|
||||
@ -14,22 +14,24 @@
|
||||
* 1. Colors
|
||||
* 2. Typography
|
||||
* 3. Layout
|
||||
* 4. Breakpoints
|
||||
*
|
||||
*/
|
||||
|
||||
// 1. Colors --------------------------------------------------------------------------------------
|
||||
|
||||
@at-gray-light-3x: #fcfcfc;
|
||||
@at-gray-light-2-5x: #fafafa;
|
||||
@at-gray-light-2x: #f2f2f2;
|
||||
@at-gray-light: #ebebeb;
|
||||
@at-gray: #e1e1e1;
|
||||
@at-gray-dark: #d7d7d7;
|
||||
@at-gray-dark-2x: #b7b7b7;
|
||||
@at-gray-dark-3x: #A9A9A9;
|
||||
@at-gray-dark-4x: #848992;
|
||||
@at-gray-dark-5x: #707070;
|
||||
@at-gray-dark-6x: #161b1f;
|
||||
@at-gray-fc: #fcfcfc;
|
||||
@at-gray-fa: #fafafa;
|
||||
@at-gray-f2: #f2f2f2;
|
||||
@at-gray-f6: #f6f6f6;
|
||||
@at-gray-eb: #ebebeb;
|
||||
@at-gray-e1: #e1e1e1;
|
||||
@at-gray-d7: #d7d7d7;
|
||||
@at-gray-b7: #b7b7b7;
|
||||
@at-gray-a9: #a9a9a9;
|
||||
@at-gray-848992: #848992;
|
||||
@at-gray-70: #707070;
|
||||
@at-gray-161b1f: #161b1f;
|
||||
|
||||
@at-white: #ffffff;
|
||||
@at-white-hover: #f2f2f2;
|
||||
@ -38,7 +40,7 @@
|
||||
@at-blue-hover: #286090;
|
||||
|
||||
@at-green: #5cb85c;
|
||||
@at-green-hover: #449D44;
|
||||
@at-green-hover: #449d44;
|
||||
|
||||
@at-orange: #f0ad4e;
|
||||
@at-orange-hover: #ec971f;
|
||||
@ -66,6 +68,11 @@
|
||||
@at-space-2x: 10px;
|
||||
@at-space-3x: 15px;
|
||||
@at-space-4x: 20px;
|
||||
@at-space-5x: 25px;
|
||||
|
||||
// 4. Breakpoints ---------------------------------------------------------------------------------
|
||||
|
||||
@at-breakpoint-sm: 700px;
|
||||
|
||||
/**
|
||||
* All variables used in the UI. Use these variables directly during the development of components
|
||||
@ -83,6 +90,7 @@
|
||||
* 3. Layout
|
||||
* 4. Buttons
|
||||
* 5. Misc
|
||||
* 6. Breakpoints
|
||||
*
|
||||
*/
|
||||
|
||||
@ -106,64 +114,79 @@
|
||||
@at-color-success: @at-green;
|
||||
@at-color-success-hover: @at-green-hover;
|
||||
|
||||
@at-color-disabled: @at-gray-dark;
|
||||
@at-color-disabled: @at-gray-d7;
|
||||
|
||||
@at-color-body-background-dark: @at-gray-dark-5x;
|
||||
@at-color-body-background-dark: @at-gray-70;
|
||||
@at-color-body-text-dark: @at-white;
|
||||
@at-color-body-background: @at-gray-light-3x;
|
||||
@at-color-body-text: @at-gray-dark-5x;
|
||||
@at-color-body-background: @at-gray-fc;
|
||||
@at-color-body-text: @at-gray-70;
|
||||
|
||||
@at-color-button-border-default: @at-gray-dark-2x;
|
||||
@at-color-button-text-default: @at-gray-dark-5x;
|
||||
@at-color-button-border-default: @at-gray-b7;
|
||||
@at-color-button-text-default: @at-gray-70;
|
||||
|
||||
@at-color-tab-default-active: @at-gray-dark-4x;
|
||||
@at-color-tab-border-default-active: @at-gray-dark-4x;
|
||||
@at-color-tab-default-active: @at-gray-848992;
|
||||
@at-color-tab-border-default-active: @at-gray-848992;
|
||||
@at-color-tab-text-default-active: @at-white;
|
||||
|
||||
@at-color-tab-default-disabled: @at-white;
|
||||
@at-color-tab-border-default-disabled: @at-gray-dark-2x;
|
||||
@at-color-tab-text-default-disabled: @at-gray-dark-5x;
|
||||
@at-color-tab-border-default-disabled: @at-gray-b7;
|
||||
@at-color-tab-text-default-disabled: @at-gray-70;
|
||||
|
||||
@at-color-form-label: @at-gray-dark-5x;
|
||||
@at-color-form-label: @at-gray-70;
|
||||
|
||||
@at-color-input-background: @at-gray-light-3x;
|
||||
@at-color-input-border: @at-gray-dark-2x;
|
||||
@at-color-input-button: @at-gray-light-3x;
|
||||
@at-color-input-button-hover: @at-gray-light-2x;
|
||||
@at-color-input-disabled: @at-gray-light;
|
||||
@at-color-input-background: @at-gray-fc;
|
||||
@at-color-input-border: @at-gray-b7;
|
||||
@at-color-input-button: @at-gray-fc;
|
||||
@at-color-input-button-hover: @at-gray-f2;
|
||||
@at-color-input-disabled: @at-gray-eb;
|
||||
@at-color-input-readonly: @at-color-input-background;
|
||||
|
||||
@at-color-input-error: @at-color-error;
|
||||
@at-color-input-focus: @at-color-info;
|
||||
@at-color-input-hint: @at-gray-dark-4x;
|
||||
@at-color-input-icon: @at-gray-dark-2x;
|
||||
@at-color-input-placeholder: @at-gray-dark-4x;
|
||||
@at-color-input-text: @at-gray-dark-6x;
|
||||
@at-color-input-hint: @at-gray-848992;
|
||||
@at-color-input-icon: @at-gray-b7;
|
||||
@at-color-input-placeholder: @at-gray-848992;
|
||||
@at-color-input-text: @at-gray-161b1f;
|
||||
|
||||
@at-color-icon-dismiss: @at-gray-dark;
|
||||
@at-color-icon-popover: @at-gray-dark-4x;
|
||||
@at-color-icon-hover: @at-gray-dark-4x;
|
||||
@at-color-icon-dismiss: @at-gray-d7;
|
||||
@at-color-icon-popover: @at-gray-848992;
|
||||
@at-color-icon-hover: @at-gray-848992;
|
||||
|
||||
@at-color-panel-heading: @at-gray-dark-5x;
|
||||
@at-color-panel-border: @at-gray-dark-2x;
|
||||
@at-color-panel-heading: @at-gray-70;
|
||||
@at-color-panel-border: @at-gray-b7;
|
||||
|
||||
@at-color-search-key-active: @at-blue;
|
||||
|
||||
@at-color-table-header-background: @at-gray-light;
|
||||
@at-color-line-separator: @at-gray;
|
||||
@at-color-table-header-background: @at-gray-eb;
|
||||
@at-color-line-separator: @at-gray-e1;
|
||||
|
||||
@at-color-top-nav-background: @at-white;
|
||||
@at-color-top-nav-border-bottom: @at-gray-dark-2x;
|
||||
@at-color-top-nav-item-text: @at-gray-dark-5x;
|
||||
@at-color-top-nav-item-icon: @at-gray-dark-4x;
|
||||
@at-color-top-nav-border-bottom: @at-gray-b7;
|
||||
@at-color-top-nav-item-text: @at-gray-70;
|
||||
@at-color-top-nav-item-icon: @at-gray-848992;
|
||||
@at-color-top-nav-item-icon-socket-outline: @at-white;
|
||||
@at-color-top-nav-item-background-hover: @at-gray-light-2-5x;
|
||||
@at-color-side-nav-background: @at-gray-dark-4x;
|
||||
@at-color-top-nav-item-background-hover: @at-gray-fa;
|
||||
@at-color-side-nav-background: @at-gray-848992;
|
||||
@at-color-side-nav-content: @at-white;
|
||||
@at-color-side-nav-item-background-hover: @at-gray-dark-2x;
|
||||
@at-color-side-nav-item-background-hover: @at-gray-b7;
|
||||
@at-color-side-nav-item-border-hover: @at-white;
|
||||
@at-color-footer-background: @at-gray-light-3x;
|
||||
@at-color-footer: @at-gray-dark-5x;
|
||||
@at-color-footer-background: @at-gray-fc;
|
||||
@at-color-footer: @at-gray-70;
|
||||
|
||||
@at-color-list-empty-border: @at-gray-d7;
|
||||
@at-color-list-empty-background: @at-gray-f6;
|
||||
@at-color-list-empty: @at-gray-848992;
|
||||
@at-color-list-border: @at-gray-b7;
|
||||
@at-color-list-row-item-tag-background: @at-gray-eb;
|
||||
@at-color-list-row-item-tag: @at-gray-70;
|
||||
@at-color-list-row-item-label: @at-gray-848992;
|
||||
@at-color-list-row-action-background: @at-white;
|
||||
@at-color-list-row-action-icon: @at-gray-848992;
|
||||
@at-color-list-row-action-hover: @at-blue;
|
||||
@at-color-list-row-action-hover-danger: @at-red;
|
||||
@at-color-list-row-action-icon-hover: @at-white;
|
||||
@at-color-list-row-item-tag-primary-background: @at-blue;
|
||||
@at-color-list-row-item-tag-primary: @at-white;
|
||||
|
||||
// 2. Typography ----------------------------------------------------------------------------------
|
||||
|
||||
@ -181,6 +204,9 @@
|
||||
@at-font-size-navigation: @at-font-size-3x;
|
||||
@at-font-size-table-heading: @at-font-size-3x;
|
||||
@at-font-size-menu-icon: @at-font-size-5x;
|
||||
@at-font-size-list-row-item-tag: 10px;
|
||||
@at-font-size-list-row-action: 19px;
|
||||
@at-font-size-list-row-action-icon: 19px;
|
||||
|
||||
@at-font-weight-body: @at-font-weight;
|
||||
@at-font-weight-heading: @at-font-weight-2x;
|
||||
@ -199,6 +225,10 @@
|
||||
@at-padding-between-side-nav-icon-text: @at-space-3x;
|
||||
@at-padding-footer-right: @at-space-4x;
|
||||
@at-padding-footer-bottom: @at-space-4x;
|
||||
@at-padding-list-empty: @at-space-2x;
|
||||
@at-padding-list-row-item-tag: 0 @at-space-2x;
|
||||
@at-padding-list-row-action: 7px;
|
||||
@at-padding-list-row: 10px 20px;
|
||||
|
||||
@at-margin-input-message: @at-space;
|
||||
@at-margin-item-column: @at-space-3x;
|
||||
@ -215,6 +245,18 @@
|
||||
|
||||
@at-margin-top-search-key: @at-space-2x;
|
||||
|
||||
@at-margin-top-list: @at-space-5x;
|
||||
@at-margin-bottom-list-toolbar: @at-space-4x;
|
||||
@at-margin-left-toolbar-action: @at-space-4x;
|
||||
@at-margin-left-toolbar-carat: @at-space;
|
||||
@at-margin-bottom-list-header: @at-space;
|
||||
@at-margin-left-list-row-item-tag: @at-space-2x;
|
||||
@at-margin-top-list-row-item-tag: 2.25px;
|
||||
@at-margin-left-list-row-action: @at-space-4x;
|
||||
@at-margin-right-list-row-item-tag-icon: 8px;
|
||||
@at-margin-left-list-row-item-tag-container: -10px;
|
||||
@at-margin-list-row-action-mobile: 10px;
|
||||
|
||||
@at-height-divider: @at-margin-panel;
|
||||
@at-height-input: 30px;
|
||||
@at-height-textarea: 144px;
|
||||
@ -226,11 +268,21 @@
|
||||
@at-height-side-nav-item-icon: 20px;
|
||||
@at-height-side-nav-spacer: 20px;
|
||||
@at-height-top-side-nav-makeup: 55px;
|
||||
@at-height-list-empty: 200px;
|
||||
@at-height-toolbar-action: 30px;
|
||||
@at-height-list-row-item: 27px;
|
||||
@at-height-list-row-item-tag: 15px;
|
||||
@at-height-list-row-action: 30px;
|
||||
|
||||
@at-width-input-button-sm: 72px;
|
||||
@at-width-input-button-md: 84px;
|
||||
@at-width-collapsed-side-nav: 50px;
|
||||
@at-width-expanded-side-nav: 200px;
|
||||
@at-width-list-row-item-label: 120px;
|
||||
@at-width-list-row-action: 30px;
|
||||
|
||||
@at-line-height-list-row-item-header: @at-space-3x;
|
||||
@at-line-height-list-row-item-labels: 17px;
|
||||
|
||||
// 4. Transitions ---------------------------------------------------------------------------------
|
||||
|
||||
@ -249,3 +301,10 @@
|
||||
@at-z-index-side-nav: 1030;
|
||||
@at-z-index-footer: 1020;
|
||||
@at-border-default-width: 1px;
|
||||
@at-border-style-list-active-indicator: 5px solid @at-color-info;
|
||||
@at-line-height-list-row-item-tag: 22px;
|
||||
|
||||
// 6. Breakpoints ---------------------------------------------------------------------------------
|
||||
|
||||
@at-breakpoint-mobile-layout: @at-breakpoint-sm;
|
||||
@at-breakpoint-compact-list: @at-breakpoint-sm;
|
||||
|
||||
@ -16,6 +16,10 @@ export default {
|
||||
label: N_("JOB TEMPLATES")
|
||||
},
|
||||
views: {
|
||||
// TODO: this controller was removed and replaced
|
||||
// with the new features/templates controller
|
||||
// this view should be updated with the new
|
||||
// expanded list
|
||||
'related': {
|
||||
templateProvider: function(FormDefinition, GenerateForm) {
|
||||
let html = GenerateForm.buildCollection({
|
||||
|
||||
@ -44,7 +44,9 @@ export default ['$stateParams', '$scope', '$state', 'GetBasePath', 'QuerySet', '
|
||||
qs.initFieldset(path, $scope.djangoModel).then((data) => {
|
||||
$scope.models = data.models;
|
||||
$scope.options = data.options.data;
|
||||
$scope.$emit(`${$scope.list.iterator}_options`, data.options);
|
||||
if ($scope.list) {
|
||||
$scope.$emit(`${$scope.list.iterator}_options`, data.options);
|
||||
}
|
||||
});
|
||||
$scope.searchPlaceholder = $scope.disableSearch ? i18n._('Cannot search running job') : i18n._('Search');
|
||||
|
||||
@ -76,6 +78,7 @@ export default ['$stateParams', '$scope', '$state', 'GetBasePath', 'QuerySet', '
|
||||
qs.search(path, queryset).then((res) => {
|
||||
$scope.dataset = res.data;
|
||||
$scope.collection = res.data.results;
|
||||
$scope.$emit('updateDataset', res.data);
|
||||
});
|
||||
|
||||
$scope.searchTerm = null;
|
||||
|
||||
@ -10,44 +10,48 @@
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
|
||||
.SmartStatus--success{
|
||||
color: @default-succ;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
padding: 0px;
|
||||
.SmartStatus-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
|
||||
}
|
||||
|
||||
.SmartStatus--failed{
|
||||
color: @default-err;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
padding: 0px;
|
||||
.SmartStatus-iconDirectionPlaceholder {
|
||||
width: 16px;
|
||||
height: 8px;
|
||||
border: 1px solid #d7d7d7;
|
||||
background: #f2f2f2;
|
||||
}
|
||||
|
||||
.SmartStatus--failed:before {
|
||||
content: "\f06a";
|
||||
.SmartStatus-iconDirectionPlaceholder--bottom {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.SmartStatus--running{
|
||||
color: @default-icon;
|
||||
margin-top: 10px;
|
||||
padding: 0px;
|
||||
.pulsate();
|
||||
.SmartStatus-iconDirectionPlaceholder--top {
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
.SmartStatus-vertCenter{
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
padding: 0px;
|
||||
.SmartStatus-iconIndicator {
|
||||
width: 16px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.SmartStatus-tooltip{
|
||||
text-align: left;
|
||||
max-width: 250px;
|
||||
padding: 10px;
|
||||
line-height: 22px;
|
||||
.SmartStatus-iconIndicator--success {
|
||||
background: #5cb85c;
|
||||
}
|
||||
|
||||
.SmartStatus-iconIndicator--failed {
|
||||
background: #d9534f;
|
||||
}
|
||||
|
||||
.SmartStatus-iconPlaceholder {
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
border: 1px solid #d7d7d7;
|
||||
background: #f2f2f2;
|
||||
}
|
||||
|
||||
|
||||
.SmartStatus-tooltip--successful,
|
||||
.SmartStatus-tooltip--success{
|
||||
color: @default-succ;
|
||||
|
||||
@ -16,7 +16,7 @@ export default ['$scope', '$filter',
|
||||
var firstJobStatus;
|
||||
var recentJobs = $scope.jobs;
|
||||
var detailsBaseUrl;
|
||||
|
||||
|
||||
if(!recentJobs){
|
||||
return;
|
||||
}
|
||||
@ -74,6 +74,7 @@ export default ['$scope', '$filter',
|
||||
$scope.singleJobStatus = singleJobStatus;
|
||||
|
||||
$scope.sparkArray = sparkData;
|
||||
$scope.placeholders = new Array(10 - sparkData.length);
|
||||
}
|
||||
$scope.$watchCollection('jobs', function(){
|
||||
init();
|
||||
|
||||
@ -9,13 +9,21 @@
|
||||
data-container="body"
|
||||
tooltipInnerClass="SmartStatus-tooltip"
|
||||
title="">
|
||||
<i class="fa
|
||||
DashboardList-status"
|
||||
ng-class="{'SmartStatus--success icon-job-successful': job.value === 1,
|
||||
'SmartStatus--failed icon-job-failed': job.value === -1,
|
||||
'SmartStatus--running icon-job-successful': job.value === 0,
|
||||
'SmartStatus-vertCenter': singleJobStatus}">
|
||||
</i>
|
||||
<div class="SmartStatus-icon">
|
||||
<div ng-show="job.value === -1"
|
||||
class="SmartStatus-iconDirectionPlaceholder SmartStatus-iconDirectionPlaceholder--bottom">
|
||||
</div>
|
||||
<div class="SmartStatus-iconIndicator"
|
||||
ng-class="{'SmartStatus-iconIndicator--success': job.value === 1,
|
||||
'SmartStatus-iconIndicator--failed': job.value === -1}">
|
||||
</div>
|
||||
<div ng-show="job.value === 1"
|
||||
class="SmartStatus-iconDirectionPlaceholder SmartStatus-iconDirectionPlaceholder--top">
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div ng-repeat="n in placeholders track by $index" class='SmartStatus-iconContainer'>
|
||||
<div class="SmartStatus-iconPlaceholder"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -16,6 +16,7 @@ export default
|
||||
templateUrl: templateUrl('templates/labels/labelsList'),
|
||||
link: function(scope, element, attrs) {
|
||||
scope.showDelete = attrs.showDelete === 'true';
|
||||
scope.isRowItem = attrs.isRowItem === 'true';
|
||||
scope.seeMoreInactive = true;
|
||||
|
||||
var getNext = function(data, arr, resolve) {
|
||||
@ -91,18 +92,23 @@ export default
|
||||
});
|
||||
};
|
||||
|
||||
scope.$watchCollection(scope.$parent.list.iterator, function() {
|
||||
// To keep the array of labels fresh, we need to set up a watcher - otherwise, the
|
||||
// array will get set initially and then never be updated as labels are removed
|
||||
if (scope[scope.$parent.list.iterator].summary_fields.labels){
|
||||
scope.labels = scope[scope.$parent.list.iterator].summary_fields.labels.results.slice(0, 5);
|
||||
scope.count = scope[scope.$parent.list.iterator].summary_fields.labels.count;
|
||||
}
|
||||
else{
|
||||
scope.labels = null;
|
||||
scope.count = null;
|
||||
}
|
||||
});
|
||||
if (scope.$parent.$parent.template) {
|
||||
scope.labels = scope.$parent.$parent.template.summary_fields.labels.results.slice(0, 5);
|
||||
scope.count = scope.$parent.$parent.template.summary_fields.labels.count;
|
||||
} else {
|
||||
scope.$watchCollection(scope.$parent.list.iterator, function() {
|
||||
// To keep the array of labels fresh, we need to set up a watcher - otherwise, the
|
||||
// array will get set initially and then never be updated as labels are removed
|
||||
if (scope[scope.$parent.list.iterator].summary_fields.labels){
|
||||
scope.labels = scope[scope.$parent.list.iterator].summary_fields.labels.results.slice(0, 5);
|
||||
scope.count = scope[scope.$parent.list.iterator].summary_fields.labels.count;
|
||||
}
|
||||
else{
|
||||
scope.labels = null;
|
||||
scope.count = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<div class="LabelList-tagContainer" ng-repeat="label in labels">
|
||||
<div class="LabelList-tagContainer" ng-repeat="label in labels" ng-if="!isRowItem">
|
||||
<div class="LabelList-deleteContainer"
|
||||
ng-click="deleteLabel(template, label)"
|
||||
ng-show="showDelete && template.summary_fields.user_capabilities.edit">
|
||||
@ -9,6 +9,25 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="LabelList-seeMoreLess" ng-show="count > 5 && seeMoreInactive"
|
||||
ng-click="seeMore()">View More</div>
|
||||
ng-click="seeMore()" ng-if="!isRowItem">View More</div>
|
||||
<div class="LabelList-seeMoreLess" ng-show="count > 5 && !seeMoreInactive"
|
||||
ng-click="seeLess()">View Less</div>
|
||||
ng-click="seeLess()" ng-if="!isRowItem">View Less</div>
|
||||
<div class="at-RowItem at-RowItem--labels" ng-show="count > 0" ng-if="isRowItem">
|
||||
<div class="at-RowItem-label">
|
||||
Labels
|
||||
</div>
|
||||
<div class="LabelList-tagContainer" ng-repeat="label in labels">
|
||||
<div class="LabelList-deleteContainer"
|
||||
ng-click="deleteLabel(template, label)"
|
||||
ng-show="showDelete && template.summary_fields.user_capabilities.edit">
|
||||
<i class="fa fa-times LabelList-tagDelete"></i>
|
||||
</div>
|
||||
<div class="LabelList-tag" ng-class="{'LabelList-tag--deletable' : (showDelete && template.summary_fields.user_capabilities.edit)}">
|
||||
<span class="LabelList-name">{{ label.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="LabelList-seeMoreLess" ng-show="count > 5 && seeMoreInactive"
|
||||
ng-click="seeMore()">View More</div>
|
||||
<div class="LabelList-seeMoreLess" ng-show="count > 5 && !seeMoreInactive"
|
||||
ng-click="seeLess()">View Less</div>
|
||||
</div>
|
||||
|
||||
@ -1,11 +0,0 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
import controller from './templates-list.controller';
|
||||
|
||||
export default
|
||||
angular.module('templatesList', [])
|
||||
.controller('TemplatesListController', controller);
|
||||
@ -1,360 +0,0 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
export default ['$scope', '$rootScope',
|
||||
'Alert','TemplateList', 'Prompt', 'ProcessErrors',
|
||||
'GetBasePath', 'InitiatePlaybookRun', 'Wait', '$state', '$filter',
|
||||
'Dataset', 'rbacUiControlService', 'TemplatesService','QuerySet',
|
||||
'TemplateCopyService', 'i18n', 'JobTemplateModel', 'TemplatesStrings',
|
||||
function(
|
||||
$scope, $rootScope, Alert,
|
||||
TemplateList, Prompt, ProcessErrors, GetBasePath,
|
||||
InitiatePlaybookRun, Wait, $state, $filter, Dataset, rbacUiControlService, TemplatesService,
|
||||
qs, TemplateCopyService, i18n, JobTemplate, TemplatesStrings
|
||||
) {
|
||||
|
||||
let jobTemplate = new JobTemplate();
|
||||
|
||||
var list = TemplateList;
|
||||
|
||||
init();
|
||||
|
||||
function init() {
|
||||
$scope.canAdd = false;
|
||||
|
||||
rbacUiControlService.canAdd("job_templates")
|
||||
.then(function(params) {
|
||||
$scope.canAddJobTemplate = params.canAdd;
|
||||
});
|
||||
|
||||
rbacUiControlService.canAdd("workflow_job_templates")
|
||||
.then(function(params) {
|
||||
$scope.canAddWorkflowJobTemplate = params.canAdd;
|
||||
});
|
||||
// search init
|
||||
$scope.list = list;
|
||||
$scope[`${list.iterator}_dataset`] = Dataset.data;
|
||||
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
|
||||
$scope.options = {};
|
||||
|
||||
$rootScope.flashMessage = null;
|
||||
}
|
||||
|
||||
$scope.$on(`${list.iterator}_options`, function(event, data){
|
||||
$scope.options = data.data.actions.GET;
|
||||
optionsRequestDataProcessing();
|
||||
});
|
||||
|
||||
$scope.$watchCollection('templates', function() {
|
||||
optionsRequestDataProcessing();
|
||||
}
|
||||
);
|
||||
// iterate over the list and add fields like type label, after the
|
||||
// OPTIONS request returns, or the list is sorted/paginated/searched
|
||||
function optionsRequestDataProcessing(){
|
||||
$scope[list.name].forEach(function(item, item_idx) {
|
||||
var itm = $scope[list.name][item_idx];
|
||||
|
||||
// Set the item type label
|
||||
if (list.fields.type && $scope.options.hasOwnProperty('type')) {
|
||||
$scope.options.type.choices.forEach(function(choice) {
|
||||
if (choice[0] === item.type) {
|
||||
itm.type_label = choice[1];
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
$scope.$on(`ws-jobs`, function () {
|
||||
let path = GetBasePath(list.basePath) || GetBasePath(list.name);
|
||||
qs.search(path, $state.params[`${list.iterator}_search`])
|
||||
.then(function(searchResponse) {
|
||||
$scope[`${list.iterator}_dataset`] = searchResponse.data;
|
||||
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
|
||||
});
|
||||
});
|
||||
|
||||
$scope.editJobTemplate = function(template) {
|
||||
if(template) {
|
||||
if(template.type && (template.type === 'Job Template' || template.type === 'job_template')) {
|
||||
$state.transitionTo('templates.editJobTemplate', {job_template_id: template.id});
|
||||
}
|
||||
else if(template.type && (template.type === 'Workflow Job Template' || template.type === 'workflow_job_template')) {
|
||||
$state.transitionTo('templates.editWorkflowJobTemplate', {workflow_job_template_id: template.id});
|
||||
}
|
||||
else {
|
||||
// Something went wrong - Let the user know that we're unable to launch because we don't know
|
||||
// what type of job template this is
|
||||
Alert('Error: Unable to determine template type', 'We were unable to determine this template\'s type while routing to edit.');
|
||||
}
|
||||
}
|
||||
else {
|
||||
Alert('Error: Unable to edit template', 'Template parameter is missing');
|
||||
}
|
||||
};
|
||||
|
||||
$scope.deleteJobTemplate = function(template) {
|
||||
if(template) {
|
||||
var action = function() {
|
||||
function handleSuccessfulDelete(isWorkflow) {
|
||||
let stateParamId = isWorkflow ? $state.params.workflow_job_template_id : $state.params.job_template_id;
|
||||
|
||||
let reloadListStateParams = null;
|
||||
|
||||
if($scope.templates.length === 1 && $state.params.template_search && !_.isEmpty($state.params.template_search.page) && $state.params.template_search.page !== '1') {
|
||||
reloadListStateParams = _.cloneDeep($state.params);
|
||||
reloadListStateParams.template_search.page = (parseInt(reloadListStateParams.template_search.page)-1).toString();
|
||||
}
|
||||
|
||||
if (parseInt(stateParamId) === template.id) {
|
||||
// Move the user back to the templates list
|
||||
$state.go("templates", reloadListStateParams, {reload: true});
|
||||
} else {
|
||||
$state.go(".", reloadListStateParams, {reload: true});
|
||||
}
|
||||
Wait('stop');
|
||||
}
|
||||
|
||||
$('#prompt-modal').modal('hide');
|
||||
Wait('start');
|
||||
if(template.type && (template.type === 'Workflow Job Template' || template.type === 'workflow_job_template')) {
|
||||
TemplatesService.deleteWorkflowJobTemplate(template.id)
|
||||
.then(function () {
|
||||
handleSuccessfulDelete(true);
|
||||
})
|
||||
.catch(function (response) {
|
||||
Wait('stop');
|
||||
ProcessErrors($scope, response.data, response.status, null, { hdr: 'Error!',
|
||||
msg: 'Call to delete workflow job template failed. DELETE returned status: ' + response.status + '.'});
|
||||
});
|
||||
}
|
||||
else if(template.type && (template.type === 'Job Template' || template.type === 'job_template')) {
|
||||
TemplatesService.deleteJobTemplate(template.id)
|
||||
.then(function () {
|
||||
handleSuccessfulDelete();
|
||||
})
|
||||
.catch(function (response) {
|
||||
Wait('stop');
|
||||
ProcessErrors($scope, response.data, response.status, null, { hdr: 'Error!',
|
||||
msg: 'Call to delete job template failed. DELETE returned status: ' + response.status + '.'});
|
||||
});
|
||||
}
|
||||
else {
|
||||
Wait('stop');
|
||||
Alert('Error: Unable to determine template type', 'We were unable to determine this template\'s type while deleting.');
|
||||
}
|
||||
};
|
||||
|
||||
if(template.type && (template.type === 'Workflow Job Template' || template.type === 'workflow_job_template')) {
|
||||
Prompt({
|
||||
hdr: i18n._('Delete'),
|
||||
resourceName: $filter('sanitize')(template.name),
|
||||
body: TemplatesStrings.get('deleteResource.CONFIRM', 'workflow job template'),
|
||||
action: action,
|
||||
actionText: 'DELETE'
|
||||
});
|
||||
}
|
||||
else if(template.type && (template.type === 'Job Template' || template.type === 'job_template')) {
|
||||
|
||||
jobTemplate.getDependentResourceCounts(template.id)
|
||||
.then((counts) => {
|
||||
const invalidateRelatedLines = [];
|
||||
let deleteModalBody = `<div class="Prompt-bodyQuery">${TemplatesStrings.get('deleteResource.CONFIRM', 'job template')}</div>`;
|
||||
|
||||
counts.forEach(countObj => {
|
||||
if(countObj.count && countObj.count > 0) {
|
||||
invalidateRelatedLines.push(`<div><span class="Prompt-warningResourceTitle">${countObj.label}</span><span class="badge List-titleBadge">${countObj.count}</span></div>`);
|
||||
}
|
||||
});
|
||||
|
||||
if (invalidateRelatedLines && invalidateRelatedLines.length > 0) {
|
||||
deleteModalBody = `<div class="Prompt-bodyQuery">${TemplatesStrings.get('deleteResource.USED_BY', 'job template')} ${TemplatesStrings.get('deleteResource.CONFIRM', 'job template')}</div>`;
|
||||
invalidateRelatedLines.forEach(invalidateRelatedLine => {
|
||||
deleteModalBody += invalidateRelatedLine;
|
||||
});
|
||||
}
|
||||
|
||||
Prompt({
|
||||
hdr: i18n._('Delete'),
|
||||
resourceName: $filter('sanitize')(template.name),
|
||||
body: deleteModalBody,
|
||||
action: action,
|
||||
actionText: 'DELETE'
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
Alert('Error: Unable to delete template', 'Template parameter is missing');
|
||||
}
|
||||
};
|
||||
|
||||
$scope.submitJob = function(template) {
|
||||
if(template) {
|
||||
if(template.type && (template.type === 'Job Template' || template.type === 'job_template')) {
|
||||
InitiatePlaybookRun({ scope: $scope, id: template.id, job_type: 'job_template' });
|
||||
}
|
||||
else if(template.type && (template.type === 'Workflow Job Template' || template.type === 'workflow_job_template')) {
|
||||
InitiatePlaybookRun({ scope: $scope, id: template.id, job_type: 'workflow_job_template' });
|
||||
}
|
||||
else {
|
||||
// Something went wrong - Let the user know that we're unable to launch because we don't know
|
||||
// what type of job template this is
|
||||
Alert('Error: Unable to determine template type', 'We were unable to determine this template\'s type while launching.');
|
||||
}
|
||||
}
|
||||
else {
|
||||
Alert('Error: Unable to launch template', 'Template parameter is missing');
|
||||
}
|
||||
};
|
||||
|
||||
$scope.scheduleJob = function(template) {
|
||||
if(template) {
|
||||
if(template.type && (template.type === 'Job Template' || template.type === 'job_template')) {
|
||||
$state.go('jobTemplateSchedules', {id: template.id});
|
||||
}
|
||||
else if(template.type && (template.type === 'Workflow Job Template' || template.type === 'workflow_job_template')) {
|
||||
$state.go('workflowJobTemplateSchedules', {id: template.id});
|
||||
}
|
||||
else {
|
||||
// Something went wrong - Let the user know that we're unable to redirect to schedule because we don't know
|
||||
// what type of job template this is
|
||||
Alert('Error: Unable to determine template type', 'We were unable to determine this template\'s type while routing to schedule.');
|
||||
}
|
||||
}
|
||||
else {
|
||||
Alert('Error: Unable to schedule job', 'Template parameter is missing');
|
||||
}
|
||||
};
|
||||
|
||||
$scope.copyTemplate = function(template) {
|
||||
if(template) {
|
||||
if(template.type && template.type === 'job_template') {
|
||||
Wait('start');
|
||||
TemplateCopyService.get(template.id)
|
||||
.then(function(response){
|
||||
TemplateCopyService.set(response.data.results)
|
||||
.then(function(results){
|
||||
Wait('stop');
|
||||
if(results.type && results.type === 'job_template') {
|
||||
$state.go('templates.editJobTemplate', {job_template_id: results.id}, {reload: true});
|
||||
}
|
||||
})
|
||||
.catch(({data, status}) => {
|
||||
ProcessErrors($scope, data, status, null, {
|
||||
hdr: 'Error!',
|
||||
msg: 'Call failed. Return status: ' + status
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(({data, status}) => {
|
||||
ProcessErrors($rootScope, data, status, null, {hdr: 'Error!',
|
||||
msg: 'Call failed. Return status: '+ status});
|
||||
});
|
||||
}
|
||||
else if(template.type && template.type === 'workflow_job_template') {
|
||||
TemplateCopyService.getWorkflowCopy(template.id)
|
||||
.then(function(result) {
|
||||
|
||||
if(result.data.can_copy) {
|
||||
if(result.data.can_copy_without_user_input) {
|
||||
// Go ahead and copy the workflow - the user has full priveleges on all the resources
|
||||
TemplateCopyService.copyWorkflow(template.id)
|
||||
.then(function(result) {
|
||||
$state.go('templates.editWorkflowJobTemplate', {workflow_job_template_id: result.data.id}, {reload: true});
|
||||
})
|
||||
.catch(function (response) {
|
||||
Wait('stop');
|
||||
ProcessErrors($scope, response.data, response.status, null, { hdr: 'Error!',
|
||||
msg: 'Call to copy workflow job template failed. Return status: ' + response.status + '.'});
|
||||
});
|
||||
}
|
||||
else {
|
||||
|
||||
let bodyHtml = `
|
||||
<div class="Prompt-bodyQuery">
|
||||
You do not have access to all resources used by this workflow. Resources that you don\'t have access to will not be copied and will result in an incomplete workflow.
|
||||
</div>
|
||||
<div class="Prompt-bodyTarget">`;
|
||||
|
||||
// List the unified job templates user can not access
|
||||
if (result.data.templates_unable_to_copy.length > 0) {
|
||||
bodyHtml += '<div>Unified Job Templates that can not be copied<ul>';
|
||||
_.forOwn(result.data.templates_unable_to_copy, function(ujt) {
|
||||
if(ujt) {
|
||||
bodyHtml += '<li>' + ujt + '</li>';
|
||||
}
|
||||
});
|
||||
bodyHtml += '</ul></div>';
|
||||
}
|
||||
// List the prompted inventories user can not access
|
||||
if (result.data.inventories_unable_to_copy.length > 0) {
|
||||
bodyHtml += '<div>Node prompted inventories that can not be copied<ul>';
|
||||
_.forOwn(result.data.inventories_unable_to_copy, function(inv) {
|
||||
if(inv) {
|
||||
bodyHtml += '<li>' + inv + '</li>';
|
||||
}
|
||||
});
|
||||
bodyHtml += '</ul></div>';
|
||||
}
|
||||
// List the prompted credentials user can not access
|
||||
if (result.data.credentials_unable_to_copy.length > 0) {
|
||||
bodyHtml += '<div>Node prompted credentials that can not be copied<ul>';
|
||||
_.forOwn(result.data.credentials_unable_to_copy, function(cred) {
|
||||
if(cred) {
|
||||
bodyHtml += '<li>' + cred + '</li>';
|
||||
}
|
||||
});
|
||||
bodyHtml += '</ul></div>';
|
||||
}
|
||||
|
||||
bodyHtml += '</div>';
|
||||
|
||||
|
||||
Prompt({
|
||||
hdr: 'Copy Workflow',
|
||||
body: bodyHtml,
|
||||
action: function() {
|
||||
$('#prompt-modal').modal('hide');
|
||||
Wait('start');
|
||||
TemplateCopyService.copyWorkflow(template.id)
|
||||
.then(function(result) {
|
||||
Wait('stop');
|
||||
$state.go('templates.editWorkflowJobTemplate', {workflow_job_template_id: result.data.id}, {reload: true});
|
||||
}, function (data) {
|
||||
Wait('stop');
|
||||
ProcessErrors($scope, data, status, null, { hdr: 'Error!',
|
||||
msg: 'Call to copy template failed. POST returned status: ' + status });
|
||||
});
|
||||
},
|
||||
actionText: 'COPY',
|
||||
class: 'Modal-primaryButton'
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
Alert('Error: Unable to copy workflow job template', 'You do not have permission to perform this action.');
|
||||
}
|
||||
}, function (data) {
|
||||
Wait('stop');
|
||||
ProcessErrors($scope, data, status, null, { hdr: 'Error!',
|
||||
msg: 'Call to copy template failed. GET returned status: ' + status });
|
||||
});
|
||||
}
|
||||
else {
|
||||
// Something went wrong - Let the user know that we're unable to copy because we don't know
|
||||
// what type of job template this is
|
||||
Alert('Error: Unable to determine template type', 'We were unable to determine this template\'s type while copying.');
|
||||
}
|
||||
}
|
||||
else {
|
||||
Alert('Error: Unable to copy job', 'Template parameter is missing');
|
||||
}
|
||||
};
|
||||
}
|
||||
];
|
||||
@ -6,7 +6,6 @@
|
||||
|
||||
import templatesService from './templates.service';
|
||||
import surveyMaker from './survey-maker/main';
|
||||
import templatesList from './list/main';
|
||||
import jobTemplates from './job_templates/main';
|
||||
import workflowAdd from './workflows/add-workflow/main';
|
||||
import workflowEdit from './workflows/edit-workflow/main';
|
||||
@ -14,7 +13,6 @@ import labels from './labels/main';
|
||||
import workflowChart from './workflows/workflow-chart/main';
|
||||
import workflowMaker from './workflows/workflow-maker/main';
|
||||
import workflowControls from './workflows/workflow-controls/main';
|
||||
import templatesListRoute from './list/templates-list.route';
|
||||
import workflowService from './workflows/workflow.service';
|
||||
import templateCopyService from './copy-template/template-copy.service';
|
||||
import WorkflowForm from './workflows.form';
|
||||
@ -22,9 +20,10 @@ import CompletedJobsList from './completed-jobs.list';
|
||||
import InventorySourcesList from './inventory-sources.list';
|
||||
import TemplateList from './templates.list';
|
||||
import TemplatesStrings from './templates.strings';
|
||||
import listRoute from '~features/templates/list.route.js';
|
||||
|
||||
export default
|
||||
angular.module('templates', [surveyMaker.name, templatesList.name, jobTemplates.name, labels.name, workflowAdd.name, workflowEdit.name,
|
||||
angular.module('templates', [surveyMaker.name, jobTemplates.name, labels.name, workflowAdd.name, workflowEdit.name,
|
||||
workflowChart.name, workflowMaker.name, workflowControls.name
|
||||
])
|
||||
.service('TemplatesService', templatesService)
|
||||
@ -32,6 +31,7 @@ angular.module('templates', [surveyMaker.name, templatesList.name, jobTemplates.
|
||||
.service('TemplateCopyService', templateCopyService)
|
||||
.factory('WorkflowForm', WorkflowForm)
|
||||
.factory('CompletedJobsList', CompletedJobsList)
|
||||
// TODO: currently being kept arround for rbac selection, templates within projects and orgs, etc.
|
||||
.factory('TemplateList', TemplateList)
|
||||
.value('InventorySourcesList', InventorySourcesList)
|
||||
.service('TemplatesStrings', TemplatesStrings)
|
||||
@ -897,7 +897,7 @@ angular.module('templates', [surveyMaker.name, templatesList.name, jobTemplates.
|
||||
states: _.reduce(generated, (result, definition) => {
|
||||
return result.concat(definition.states);
|
||||
}, [
|
||||
stateExtender.buildDefinition(templatesListRoute),
|
||||
stateExtender.buildDefinition(listRoute),
|
||||
stateExtender.buildDefinition(workflowMaker),
|
||||
stateExtender.buildDefinition(inventoryLookup),
|
||||
stateExtender.buildDefinition(credentialLookup)
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
@breakpoint-md: 1200px;
|
||||
@breakpoint-sm: 623px;
|
||||
@breakpoint-sm: 700px;
|
||||
|
||||
.WorkflowResults {
|
||||
.OnePlusTwo-container(100%, @breakpoint-md);
|
||||
|
||||
@ -247,7 +247,7 @@ module.exports = {
|
||||
templates.waitForElementPresent('i[class$="launch"]');
|
||||
templates.waitForElementNotPresent('i[class$="launch"]:nth-of-type(2)');
|
||||
|
||||
templates.expect.element('.List-titleBadge').text.equal('1');
|
||||
templates.expect.element('.at-Panel-headingTitleBadge').text.equal('1');
|
||||
|
||||
templates.waitForElementVisible('i[class$="launch"]');
|
||||
templates.click('i[class$="launch"]');
|
||||
|
||||
@ -167,35 +167,36 @@ module.exports = {
|
||||
client.expect.element('#job_template_form').visible;
|
||||
},
|
||||
'check template list for unsanitized content': client => {
|
||||
const itemRow = `#templates_table tr[id="${data.jobTemplate.id}"]`;
|
||||
const itemName = `${itemRow} td[class*="name-"] a`;
|
||||
const itemRow = `#row-${data.jobTemplate.id}`;
|
||||
const itemName = `${itemRow} .at-RowItem-header`;
|
||||
|
||||
client.expect.element('div[class^="Panel"] smart-search').visible;
|
||||
client.expect.element('div[class^="Panel"] smart-search input').enabled;
|
||||
client.expect.element('.at-Panel smart-search').visible;
|
||||
client.expect.element('.at-Panel smart-search input').enabled;
|
||||
|
||||
client.sendKeys('div[class^="Panel"] smart-search input', `id:${data.jobTemplate.id}`);
|
||||
client.sendKeys('div[class^="Panel"] smart-search input', client.Keys.ENTER);
|
||||
client.sendKeys('.at-Panel smart-search input', `id:${data.jobTemplate.id}`);
|
||||
client.sendKeys('.at-Panel smart-search input', client.Keys.ENTER);
|
||||
|
||||
client.expect.element('div.spinny').not.visible;
|
||||
|
||||
client.expect.element('.List-titleBadge').text.equal('1');
|
||||
client.expect.element('.at-Panel-headingTitleBadge').text.equal('1');
|
||||
client.expect.element(itemName).visible;
|
||||
|
||||
client.moveToElement(itemName, 0, 0, () => {
|
||||
client.expect.element(itemName).attribute('aria-describedby');
|
||||
|
||||
client.getAttribute(itemName, 'aria-describedby', ({ value }) => {
|
||||
const tooltip = `#${value}`;
|
||||
|
||||
client.expect.element(tooltip).present;
|
||||
client.expect.element(tooltip).visible;
|
||||
|
||||
client.expect.element('#xss').not.present;
|
||||
client.expect.element('[class=xss]').not.present;
|
||||
client.expect.element(tooltip).attribute('innerHTML')
|
||||
.contains('<div id="xss" class="xss">test</div>');
|
||||
});
|
||||
});
|
||||
// TODO: uncomment when tooltips are added
|
||||
// client.moveToElement(itemName, 0, 0, () => {
|
||||
// client.expect.element(itemName).attribute('aria-describedby');
|
||||
//
|
||||
// client.getAttribute(itemName, 'aria-describedby', ({ value }) => {
|
||||
// const tooltip = `#${value}`;
|
||||
//
|
||||
// client.expect.element(tooltip).present;
|
||||
// client.expect.element(tooltip).visible;
|
||||
//
|
||||
// client.expect.element('#xss').not.present;
|
||||
// client.expect.element('[class=xss]').not.present;
|
||||
// client.expect.element(tooltip).attribute('innerHTML')
|
||||
// .contains('<div id="xss" class="xss">test</div>');
|
||||
// });
|
||||
// });
|
||||
|
||||
client.click(`${itemRow} i[class*="trash"]`);
|
||||
|
||||
|
||||
@ -1,281 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
describe('Controller: TemplatesList', () => {
|
||||
// Setup
|
||||
let scope,
|
||||
rootScope,
|
||||
state,
|
||||
TemplatesListController,
|
||||
GetChoices,
|
||||
Alert,
|
||||
Prompt,
|
||||
InitiatePlaybookRun,
|
||||
rbacUiControlService,
|
||||
canAddDeferred,
|
||||
q,
|
||||
TemplatesService,
|
||||
JobTemplateModel,
|
||||
deleteWorkflowJobTemplateDeferred,
|
||||
deleteJobTemplateDeferred,
|
||||
jobTemplateGetDepDeferred,
|
||||
Dataset;
|
||||
|
||||
beforeEach(angular.mock.module('awApp'));
|
||||
beforeEach(angular.mock.module('templates', ($provide) => {
|
||||
|
||||
state = jasmine.createSpyObj('state', [
|
||||
'$get',
|
||||
'transitionTo',
|
||||
'go'
|
||||
]);
|
||||
|
||||
state.params = {
|
||||
id: 1
|
||||
};
|
||||
|
||||
rbacUiControlService = {
|
||||
canAdd: function(){
|
||||
return angular.noop;
|
||||
}
|
||||
};
|
||||
|
||||
TemplatesService = {
|
||||
deleteWorkflowJobTemplate: function(){
|
||||
return angular.noop;
|
||||
},
|
||||
deleteJobTemplate: function(){
|
||||
return angular.noop;
|
||||
}
|
||||
};
|
||||
|
||||
Dataset = {
|
||||
data: {
|
||||
results: []
|
||||
}
|
||||
};
|
||||
|
||||
GetChoices = jasmine.createSpy('GetChoices');
|
||||
Alert = jasmine.createSpy('Alert');
|
||||
Prompt = jasmine.createSpy('Prompt').and.callFake(function(args) {
|
||||
args.action();
|
||||
});
|
||||
InitiatePlaybookRun = jasmine.createSpy('InitiatePlaybookRun');
|
||||
|
||||
$provide.value('GetChoices', GetChoices);
|
||||
$provide.value('Alert', Alert);
|
||||
$provide.value('Prompt', Prompt);
|
||||
$provide.value('state', state);
|
||||
$provide.value('InitiatePlaybookRun', InitiatePlaybookRun);
|
||||
}));
|
||||
|
||||
beforeEach(angular.mock.inject( ($rootScope, $controller, $q, _state_, _ConfigService_, _GetChoices_, _Alert_, _Prompt_, _InitiatePlaybookRun_) => {
|
||||
scope = $rootScope.$new();
|
||||
rootScope = $rootScope;
|
||||
q = $q;
|
||||
state = _state_;
|
||||
GetChoices = _GetChoices_;
|
||||
Alert = _Alert_;
|
||||
Prompt = _Prompt_;
|
||||
InitiatePlaybookRun = _InitiatePlaybookRun_;
|
||||
canAddDeferred = q.defer();
|
||||
deleteWorkflowJobTemplateDeferred = q.defer();
|
||||
deleteJobTemplateDeferred = q.defer();
|
||||
jobTemplateGetDepDeferred = q.defer();
|
||||
|
||||
rbacUiControlService.canAdd = jasmine.createSpy('canAdd').and.returnValue(canAddDeferred.promise);
|
||||
|
||||
TemplatesService.deleteWorkflowJobTemplate = jasmine.createSpy('deleteWorkflowJobTemplate').and.returnValue(deleteWorkflowJobTemplateDeferred.promise);
|
||||
TemplatesService.deleteJobTemplate = jasmine.createSpy('deleteJobTemplate').and.returnValue(deleteJobTemplateDeferred.promise);
|
||||
|
||||
JobTemplateModel = function () {
|
||||
this.getDependentResourceCounts = function() {
|
||||
return jobTemplateGetDepDeferred.promise;
|
||||
};
|
||||
};
|
||||
|
||||
TemplatesListController = $controller('TemplatesListController', {
|
||||
$scope: scope,
|
||||
$rootScope: rootScope,
|
||||
$state: state,
|
||||
GetChoices: GetChoices,
|
||||
Alert: Alert,
|
||||
Prompt: Prompt,
|
||||
InitiatePlaybookRun: InitiatePlaybookRun,
|
||||
rbacUiControlService: rbacUiControlService,
|
||||
TemplatesService: TemplatesService,
|
||||
JobTemplateModel: JobTemplateModel,
|
||||
Dataset: Dataset
|
||||
});
|
||||
}));
|
||||
|
||||
describe('scope.editJobTemplate()', () => {
|
||||
|
||||
it('should call Alert when template param is not present', ()=>{
|
||||
scope.editJobTemplate();
|
||||
expect(Alert).toHaveBeenCalledWith('Error: Unable to edit template', 'Template parameter is missing');
|
||||
});
|
||||
|
||||
it('should transition to templates.editJobTemplate when type is "Job Template"', ()=>{
|
||||
|
||||
var testTemplate = {
|
||||
type: "Job Template",
|
||||
id: 1
|
||||
};
|
||||
|
||||
scope.editJobTemplate(testTemplate);
|
||||
expect(state.transitionTo).toHaveBeenCalledWith('templates.editJobTemplate', {job_template_id: 1});
|
||||
});
|
||||
|
||||
it('should transition to templates.templates.editWorkflowJobTemplate when type is "Workflow Job Template"', ()=>{
|
||||
|
||||
var testTemplate = {
|
||||
type: "Workflow Job Template",
|
||||
id: 1
|
||||
};
|
||||
|
||||
scope.editJobTemplate(testTemplate);
|
||||
expect(state.transitionTo).toHaveBeenCalledWith('templates.editWorkflowJobTemplate', {workflow_job_template_id: 1});
|
||||
});
|
||||
|
||||
it('should call Alert when type is not "Job Template" or "Workflow Job Template"', ()=>{
|
||||
|
||||
var testTemplate = {
|
||||
type: "Some Other Type",
|
||||
id: 1
|
||||
};
|
||||
|
||||
scope.editJobTemplate(testTemplate);
|
||||
expect(Alert).toHaveBeenCalledWith('Error: Unable to determine template type', 'We were unable to determine this template\'s type while routing to edit.');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
xdescribe('scope.deleteJobTemplate()', () => {
|
||||
|
||||
it('should call Alert when template param is not present', ()=>{
|
||||
scope.deleteJobTemplate();
|
||||
expect(Alert).toHaveBeenCalledWith('Error: Unable to delete template', 'Template parameter is missing');
|
||||
});
|
||||
|
||||
it('should call Prompt if template param is present', ()=>{
|
||||
|
||||
var testTemplate = {
|
||||
id: 1,
|
||||
name: "Test Template",
|
||||
type: "Job Template"
|
||||
};
|
||||
|
||||
scope.deleteJobTemplate(testTemplate);
|
||||
jobTemplateGetDepDeferred.resolve([]);
|
||||
rootScope.$apply();
|
||||
expect(Prompt).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call TemplatesService.deleteWorkflowJobTemplate when the user takes affirmative action on the delete modal and type = "Workflow Job Template"', ()=>{
|
||||
// Note that Prompt has been mocked up above to immediately call the callback function that gets passed in
|
||||
// which is how we access the private function in the controller
|
||||
|
||||
var testTemplate = {
|
||||
id: 1,
|
||||
name: "Test Template",
|
||||
type: "Workflow Job Template"
|
||||
};
|
||||
|
||||
scope.deleteJobTemplate(testTemplate);
|
||||
jobTemplateGetDepDeferred.resolve([]);
|
||||
rootScope.$apply();
|
||||
expect(TemplatesService.deleteWorkflowJobTemplate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call TemplatesService.deleteJobTemplate when the user takes affirmative action on the delete modal and type = "Workflow Job Template"', ()=>{
|
||||
// Note that Prompt has been mocked up above to immediately call the callback function that gets passed in
|
||||
// which is how we access the private function in the controller
|
||||
|
||||
var testTemplate = {
|
||||
id: 1,
|
||||
name: "Test Template",
|
||||
type: "Job Template"
|
||||
};
|
||||
|
||||
scope.deleteJobTemplate(testTemplate);
|
||||
jobTemplateGetDepDeferred.resolve([]);
|
||||
rootScope.$apply();
|
||||
expect(TemplatesService.deleteJobTemplate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('scope.submitJob()', () => {
|
||||
|
||||
it('should call Alert when template param is not present', ()=>{
|
||||
scope.submitJob();
|
||||
expect(Alert).toHaveBeenCalledWith('Error: Unable to launch template', 'Template parameter is missing');
|
||||
});
|
||||
|
||||
it('should call InitiatePlaybookRun when type is "Job Template"', ()=>{
|
||||
|
||||
var testTemplate = {
|
||||
type: "Job Template",
|
||||
id: 1
|
||||
};
|
||||
|
||||
scope.submitJob(testTemplate);
|
||||
expect(InitiatePlaybookRun).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
xit('should call [something] when type is "Workflow Job Template"', ()=>{
|
||||
|
||||
var testTemplate = {
|
||||
type: "Workflow Job Template",
|
||||
id: 1
|
||||
};
|
||||
|
||||
scope.submitJob(testTemplate);
|
||||
expect([something]).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call Alert when type is not "Job Template" or "Workflow Job Template"', ()=>{
|
||||
|
||||
var testTemplate = {
|
||||
type: "Some Other Type",
|
||||
id: 1
|
||||
};
|
||||
|
||||
scope.submitJob(testTemplate);
|
||||
expect(Alert).toHaveBeenCalledWith('Error: Unable to determine template type', 'We were unable to determine this template\'s type while launching.');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('scope.scheduleJob()', () => {
|
||||
|
||||
it('should transition to jobTemplateSchedules when type is "Job Template"', ()=>{
|
||||
|
||||
var testTemplate = {
|
||||
type: "Job Template",
|
||||
id: 1
|
||||
};
|
||||
|
||||
scope.scheduleJob(testTemplate);
|
||||
expect(state.go).toHaveBeenCalledWith('jobTemplateSchedules', {id: 1});
|
||||
});
|
||||
|
||||
it('should transition to workflowJobTemplateSchedules when type is "Workflow Job Template"', ()=>{
|
||||
|
||||
var testTemplate = {
|
||||
type: "Workflow Job Template",
|
||||
id: 1
|
||||
};
|
||||
|
||||
scope.scheduleJob(testTemplate);
|
||||
expect(state.go).toHaveBeenCalledWith('workflowJobTemplateSchedules', {id: 1});
|
||||
});
|
||||
|
||||
it('should call Alert when template param is not present', ()=>{
|
||||
scope.scheduleJob();
|
||||
expect(Alert).toHaveBeenCalledWith('Error: Unable to schedule job', 'Template parameter is missing');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user